Repository navigation
docs/spec: close Go verifier findings on Anchor Format §1 escaping rules, verifier-compatibility README, and Acta crosswalk (#536) - #537
Conversation
imran-siddique
left a comment
There was a problem hiding this comment.
Checked 92ef2fd. The new test file passes 32 of 32 here, also on a trial merge with #533, and turning ensure_ascii off in canonical_claim_bytes fails 4 of them.
One normative gap. #536 item 4 said lone surrogates are rejected. They are not: anchor_bytes({"s": "\ud800"}) and canonical_claim_bytes both return {"s":"\ud800"}, and the new bullet folds them into U+007F to U+FFFF. A Go or Rust peer cannot hold that string, so its leaf hash differs. Please put unpaired surrogates outside the profile next to non-integer numbers, and either reject them in both serializers here or open a follow-up issue for the code.
| ``` | ||
|
|
||
| Claims MUST be JSON objects. TRACE Trust Records contain only strings, integers, booleans, nulls, arrays, and objects. Claims containing non-integer numbers are outside this profile, because cross-language float serialization is not canonical, and claims containing an integer outside -9007199254740991 to 9007199254740991 are outside it for the same reason: an implementation of the four rules above in a language whose only number type is the IEEE 754 double writes one value for two distinct integers. TRACE v0.2 §3.2.2 already holds a Trust Record to that range, so a schema-valid record cannot reach this; a claim that is not a Trust Record can, and this is where it is excluded. | ||
| Claims MUST be JSON objects. TRACE Trust Records contain only strings, integers, booleans, nulls, arrays, and objects. Claims containing non-integer numbers are outside this profile, because cross-language float serialization is not canonical, and claims containing an integer outside -9007199254740991 to 9007199254740991 are outside it for the same reason: an implementation of the five rules above in a language whose only number type is the IEEE 754 double writes one value for two distinct integers. TRACE v0.2 §3.2.2 already holds a Trust Record to that range, so a schema-valid record cannot reach this; a claim that is not a Trust Record can, and this is where it is excluded. |
There was a problem hiding this comment.
Add unpaired surrogates (U+D800 to U+DFFF not part of a pair) to the out-of-profile list here.
| @pytest.mark.parametrize("serialize", SERIALIZERS) | ||
| def test_rule_4_integer_zero_is_written_without_minus_sign(serialize) -> None: | ||
| """Integer zero (-0) is serialized as 0, never -0.""" | ||
| assert serialize({"n": -0}) == b'{"n":0}' |
There was a problem hiding this comment.
-0 is the int 0 before it reaches the serializer, and json.loads("-0") is 0 too, so this cannot fail. Keep the spec bullet for implementations with a signed zero; drop or relabel the test.
|
Addressed in 563a8c3:
|
dinakarjs
left a comment
There was a problem hiding this comment.
Independent technical review of 563a8c3, including the surrogate refusal update. No blocking defect found in the six-file change.
Validation in a fresh Python 3.12.14 environment installed from the repository's hashed root and conformance test locks, with both packages installed editable:
- Reference suite: 4,070 passed, 48 skipped; 97% aggregate coverage.
- Conformance suite: 778 passed, 5 xpassed.
- New canonical-rules module: 37 passed.
- Ruff passes for root src/tests and the changed conformance inclusion module; mypy passes all 15 reference source files. Diff whitespace and dash checks pass. A broader root-config Ruff scan of conformance reports 45 diagnostics, all in 16 files unchanged by this PR; that broader scan is not a clean lint result.
Independent byte controls: I wrote a separate Python encoder directly from the five stated rules, without json.dumps or rfc8785 for expected bytes. Both serializers exactly match its 12,963,355-byte output for a single string containing every one of the 1,112,064 Unicode scalar values, and match additional recursive key-order, escaped-key, safe-integer-boundary, boolean/null, slash, and signature-field cases. The alphabet-output SHA-256 is b48cf55e7ca0ce46a0ea7ee015e657c15aee5c6d8f1de05908cb8a7af82a3f6f.
All 2,048 surrogate code points were independently exercised as a value, object key, and nested value against both serializers: 12,288 expected refusals, with the documented exception types. This confirms the newest fix covers the original gap on both surfaces.
Behavioral weakening checks retained symbols and valid ASCII controls: disabling each serializer's string refusal or key refusal separately produces 5 failing refusal cases and 2 passing positive controls (four mutations); disabling ensure_ascii in the conformance serializer produces 1 failing BMP-escaping case and 3 passing controls. All temporary mutations were restored and the checkout is clean.
Scope: this is source/runtime evidence from a separately written Python oracle, not an independent-language interoperability result or hardware acceptance claim. The integer-zero case is a positive serialization control; Python integer -0 is already 0 before serialization. This COMMENT records technical findings and does not replace maintainer approval or the project's normative-change process.
|
@drk-ram the surrogate change checks out on 563a8c3: both serializers refuse a lone surrogate in a value or a key, a paired one (U+1F600) still encodes, and the canonical-rules module passes 37 of 37. A trial merge with main is clean. One thread is still open. |
|
@imran-siddique Updated in 61e9b61: \ est_rule_4_integer_zero_is_written_without_minus_sign\ now explicitly states and asserts that it guards the \spec/registry-anchor-v1.md\ §1 bullet only (since Python's integer literal -0\ is already the \int\ \ |
noah-ing
left a comment
There was a problem hiding this comment.
Reviewed exact head 61e9b61e41504483a31a2735f95bb33ce4ba696b. The latest commit correctly makes the integer-zero test a spec-text guard, and the prior lone-surrogate gap is closed in both serializers. An independent encoder matched both implementations on 1,000 nested valid claims; nested lone-surrogate values and keys were refused. Locally, 347 targeted tests passed, the conformance suite passed 778 tests (5 xpassed), Ruff/mypy/dash/diff checks passed, and the review-bar backcheck produced 8 behavioral failures on the base with no missing-symbol failures. Current-head hosted test, conformance, CodeQL and fuzz jobs are green. I found no blocking technical defect and am approving with two bounded documentation notes.
The local full root suite reached 2,738 passes and 36 skips before a local macOS test_review_bar worktree-cleanup assertion failed; I am not claiming a full local suite pass. Hosted Linux test jobs pass. Because this file identifies as a normative companion and the change adds a refusal, please have a maintainer decide whether CONTRIBUTING's changelog requirement applies before merge. My Reviewer approval does not satisfy the current-head named-maintainer gate.
dinakarjs
left a comment
There was a problem hiding this comment.
Concrete companion for the two bounded documentation notes in Noah's current-head review, based on exact head 61e9b61:
-
Registry-anchor section 0 now excludes DEL (U+007F) from its ASCII equality shortcut, explicitly distinguishes JCS's literal DEL from the anchor's \u007f, and names DEL as a useful canonicalizer-substitution control. I checked RFC 8785 section 3.2.2.2 directly: https://www.rfc-editor.org/rfc/rfc8785.html#section-3.2.2.2 . The companion tests both serializers against explicit DEL key/value bytes and the RFC 8785 implementation, with accepting twins containing every ASCII code point below DEL and a safe-boundary integer. This corrects the explanatory shortcut; it does not change the serializers or section 1's rules.
-
The Acta introduction, field-mapping attribution and evidence_hash row now cite section 3.3.2 for external-evidence fields/verification while retaining section 3.3.3 as the informative action-receipt profile built on it. The old test's global ban on the string 3.3.2 is replaced with scoped checks for those actual claims and both local section links. This leaves the separate draft-03 update in #554 to its owner rather than duplicating it.
Validation on Linux/Python 3.12.14 with this head's hashed reference/conformance dependencies and editable checkout imports: 144 related root tests passed, 5 skipped; conformance 778 passed, 5 xpassed; the final canonical-rules module has 41 passes. Repository Ruff, source mypy (15 files), dash and whitespace checks pass. Restoring only the old section-0 text makes its documentation guard fail; restoring only the old Acta document makes the evidence-attribution guard fail. Both restorations fail on assertions, not missing symbols; source restored before final validation. Published tree 231cf162e92962253a7c7f9c574574658661a2a6 equals the tested tree.
This is a DCO-signed, three-file docs/tests companion for author adoption, not an upstream merge or independent approval of my edits. Runtime, schemas and committed fixture bytes are unchanged. The maintainer decision about the underlying PR's normative-companion/changelog process remains open; this editorial clarification does not settle it. Implemented with OpenAI Codex assistance.
|
Rebased onto \origin/main\ (\�7f0858, resolving the #554 merge conflict in \docs/crosswalks/acta-decision-receipts.md) and incorporated the review notes from @noah-ing and @dinakarjs in \907f9ee:\n- Narrowed \spec/registry-anchor-v1.md\ §0's equality shortcut to ASCII other than DEL (\U+007F), contrasting JCS's literal DEL with §1's \\u007f, and added the paired DEL key/value JCS-vs-anchor controls in \ ests/test_registry_anchor_canonical_rules.py.\n- Updated \docs/crosswalks/acta-decision-receipts.md\ on top of #554 to cite §3.3.2 for external-evidence fields/verification and §3.3.3 for the informative action-receipt profile built on it, with scoped assertions in \ ests/test_registry_anchor_canonical_rules.py.\n- Added a \CHANGELOG.md\ entry covering the |
61e9b61 to
907f9ee
Compare
imran-siddique
left a comment
There was a problem hiding this comment.
Thanks @drk-ram. The -0 test now says it guards the section 1 bullet only, which was the last open point. I also checked the new section 0 wording on 907f9ee: anchor_bytes({"s": "\x7f"}) gives {"s":"\u007f"} while the signing serializer writes DEL as a literal byte, so excluding DEL from the shortcut is right. Thanks @noah-ing and @dinakarjs for the DEL catch.
|
@imran-siddique Rebased onto \origin/main\ (\4c2883d) in \�51dc86, keeping both [Unreleased]\ entries in \CHANGELOG.md. |
907f9ee to
b51dc86
Compare
noah-ing
left a comment
There was a problem hiding this comment.
Reviewed current head b51dc86. The DEL/JCS boundary and Acta section mapping from my prior review are addressed. I exercised the producer and inclusion verifier against three committed records, all proof positions, body/signature/path/root alteration controls, Unicode/DEL divergence, and out-of-profile refusals. The 27 committed inclusion vectors matched in Python and the independent Node cross-check; current-head hosted technical checks pass. I have no further requested changes on this head.
dinakarjs
left a comment
There was a problem hiding this comment.
Current-head follow-through at b51dc86: reviewed the seven-file delta against base 4c2883d and ran tests/test_registry_anchor_canonical_rules.py with this checkout on PYTHONPATH under Python 3.12.14: 41 passed. The DEL distinction, recursive code-point ordering, supplementary-plane escapes, surrogate refusals in keys/values, and corrected Acta/profile references are covered by the selected checks. The integer-zero check is explicitly a specification-text check, not runtime evidence for a Python signed-zero distinction.
This PR includes our review companion, so this is incorporation verification rather than independent approval. Imran’s outstanding review disposition and the eligible maintainer gate still require maintainer action. No normative acceptance or merge is implied.
lywinged
left a comment
There was a problem hiding this comment.
One clause to add to §1 and one word to change, carried on the rebase the CHANGELOG conflict already needs, and the description brought to this head; approval goes on that head. Reviewed at b51dc86 on 4c2883d.
Measured on b51dc86 with the hash-pinned requirements: ruff, mypy and the dash check clean, the root suite 4644 passed and 48 skipped, the conformance suite 874 passed, 2 skipped and 5 xpassed, and the new module 41 tests. review_bar.py against the base reports 9 failures it sorts as behaviour, the five surrogate refusals and four text guards, and none on a missing name. Each §1 bullet says what json.dumps(..., ensure_ascii=True) does, checked by running it. DEL comes out as \u007f, the control characters as lowercase \u00xx, the five short escapes and a bare /, and a supplementary-plane character as a lowercase pair. Keys come in code-point order, , and : carry no space after either, integer zero is 0, and there is no trailing newline. A lone surrogate is written rather than refused, which is why the refusal has to be separate; both serializers now carry it for values and for keys. §0's new boundary matches RFC 8785 §3.2.2.2, which writes anything outside the control range as is, the backslash and the quote aside, and whose note requires an error on a lone surrogate. The Acta crosswalk's introduction, its field-mapping line and its evidence_hash row cite the section that defines evidence_hash and the verification rule, 3.3.2, and the informative profile that §3.3.3 itself says can build on it, which is what the spec says. The README's claim holds: #408 put obligations 2 and 3 of #116 into §3.3 as MUSTs, and docs/verification.md step 4 says the same and defers 1 and 4. No objection to the substance.
-
§1 still leaves one rule to the Python expression, and it is the one a Go implementer trips on first. The bullets say what is escaped and never say that the rest of printable ASCII is written as itself. Go's
encoding/jsonescapes<,>and&unlessSetEscapeHTML(false)is called, by its own doc comment onMarshaland as run here on Go 1.24.7, wherejson.Marshalwrites the stringa<b>&casa\u003cb\u003e\u0026c. An implementation of the five bullets as written overjson.Marshaltherefore emits a different leaf for any string holding one of them. The verifier the findings came from wrote its own serializer and states the rule itself,case r >= 0x20 && r <= 0x7e: b.WriteByte(byte(r)), under a comment that §1's prose had left the escapes to the reference expression. One clause closes it, in the string-escaping bullet after the slash. The parameter below goes intest_rule_3_short_escapes_and_unescaped_slash, the test that lists the escapes.test_ascii_del_distinguishes_signing_and_anchor_bytesalready holds every character below DEL equal torfc8785.dumpsthrough both serializers, so it is for the reader of the bullet, not for a gap:every other character in `U+0020` to `U+007E` is written as itself, including `<`, `>` and `&`; ("<>&", b"<>&"),One word in the same section's surrogate paragraph. A schema-valid record can carry the escape, since the schema's string type admits it and
json.loadsaccepts it; what cannot reach the exclusion is a signed record, because the RFC 8785 step in §3.2.2 refuses a lone surrogate.a schema-valid recordthere isa signed record. -
The description, whose Type of change is what CONTRIBUTING's review timeline is read against. Item 3 still says the two stale
3.3.2citations change to3.3.3, and the 10-21 comment on #536 reports main that way after #554. The head cites3.3.2again, on purpose and for a different reason: v0.2's §3.3.2 is whereevidence_hashand the verification rule are defined, and §3.3.3 is the informative profile that says it builds on them, which is what the CHANGELOG entry says. Item 3 should say the same, since on #536 the number reads as the stale one coming back. The body also quotes the string-escaping bullet in full, so the clause goes there with it. Under Type of change all five boxes are ticked, editorial through breaking, schema change and example addition, where the diff touches no schema file and adds no example. The checklist ticks aCHANGEDmarker the spec diff does not carry and a backward-compatibility statement the body does not carry. One class decides the clock, and this is a non-breaking change. The serializer expression is untouched in both files, so every claim the profile accepted before gives the same bytes, and the new exclusion reaches only a value that the RFC 8785 step in §3.2.2 already refuses, so no signed record carries it. #536 opened on 2026-10-15, so the fifth business day is 2026-10-22; the template's fourteen-day line is stale against the thirty in CONTRIBUTING and GOVERNANCE either way. Please leave Non-breaking as the one ticked box and untick the two breaking-only checklist boxes. The PR names no sponsor; CONTRIBUTING step 3 has a reviewer say so, and the sentence naming a sponsoring maintainer, as three CHANGELOG entries carry it, is the Project Lead's to add here or to decline, not the author's. The counts are from an earlier head as well: 348 and 7 are 352 and 9 on this one, 778 is 874, and mypy's 15 source files are 16.
The rebase that carries all of it: main has moved since 4c2883d, a trial merge with 295c36d conflicts in CHANGELOG.md again, both sides adding under Unreleased, and inclusion.py and registry-anchor-v1.md merge clean. The push dismisses the approvals on this head as the pushes to 907f9ee and b51dc86 dismissed the two before them, so approval goes on the new one.
One thing I would not hold the merge for. Item 3 of #448, the README finding, is closed on the README and still open on two surfaces the README describes. Each of the eight fixtures carries "status": "under review, not accepted normative text" in its proposal object, from gen_vectors.py, and the VerificationResult docstring in revocation.py says profile and accepted_profiles are "not accepted normative text", in bold. Regenerating the wrapper leaves the signed records as they are, and test_vector_01_is_the_verifier_compatibility_record_unchanged pins the record and the key only. The one assertion that reads the wording is in test_verifier_compatibility_vector, which checks that each fixture's status contains "not accepted normative text", so it changes with the generator. Either here with the generator rerun, or named on #448 as what is left of item 3.
|
Thank you for the thorough review, @lywinged! Addressed on
|
b51dc86 to
89529c1
Compare
There was a problem hiding this comment.
Approve at 2cef490, which is 89529c1 with main merged in and the CHANGELOG entry moved to main at merge, as the new rule has it. The clause is in the string-escaping bullet after the slash, in the words proposed, and test_registry_anchor_spec_documents_all_five_canonical_rules pins it; ("<>&", b"<>&") is in test_rule_3_short_escapes_and_unescaped_slash; the surrogate paragraph now says a signed record; the description quotes the new bullet and names 3.3.2 as the section that defines evidence_hash and the verification rule. Measured on 89529c1 with the hash-pinned requirements: the new module 43 tests and test_verifier_compatibility_fixtures.py 25, ruff, mypy and the dash check clean, review_bar.py against a8e3559 9 on behaviour and none on a missing name, git diff --check clean; CI, Conformance CI, Review bar and Docs green. The PR names no sponsor; under CONTRIBUTING step 3 I carry it as sponsoring maintainer, and the entry gains that clause when it is written on main. Merging once the checks are green.
…n at merge The entry this pull request proposed is written on main at merge by the merging maintainer, as CONTRIBUTING step 5 now asks, so open pull requests stop conflicting on the one file they would all touch. Signed-off-by: lywinged <48041247+lywinged@users.noreply.github.com>
noah-ing
left a comment
There was a problem hiding this comment.
Reviewed current head 2cef490. The printable ASCII rule, DEL boundary and regression controls, unpaired-surrogate refusal in both serializers, zero test clarification, and Acta §3.3.2/§3.3.3 mapping address my remaining concerns. I matched the six changed files to this head, ran the focused canonical-rules and verifier-fixture tests (43 and 25 passed), and checked independent serialization controls. The current hosted checks show 19 successful and one skipped. I approve this scoped change; the older review conversations still need to be resolved before merge.
rajnisht7
left a comment
There was a problem hiding this comment.
Reviewed the latest head,
- Status still inconsistent- README now correctly says Obligations 2 & 3 are normative, but gen_vectors.py (and every fixture) + the VerificationResult docstring still say “under review, not accepted normative text”. kindly align both so 2/3 are marked normative and 1/4 remain deferred, then regenerate the fixtures.
- Python reference in §1 is inaccurate-“In Python this is exactly json.dumps(...)” still accepts unpaired surrogates, while the normative text and the actual serializers reject them, qualify the snippet so it matches the full profile (reject first, then json.dumps).
noah-ing
left a comment
There was a problem hiding this comment.
I revisited current head 4207896ec62a87e4271caff64eb127cf16e4a5c3 after Rajnish's review. This head merges main into the previously reviewed 2cef490; the affected specification, generator, fixture and result-documentation lines are unchanged. In an isolated Python 3.12 environment installed from the hash-locked dev requirements, the 102 focused anchor and verifier-compatibility tests passed. I independently reproduced both documentation inconsistencies below. My earlier approval was scoped to the preceding corrections; I am updating my disposition because these points remain in the current PR.
-
spec/registry-anchor-v1.md§1 says the Python expression using rawjson.dumpsis exactly the canonical-claim operation. Given a string containing an unpaired U+D800 surrogate, that expression emits escaped JSON bytes, while bothagentrust_trace.sign.anchor_bytesandtrace_tests.inclusion.canonical_claim_bytesreject the claim with their documented profile errors. Please qualify the example as applying after the profile's input exclusions have been checked, or show the exclusion step in the example. The normative text and example should give an implementer the same acceptance boundary. -
examples/verifier-compatibility/README.mdcorrectly calls Obligations 2 and 3 of #116 normative, consistent with the MUSTs inspec/trace-v0.2.md§3.3. Butgen_vectors.py, all eight generated fixtures, and theVerificationResultdocumentation still say these obligations are “under review, not accepted normative text”; a fixture test also asserts that stale status. Please align the generator and result documentation with the adopted obligations, retain the distinction that Obligations 1 and 4 are deferred, regenerate the fixtures, and update the assertion.
These are consistency corrections; I found no change to the serializer behavior in this main-merge commit. The passing hosted checks and focused tests do not exercise these claims. I can review the narrow follow-up once it is on a new head.
dinakarjs
left a comment
There was a problem hiding this comment.
Follow-up on current head 4207896: my earlier incorporation verification does not close the two consistency findings subsequently recorded by Rajnish and Noah.
The narrow follow-up should qualify the Python json.dumps example as serialization after the profile's input exclusions are checked, then align the verifier-compatibility generator, generated fixtures, result documentation and stale test assertion. Obligations 2/3 should be identified as adopted while 1/4 remain deferred. Regenerate fixtures from the corrected generator rather than editing only their outputs.
These are the existing reviewers' open findings, not a claim that I independently reran their reproductions today. This PR includes my earlier contribution, so this COMMENT does not supply independent approval. Reviewed with OpenAI Codex.
|
Thank you @rajnisht7, @noah-ing, and @dinakarjs! Addressed both points in
|
9fc330a to
2b98d83
Compare
…verifier-compatibility README, and Acta crosswalk (agentrust-io#536) - State the five language-independent canonical JSON rules in spec/registry-anchor-v1.md §1 (recursive Unicode code-point key sorting, ',' and ':' separators without whitespace, exact string and surrogate-pair escaping rules including \u007f for U+007F, integer 0 without minus sign, and no trailing newline). - Update examples/verifier-compatibility/README.md to reflect that Obligations 2 and 3 of agentrust-io#116 are now normative in spec/trace-v0.2.md §3.3 and documented in docs/verification.md ("Step 4: Check the EAT profile"), while Obligations 1 and 4 remain deferred. - Update the two stale v0.1 section 3.3.2 citations in docs/crosswalks/acta-decision-receipts.md to 3.3.3. - Add tests/test_registry_anchor_canonical_rules.py pinning all five rules across agentrust_trace.sign.anchor_bytes and trace_tests.inclusion.canonical_claim_bytes plus the documentation fixes. Closes agentrust-io#536 Refs agentrust-io#448 Signed-off-by: Raghuvar Karthik Durga <177662415+drk-ram@users.noreply.github.com>
2b98d83 to
608c2d6
Compare
dinakarjs
left a comment
There was a problem hiding this comment.
Follow-up review at exact head 2b98d83, isolated checkout with hash-locked development dependencies, Linux/Python 3.12.14.
Verified the follow-up qualification of the Python json.dumps example and the consistent status of verifier-compatibility Obligations 2/3 versus deferred 1/4 in the generator, eight generated fixtures, result documentation and tests. Running gen_vectors.py reproduces the committed fixtures without a diff. Full local pytest: 5,263 passed, 98 skipped; Ruff, the dash check and mypy pass. Current-head hosted CI and the returned technical workflows succeed. My earlier two follow-up findings are addressed. This PR includes my earlier contribution, so this is incorporation verification and does not supply independent approval.
Reviewed with OpenAI Codex.
noah-ing
left a comment
There was a problem hiding this comment.
I reviewed exact head 608c2d673434bc98c501bc7dc20456e657244236. Both points in my changes request are addressed: Anchor Format §1 now qualifies the json.dumps example as applying after the profile's input exclusions, and the verifier-compatibility generator, eight regenerated fixtures, VerificationResult documentation, and test assertion consistently distinguish normative Obligations 2/3 from deferred Obligations 1/4. Regenerating the fixtures produced no diff. Independent surrogate and canonicalization controls also matched the stated boundary.
The exact-head focused tests passed (78 passed, 5 skipped), as did the conformance suite (958 passed, 2 skipped, 5 xpassed). The 34 TypeScript integration tests passed with npm on PATH; the hosted technical checks are green. A broader local root run had two macOS review_bar worktree-cleanup failures unrelated to the changed files, so I am not claiming a completely green local root suite.
I have no further changes requested on the reviewed corrections and approve this scope. The independent maintainer approval gates remain separate.
lywinged
left a comment
There was a problem hiding this comment.
Approve. Reviewed at 608c2d6 on e9ab302.
Both consistency points are in; rajnisht7 on 25 October and noah-ing on 28 October asked for the same two things. Section 1 of registry-anchor-v1.md now says the json.dumps line applies once the profile's exclusions have been checked, and names them; gen_vectors.py, the eight fixtures, the VerificationResult docstring and the fixture test all say Obligations 2 and 3 are normative in section 3.3 of spec/trace-v0.2.md and 1 and 4 deferred. 608c2d6 carries the same tree as 2b98d83, remade as one commit on e9ab302.
What I checked rather than read:
- CI's commands, run here on 3.11 and 3.12: ruff, the dash check and mypy clean; the suite at 5263 passed and 98 skipped on both; the replay experiment, the two
LC_ALL=C PYTHONUTF8=0runs, thePYTHONOPTIMIZE=2command and the two--helpcalls pass. 3.13, which Conformance CI also runs for the conformance package this change touches, is not installed here. - Running
gen_vectors.pyhere reproduces the eight fixtures with no diff. anchor_bytesandcanonical_claim_bytesboth refuse an unpaired surrogate in a value and in a key, writeDELas\u007fand a supplementary-plane character as a lowercase surrogate pair; onmainboth wrote the unpaired surrogate as\ud800.sign_recordrefuses the same input withCanonicalizationErrorfrom the RFC 8785 step.- Section 3.3.2 of
spec/trace-v0.2.mdis whereevidence_hashis named, section 3.3 carries theaccepted_profilesMUSTs the README now cites, anddocs/verification.mdhas the "Step 4: Check the EAT profile" heading. tools/review_bar.pyreports 17 changed test cases failing on behaviour at the merge base: the 5 surrogate refusals, 4 assertions on the documents' text, and 8 fixture cases that fail on the base only because the tool copiestests/and notexamples/.
Things I would not hold the merge for:
- Section 1's last sentence now says a signed record cannot reach the exclusions because section 3.2.2 holds it to "that range"; the range covers the integers, and what keeps a lone surrogate out of a record signed by this library is the RFC 8785 step, which the sentence does not name. The two comments in
sign.pythat count section 1's exclusions still say two. anchor_bytesdoes not refuse a non-object root, which section 1 lists among the exclusions andcanonical_claim_bytesrefuses:anchor_bytes([1, 2])returns[1,2]. That predates this change.- The commit message's crosswalk bullet describes updating two stale v0.1 citations to 3.3.3, which #481 and #554 already did on
main; this diff adds three citations of 3.3.2, the v0.2 section that names the external-evidence fields, and the new test asserts two of them. The description describes the diff; with one commit, the squash carries the message, and its title's(#536)is the issue's number, unless the merging Maintainer edits it. - The description and the commit message say the new test pins all five rules across both serializers; the rule 4 case reads section 1's text, as its docstring says, since a Python
-0is0before either serializer sees it. - Section 1's exclusions paragraph is also rewritten by #630, differently, so whichever of the two merges second will have to resolve that paragraph.
- The type-of-change box is editorial and the description has no changelog entry, while
anchor_bytesandcanonical_claim_bytesnow refuse inputs they accepted; CHANGELOG records the adapters' surrogate refusals, so the merging Maintainer will want a line for these too.
Generated by Claude Code
What this changes
Closes the three remaining verified findings from the independent Go verifier report (#448 items 3, 16, and 19; tracked in #536):
spec/registry-anchor-v1.md§0 and §1,Canonical claim bytes,src/agentrust_trace/sign.py, andconformance/src/trace_tests/inclusion.py): Replaces the prose summary that deferred byte-level string/number escaping details to Python'sjson.dumpswith the five explicit, language-independent canonical JSON rules, narrows §0's equality boundary with JCS to ASCII other than DEL (U+007F), and excludes unpaired UTF-16 surrogates (U+D800toU+DFFF) in strings and object keys from the anchor-leaf profile (noting that a signed record cannot reach the exclusion because TRACE v0.2 §3.2.2's RFC 8785 step already refuses lone surrogates):","and":", no whitespace."(U+0022),\(U+005C), and the five control charactersU+0008,U+0009,U+000A,U+000C, andU+000Duse the two-character escapes\",\\,\b,\t,\n,\f, and\r;/(U+002F) is not escaped; every other character inU+0020toU+007Eis written as itself, including<,>and&; all other control characters (U+0000toU+001F) and all BMP characters inU+007FtoU+D7FFandU+E000toU+FFFF(includingU+007F, which is escaped as\u007f) use lowercase four-hex-digit\uXXXXescapes; supplementary-plane characters (U+10000toU+10FFFF) use lowercase UTF-16 surrogate pairs (a high surrogate inU+D800toU+DBFFfollowed by a low surrogate inU+DC00toU+DFFF, e.g.\ud8xx\udcxx); output is encoded as ASCII bytes.0(never-0).U+D800toU+DFFF) in strings or object keys are placed outside the profile and rejected by bothagentrust_trace.sign.anchor_bytes(UnanchorableValue) andtrace_tests.inclusion.canonical_claim_bytes(InclusionError).examples/verifier-compatibility/README.md): Updates the opening note, introductory paragraphs, andfailurediscussion so they accurately state that Obligations 2 and 3 of verification.md: format-versioning and verifier-compatibility obligations (evidence outlives verifiers) #116 (nonemptyaccepted_profilesdeclaration and reporting bothprofileandaccepted_profileson the verification result) are now normative inspec/trace-v0.2.md§3.3 and documented indocs/verification.md("Step 4: Check the EAT profile"), while Obligations 1 (emitter version declarations) and 4 (downgrade disclosure) remain deferred.docs/crosswalks/acta-decision-receipts.md): Ensures the introduction, field-mapping line, andreceipt.evidence_hashrow cite3.3.2(whereevidence_hashand the external-evidence verification rule are defined inspec/trace-v0.2.md) and3.3.3(the informative action-receipt profile that builds on them).tests/test_registry_anchor_canonical_rules.py): Pins all five serialization rules (including<,>,&and ASCII DELU+007Fvs JCS) and unpaired-surrogate rejection across bothagentrust_trace.sign.anchor_bytesandtrace_tests.inclusion.canonical_claim_bytes(including key and value escaping) as well as the documentation fixes inspec/registry-anchor-v1.md,examples/verifier-compatibility/README.md, anddocs/crosswalks/acta-decision-receipts.md.Cold-reader review:
\ud8xx\udcxxfor supplementary-plane surrogate pairs could be read literally as restricting high surrogates toU+D800–U+D8FFand low surrogates toU+DC00–U+DCFF, excluding code points such asU+10FFFF(\udbff\udfff). Updatedspec/registry-anchor-v1.md§1 andtests/test_registry_anchor_canonical_rules.pyto specify the full surrogate ranges (a high surrogate in U+D800 to U+DBFF followed by a low surrogate in U+DC00 to U+DFFF, e.g. \ud8xx\udcxx).What failure is and is notsection ofexamples/verifier-compatibility/README.mdstill referred to "The draft normative text for verification.md: format-versioning and verifier-compatibility obligations (evidence outlives verifiers) #116, held for a maintainer to carry". Updated that paragraph to reference verification.md: format-versioning and verifier-compatibility obligations (evidence outlives verifiers) #116 andspec/trace-v0.2.md§3.3 consistently with the updated opening section.tests/test_registry_anchor_canonical_rules.pyexercised string values but not object member names for control/short/hex escapes. Addedtest_rule_3_escaping_applies_to_object_keys_as_well_as_values.@imran-siddiquepointed out thatU+007FtoU+FFFFincluded unpaired UTF-16 surrogates (U+D800toU+DFFF) and thatanchor_bytesandcanonical_claim_bytesaccepted and escaped them (\ud800), whereas UTF-8 peers cannot represent unpaired surrogates. Updatedspec/registry-anchor-v1.md§1 to scope BMP escapes toU+007FtoU+D7FFandU+E000toU+FFFF, put unpaired UTF-16 surrogates (U+D800toU+DFFF) outside the profile, and rejected them in bothanchor_bytesandcanonical_claim_byteswithtest_unpaired_surrogates_in_values_and_keys_are_rejected.Closes #536
Refs #448
Contributor: Raghuvar Durga (@drk-ram)
Type of change
Spec section
Companion specification
spec/registry-anchor-v1.md§0 and §1 (Canonical claim bytes),examples/verifier-compatibility/README.md(referencingspec/trace-v0.2.md§3.3), anddocs/crosswalks/acta-decision-receipts.md(referencingspec/trace-v0.2.md§3.3.2 and §3.3.3).Tests for a fix
tests/test_registry_anchor_canonical_rules.py::test_unpaired_surrogates_in_values_and_keys_are_rejectedfails withDID NOT RAISE UnanchorableValue(foranchor_bytes) andDID NOT RAISE InclusionError(forcanonical_claim_bytes) when the unpaired-surrogate rejection insrc/agentrust_trace/sign.pyandconformance/src/trace_tests/inclusion.pyis switched off (python tools/review_bar.py --base origin/main --head HEAD: 9 fail on behaviour, 0 fail on a missing name or file, 34 pass or skip).Validation commands run and results:
pytest tests/test_registry_anchor_canonical_rules.py tests/test_requirement_keywords_are_attributable.py tests/test_safe_integer_range.py tests/test_public_functions_raise_what_they_document.py tests/test_untrusted_input_stays_on_the_documented_error.py -q— passed (354 passed)pytest tests/test_registry_anchor_canonical_rules.py -q— passed (43 passed)pytest conformance/tests -q— passed (958 passed, 2 skipped, 5 xpassed)python tools/review_bar.py --base origin/main --head HEAD— passed (9 fail on behaviour, 0 fail on a missing name or file, 34 pass or skip)ruff check tests/test_registry_anchor_canonical_rules.py src/agentrust_trace/sign.py conformance/src/trace_tests/inclusion.py— passedpython tools/check_dashes.py— passed (no dashes outside examples/, spec/trace-v0.1.md)mypy src/agentrust_trace— passed (Success: no issues found in 16 source files)Checklist
git commit -s)CHANGELOG.mdupdated (for any normative change)<!-- CHANGED: #NNN: description -->in spec text