Skip to content

docs(crosswalks): Acta decision receipts as a second action-receipt profile#98

Open
tomjwxf wants to merge 4 commits into
agentrust-io:mainfrom
tomjwxf:acta-crosswalk
Open

docs(crosswalks): Acta decision receipts as a second action-receipt profile#98
tomjwxf wants to merge 4 commits into
agentrust-io:mainfrom
tomjwxf:acta-crosswalk

Conversation

@tomjwxf

@tomjwxf tomjwxf commented Jul 8, 2026

Copy link
Copy Markdown

Addresses the acceptance bar @carloshvp set in #97, one point at a time:

TRACE should carry or reference Acta as external evidence by digest/chain head plus issuer/key metadata, not embed a second receipt schema into the core Trust Record.

Done as a cross-walk doc (docs/crosswalks/acta-decision-receipts.md), following the exact pattern of the existing OWASP cross-walk: non-normative, no schema/wire-format change, registered in mkdocs.yml the same way. Acta stays an external, independently specified format (draft-farley-acta-signed-receipts, rev 02); nothing here touches schema/trace-claim.json. The doc includes a concrete (illustrative, non-normative) Trust Record evidence entry showing exactly that shape: chain head + issuer/key metadata, where the chain head is the real, recomputable digest of the fixture chain's latest receipt.

A mapping should state exactly which Acta fields satisfy the action-receipts verifier obligations: canonical preimage, signature verification against a trusted or manifest-bound key, chain ordering, and TRACE/cMCP session or call binding.

The field-mapping table walks the exact shape from spec §3.3.2 / examples/action-receipts/README.md (issuer, issuer_key_id, linked_call_id/session_id, evidence_hash, previous_receipt_hash, decision, signature) against the exact Acta envelope fields, and restates the five verifier checks from the embodied profile for this one, plus a sixth (policy_digest freshness) that is not a signature check and should not be conflated with one.

Any appendix should include negative fixtures: wrong kid/key, broken chain, mismatched session/call binding, and a valid receipt that represents rejection rather than successful physical or business outcome.

Six fixtures in examples/action-receipts/acta/, all real (an actual Ed25519 signer over JCS-canonical bytes, not hand-typed JSON; generator included): valid accepted, valid denied (the rejection case), signature/key mismatch, broken chain, stale policy_digest, and mismatched session/call binding. The last two are deliberately receipts whose signatures verify cleanly but which fail a non-cryptographic check (stale authorization basis, wrong session), to keep those distinctions explicit rather than folding them into the crypto check.

Keep the boundary explicit: Acta/TRACE composition verifies decision/action evidence integrity; it does not prove physical completion or safety certification.

Restated directly against §2.4 and the #34 external-execution-evidence boundary, generalized to the software (non-embodied) case this profile covers.

Every fixture and every doc-internal anchor link was checked against this repo's actual spec headings and fixture format before commit, not assumed. Happy to adjust anything that does not fit the maintainers' preferred shape for this.

tomjwxf added 2 commits July 8, 2026 09:09
…rofile

Non-normative cross-walk, following the acceptance bar from agentrust-io#97: Acta
(draft-farley-acta-signed-receipts-02) is described as external evidence
composed with TRACE's existing 3.3.2 action-receipt pattern, not a schema
change to the Trust Record. Field mapping table uses exact TRACE and Acta
field names. Boundary section restates 2.4's scope limits for the software
(non-embodied) case explicitly.

Five real conformance fixtures (examples/action-receipts/acta/), generated by
an actual Ed25519 signer over JCS-canonical bytes, not hand-typed: valid
accepted, valid denied (the negative-outcome case agentrust-io#95 asks for), signature/
key mismatch, broken chain, and stale policy_digest (valid signature, stale
authorization binding -- a distinct check from cryptographic verification).
Generator script included; every fixture re-verified against its stated
result before commit.
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

🔴 Contributor Check: HIGH

Check Result
Profile HIGH
Credential NONE
Overall HIGH

Automated check by AgenTrust Contributor Check.

@github-actions github-actions Bot added the needs-review:HIGH Contributor check flagged HIGH risk label Jul 8, 2026
…ce bar

1. Add the mismatched session/call binding fixture the acceptance bar
   explicitly lists (06-session-binding-mismatch.json): a validly signed
   receipt presented under the wrong session. Passes signature verification,
   fails the binding check; pairs with 05 to show that signature validity
   alone accepts genuine receipts in the wrong context.

2. Make the digest/chain-head reference concrete: new section showing an
   illustrative Trust Record evidence entry (chain head + issuer/key
   metadata, no embedded schema), with the chain head being the real,
   recomputable digest of the fixture chain's latest receipt.

Also: parent_receipt_hash now consistently sha256:-prefixed (the real link
in 02 previously used bare hex while the tampered value in 04 was prefixed);
02 and 04 regenerated and re-verified accordingly.

@carloshvp carloshvp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes

  1. The fixtures are not draft-farley-acta-signed-receipts-02 envelopes, despite being presented as exact/independently verifiable Acta fixtures. Draft-02 §2 requires a top-level { "payload": {...}, "signature": { "alg", "kid", "sig" } } envelope, with common fields such as type, issued_at, and issuer_id inside payload; §3.1 defines the access-decision fields as type: "protectmcp:decision", tool_name, decision, reason, etc. These fixtures instead put v, type, algorithm, kid, issuer, and issued_at at the top level, use a bare signature string, and place a different custom shape under payload. A conforming draft-02 verifier cannot decode them, so the cross-walk's field mapping and the README's independent-verification claim are currently misleading.

Please regenerate the fixtures using the draft-02 envelope and field names (or explicitly define this as a separate profile rather than claiming draft-02 conformance), then update the mapping and verifier instructions accordingly. Add an automated check that parses each fixture against the claimed Acta schema and verifies the expected positive/negative result; otherwise this drift can recur while the repository's Python tests still pass.

Validation: compared the PR diff and generated fixtures against the published draft-02 receipt structure and access-decision schema; repository Python 3.11/3.12 and CodeQL checks pass, while the contributor gate currently fails. I did not run an Acta verifier because the PR does not add one and the envelopes fail schema inspection before signature verification.

@carloshvp carloshvp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes

The cross-walk direction is useful, but two fixtures do not currently exercise the verifier obligations they claim to cover:

  1. examples/action-receipts/acta/03-signature-key-mismatch.json is byte-for-byte identical to 01-valid-accepted.json. Following the README's independent-verification steps with the published signer-public-key.txt, fixture 03 verifies successfully. The unrelated key exists only transiently inside gen.mjs, so the committed fixture set gives an independent verifier no mismatched key or key-resolution input with which to reproduce the advertised failure. Please commit the mismatched public-key/key-set context (or encode a resolvable kid mismatch) and make the verification instructions deterministic.

  2. 04-broken-chain.json changes parent_receipt_hash after signing, so it only proves that payload tampering invalidates an Ed25519 signature. It does not test chain verification. A valid issuer can sign a receipt whose parent_receipt_hash does not match the actual predecessor; that signature must pass while the separate chain-link check fails. Please generate fixture 04 by signing an intentionally wrong parent hash, then verify both conditions independently: signature valid, predecessor digest mismatch.

Validation performed on head 82e56463aaf0a641933d93a68d891d4066573cbd:

  • Independent Ed25519 verification over compact, recursively key-sorted payload JSON with the committed signer public key: 01 PASS, 03 PASS, 04 FAIL, 05 PASS, 06 PASS.
  • Confirmed 01-valid-accepted.json and 03-signature-key-mismatch.json are byte-identical.

I stopped at these conformance blockers because they need to be corrected before the fixtures can support the cross-walk's interoperability claims.

… make every negative case a committed, reproducible artifact

Addresses both change-request reviews on agentrust-io#98:

1. Envelope conformance: fixtures are now actual draft-02 receipts:
   { payload, signature: { alg: EdDSA, kid, sig } }, access-decision payload
   per s3.1 (type protectmcp:decision, tool_name, decision, reason,
   policy_digest, opaque session_id), issuer_id matching signature.kid, kid
   in the s2.1.1 recommended sb:issuer:<base58-12> format, PureEdDSA over
   JCS(payload) per s5.6, and previousReceiptHash as the bare-hex s5.7 hash
   over the predecessor's ENTIRE envelope including its signature. The
   crosswalk doc and field mapping are rewritten against the real draft
   field names.

2. Fixture 03 is no longer byte-identical to 01: it is signed by a second,
   committed key (mismatched-signer-public-key.txt) while claiming the
   signer's kid, so resolving kid to signer-public-key.txt fails
   deterministically from committed bytes alone.

3. Fixture 04 now tests chain verification, not signature tampering: it is
   VALIDLY SIGNED over a wrong previousReceiptHash (a real receipt hash,
   just the wrong predecessor's), so the signature check passes and only
   recomputation against the actual predecessor fails. The docs no longer
   claim chain and signature failures collapse; under s5.7 they are
   distinct checks, and 04 is the fixture that proves it.

4. Automated check: tests/test_acta_fixtures.py verifies every fixture's
   structure against the draft-02 envelope and its expected
   positive/negative result from the new machine-readable expected.json,
   using only existing project dependencies (rfc8785 for JCS, cryptography
   for Ed25519). Full suite passes locally (97 tests). This also
   cross-validates the JS generator's canonicalization against the Python
   RFC 8785 reference implementation.
@tomjwxf

tomjwxf commented Jul 8, 2026

Copy link
Copy Markdown
Author

@carloshvp both reviews were correct, and thank you for validating against the published draft rather than taking the PR's description at its word. All three findings are fixed in 9053f36:

1. Envelope conformance. You were right: the fixtures used an internal implementation shape, not the draft-02 envelope. They are now conforming receipts: { payload, signature: { alg: "EdDSA", kid, sig } }, access-decision payload per s3.1, issuer_id matching signature.kid, kid in the s2.1.1 recommended sb:issuer:<base58-12> format, PureEdDSA directly over JCS(payload) per s5.6, and previousReceiptHash as the bare-hex s5.7 hash over the predecessor's entire envelope including its signature. The crosswalk doc and mapping table are rewritten against the real draft field names (this also produced a better mapping: evidence_hash now maps to the draft's own payload_digest rather than a hand-wave).

2. Fixture 03. Now signed by a second, committed key (mismatched-signer-public-key.txt) while claiming the signer's kid, so resolving kid to the published signer-public-key.txt fails deterministically from committed bytes alone. No transient state in the generator is needed to reproduce the failure, and a test asserts the mismatch signature is a genuine signature by the committed second key rather than random bytes.

3. Fixture 04. Your framing was the correct one and the docs now say so: it is validly signed over a wrong previousReceiptHash (a real receipt hash, just the wrong predecessor's, so it models reordering rather than garbage). Signature check passes, chain recomputation fails, and the previous claim that chain and signature failures collapse is gone; under s5.7 they are distinct checks and 04 exists to prove it.

Automated check. tests/test_acta_fixtures.py verifies every fixture's structure against the draft-02 envelope and its expected positive/negative result from a machine-readable expected.json, using only dependencies this project already declares (rfc8785 for JCS, cryptography for Ed25519). Full suite passes locally, 97 tests. A side benefit: the Python RFC 8785 reference now cross-validates the JS generator's canonicalization on every run.

On the gate status: as far as I can tell that is the maintainer-approval gate and clears on approval of the head commit, so nothing for me to action there. Happy to adjust anything further.

@carloshvp carloshvp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment review

Re-reviewed current head 9053f367cb7eec1d1324e94a77966d8c8f21b6b7. This update resolves the findings from both earlier change-request reviews:

  • The fixtures now use the draft-02 {payload, signature} envelope and access-decision field names.
  • Fixture 03 is signed by the committed second key while retaining the claimed kid; verification with the resolved signer key fails deterministically.
  • Fixture 04 is validly signed but points to the wrong predecessor; signature verification passes and the independent chain-link check fails.
  • tests/test_acta_fixtures.py now checks envelope structure and the declared signature, chain, policy-freshness, and session-binding outcomes.

Validated locally:

  • uv run --project /tmp/trace-spec-pr98 --extra dev python -m pytest -q tests/test_acta_fixtures.py18 passed.
  • uv run --project /tmp/trace-spec-pr98 --extra dev python -m pytest -q tests97 passed.
  • GitHub checks on this head: Python 3.11, Python 3.12, CodeQL, and Python analysis pass. The remaining failing gate check is the maintainer-approval gate.

No further blocker from my re-review. This single follow-up supersedes and consolidates my two earlier reviews.

@imran-siddique imran-siddique left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment, not a block. This needs a maintainer decision (cc @carloshvp) on one governance point.

Technically the crosswalk is accurate and correctly non-normative: the anchors resolve, the field names match the fixtures, the six Ed25519 fixtures self-verify in CI, and the schema is untouched per #97.

The decision before merge: the crosswalk elevates a non-working-group individual I-D (draft-farley-acta-signed-receipts-02) and points to a company-owned test-vector repo as "the shared conformance suite," framed as a peer profile. The PR does not disclose that the submitter is the draft's author. Whether trace-spec wants to give an external individual draft peer standing is a maintainer call.

Two tightening items if it proceeds:

  • evidence_hash -> payload_digest maps to a field present in none of the six fixtures, so it is asserted, not demonstrated.
  • Acta has no per-call identifier, so the linked_call_id obligation from #97 is only partially satisfiable; call it a gap rather than a clean mapping.

Minor: "exact TRACE terms as used in section 3.3.2" overstates it; 3.3.2 is prose and the field names live only in the informative examples README.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-review:HIGH Contributor check flagged HIGH risk

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants