draft: ApprovalRecord schema (#5)#6
Conversation
|
Thanks Nicolas — this is the right shape for v1. On I would make two small adjustments:
Something like: sha256(JCS({
method: method.toUpperCase(),
url,
amount,
currency,
recipient_wallet: recipient_wallet ?? null,
body
}))
Same audit JSONL with On states, I would keep policy reject out of ApprovalRecord unless a staged approval actually exists. For the human path, Nullable |
Type-only design draft for the in-chat approval receipt discussed in #5. Not yet emitted by agent.ts; implementation lands in a follow-up PR. - ApprovalRoute: "allow" | "reject" | "human_review" (revise reserved) - ApprovalFinalState lifecycle: staged | approved | denied | expired | settled | failed - ApprovalRecord interface with intent_id, mcp_server/tool provenance, nullable actor, settlement target, params_hash binding, expires_at, created_at, route, final_state, audit_event_refs Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Applying @rpelevin's feedback from PR #6: - params_hash format: tagged string `sha256:jcs-v1:<hex>` so the algorithm and canonicalization version are atomic with the value. Input shape is JCS-canonicalized (RFC 8785) instead of string concatenation, removing delimiter and number-coercion ambiguity. Headers stay excluded. - final_state: explicitly scoped to the human-in-the-loop path. Policy rejects (the policy fires `reject` from the start) never produce an ApprovalRecord; they stay in the audit stream as `payment_denied`. - Storage clarified inline: append-only in the audit JSONL tagged with `event: "approval_record"`. Type-only, no emission wiring yet. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
71cf02f to
d6f6188
Compare
|
Applied all five, pushed in d6f6188 (also rebased on main to pick up the 0.1.3 work).
Switch back to ready-for-review whenever you want to take another pass. Implementation (emission in agent.ts + tests + the JCS dep) is the next PR; I'd rather land the schema first and let the shape settle. |
Draft schema for the in-chat approval receipt discussed in #5. Type-only for now: no emission wired into
agent.ts, no tests beyondtsc --noEmit.Based on @rpelevin's proposal in #5 with two refinements:
actoris nullable in v1. blacktea today is mostly one human / one agent / one wallet, so forcing a value would mean people invent one. Field reserved for the multi-tenant case.allow | reject | human_review.rejectanddenyare the same thing here and the rest of the codebase already usesreject.reviseis reserved (returns unsupported in v1) per the discussion.In this PR
src/approval-record.ts:ApprovalRecordinterface plusApprovalRouteandApprovalFinalStateenums.src/index.ts: re-exports.Not in this PR
agent.ts(lands next).Open questions for v1
params_hash. Proposingsha256(method + "\n" + url + "\n" + amount + "\n" + currency + "\n" + (recipient_wallet ?? "") + "\n" + stableStringify(body)). Order-stable, no headers (servers can vary headers without changing the spend).event: "approval_record") or in a separate file. Leaning same file for v1.deniedcovers both human-deny and policy-reject, or whether those need separate states. Probably keepdeniedas the human path and usefinal_state: "settled"withroute: "reject"for policy-reject (policy rejects don't go through approval at all, so the question may be moot).Closes #5.
cc @rpelevin — keen on your read, especially on the
params_hashinput shape.