Skip to content

docs/spec: close Go verifier findings on Anchor Format §1 escaping rules, verifier-compatibility README, and Acta crosswalk (#536) - #537

Open
drk-ram wants to merge 3 commits into
agentrust-io:mainfrom
drk-ram:drk-ram-docs-close-go-verifier-findings-3-16-19
Open

drk-ram wants to merge 3 commits into
agentrust-io:mainfrom
drk-ram:drk-ram-docs-close-go-verifier-findings-3-16-19

Conversation

@drk-ram

@drk-ram drk-ram commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

What this changes

Closes the three remaining verified findings from the independent Go verifier report (#448 items 3, 16, and 19; tracked in #536):

  1. Implementation findings from the Go verifier (#444): 26 items #448 Item 19 (spec/registry-anchor-v1.md §0 and §1, Canonical claim bytes, src/agentrust_trace/sign.py, and conformance/src/trace_tests/inclusion.py): Replaces the prose summary that deferred byte-level string/number escaping details to Python's json.dumps with 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+D800 to U+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):
    • Object keys sorted lexicographically by Unicode code point, recursively (UTF-16 surrogate pairs are not used for key comparison; see §0).
    • Separators "," and ":", no whitespace.
    • String escaping: " (U+0022), \ (U+005C), and the five control characters U+0008, U+0009, U+000A, U+000C, and U+000D use the two-character escapes \", \\, \b, \t, \n, \f, and \r; / (U+002F) is not escaped; every other character in U+0020 to U+007E is written as itself, including <, > and &; all other control characters (U+0000 to U+001F) and all BMP characters in U+007F to U+D7FF and U+E000 to U+FFFF (including U+007F, which is escaped as \u007f) use lowercase four-hex-digit \uXXXX escapes; supplementary-plane characters (U+10000 to U+10FFFF) use lowercase UTF-16 surrogate pairs (a high surrogate in U+D800 to U+DBFF followed by a low surrogate in U+DC00 to U+DFFF, e.g. \ud8xx\udcxx); output is encoded as ASCII bytes.
    • Integer zero is written 0 (never -0).
    • No trailing newline.
    • Unpaired UTF-16 surrogates (U+D800 to U+DFFF) in strings or object keys are placed outside the profile and rejected by both agentrust_trace.sign.anchor_bytes (UnanchorableValue) and trace_tests.inclusion.canonical_claim_bytes (InclusionError).
  2. Implementation findings from the Go verifier (#444): 26 items #448 Item 3 (examples/verifier-compatibility/README.md): Updates the opening note, introductory paragraphs, and failure discussion so they accurately state that Obligations 2 and 3 of verification.md: format-versioning and verifier-compatibility obligations (evidence outlives verifiers) #116 (nonempty accepted_profiles declaration and reporting both profile and accepted_profiles on the verification result) 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 (emitter version declarations) and 4 (downgrade disclosure) remain deferred.
  3. Implementation findings from the Go verifier (#444): 26 items #448 Item 16 (docs/crosswalks/acta-decision-receipts.md): Ensures the introduction, field-mapping line, and receipt.evidence_hash row cite 3.3.2 (where evidence_hash and the external-evidence verification rule are defined in spec/trace-v0.2.md) and 3.3.3 (the informative action-receipt profile that builds on them).
  4. Regression tests (tests/test_registry_anchor_canonical_rules.py): Pins all five serialization rules (including <, >, & and ASCII DEL U+007F vs JCS) and unpaired-surrogate rejection across both agentrust_trace.sign.anchor_bytes and trace_tests.inclusion.canonical_claim_bytes (including key and value escaping) as well as the documentation fixes in spec/registry-anchor-v1.md, examples/verifier-compatibility/README.md, and docs/crosswalks/acta-decision-receipts.md.

Cold-reader review:

  1. The cold reader noted that writing only \ud8xx\udcxx for supplementary-plane surrogate pairs could be read literally as restricting high surrogates to U+D800–U+D8FF and low surrogates to U+DC00–U+DCFF, excluding code points such as U+10FFFF (\udbff\udfff). Updated spec/registry-anchor-v1.md §1 and tests/test_registry_anchor_canonical_rules.py to 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).
  2. The cold reader noted that the What failure is and is not section of examples/verifier-compatibility/README.md still 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 and spec/trace-v0.2.md §3.3 consistently with the updated opening section.
  3. The cold reader noted that string-escaping tests in tests/test_registry_anchor_canonical_rules.py exercised string values but not object member names for control/short/hex escapes. Added test_rule_3_escaping_applies_to_object_keys_as_well_as_values.
  4. Both the cold reader and @imran-siddique pointed out that U+007F to U+FFFF included unpaired UTF-16 surrogates (U+D800 to U+DFFF) and that anchor_bytes and canonical_claim_bytes accepted and escaped them (\ud800), whereas UTF-8 peers cannot represent unpaired surrogates. Updated spec/registry-anchor-v1.md §1 to scope BMP escapes to U+007F to U+D7FF and U+E000 to U+FFFF, put unpaired UTF-16 surrogates (U+D800 to U+DFFF) outside the profile, and rejected them in both anchor_bytes and canonical_claim_bytes with test_unpaired_surrogates_in_values_and_keys_are_rejected.

Closes #536
Refs #448

Contributor: Raghuvar Durga (@drk-ram)

Type of change

  • Editorial (typo, link fix, clarification: no normative effect)
  • Non-breaking spec change (new optional field, new platform profile, informative addition)
  • Breaking spec change (requires 14-day comment period and Project Lead sign-off)
  • Schema change
  • Example addition

Spec section

Companion specification spec/registry-anchor-v1.md §0 and §1 (Canonical claim bytes), examples/verifier-compatibility/README.md (referencing spec/trace-v0.2.md §3.3), and docs/crosswalks/acta-decision-receipts.md (referencing spec/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_rejected fails with DID NOT RAISE UnanchorableValue (for anchor_bytes) and DID NOT RAISE InclusionError (for canonical_claim_bytes) when the unpaired-surrogate rejection in src/agentrust_trace/sign.py and conformance/src/trace_tests/inclusion.py is 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 — passed
  • python 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

  • DCO sign-off on all commits (git commit -s)
  • CHANGELOG.md updated (for any normative change)
  • Breaking changes marked with <!-- CHANGED: #NNN: description --> in spec text
  • Backward compatibility statement included (for breaking changes)

@drk-ram
drk-ram requested review from a team, lywinged and rajnisht7 as code owners October 15, 2026 22:01

@imran-siddique imran-siddique 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.

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.

Comment thread spec/registry-anchor-v1.md Outdated
```

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.

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.

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}'

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.

-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.

@drk-ram

drk-ram commented Oct 16, 2026

Copy link
Copy Markdown
Contributor Author

Addressed in 563a8c3:

  • Scoped the BMP \uXXXX bullet in spec/registry-anchor-v1.md §1 to U+007F to U+D7FF and U+E000 to U+FFFF, and placed unpaired UTF-16 surrogates (U+D800 to U+DFFF) in string values or object keys outside the profile alongside non-integer numbers and unsafe integers.
  • Updated src/agentrust_trace/sign.py (_reject_unanchorable) and conformance/src/trace_tests/inclusion.py (_outside_profile) to reject unpaired UTF-16 surrogates in both string values and object keys (UnanchorableValue / InclusionError).
  • Added est_unpaired_surrogates_in_values_and_keys_are_rejected in ests/test_registry_anchor_canonical_rules.py (7 fail on behaviour under ools/review_bar.py).

@dinakarjs dinakarjs 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.

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.

@imran-siddique

Copy link
Copy Markdown
Member

@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. test_rule_4_integer_zero_is_written_without_minus_sign cannot fail, because -0 is already the int 0 before it reaches either serializer. Drop it, or keep the §1 bullet and say in the test that it guards the spec text only. Approval goes on that head.

@drk-ram

drk-ram commented Oct 18, 2026

Copy link
Copy Markdown
Contributor Author

@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
noah-ing previously approved these changes Oct 18, 2026

@noah-ing noah-ing 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.

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.

Comment thread spec/registry-anchor-v1.md Resolved

@dinakarjs dinakarjs 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.

Concrete companion for the two bounded documentation notes in Noah's current-head review, based on exact head 61e9b61:

dinakarjs@8377a35

  1. 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.

  2. 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.

@drk-ram

drk-ram commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

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
egistry-anchor-v1\ §1 canonical JSON rules and unpaired UTF-16 surrogate exclusion/refusal.

@drk-ram
drk-ram force-pushed the drk-ram-docs-close-go-verifier-findings-3-16-19 branch from 61e9b61 to 907f9ee Compare October 19, 2026 17:13
imran-siddique
imran-siddique previously approved these changes Oct 19, 2026

@imran-siddique imran-siddique 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.

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

Copy link
Copy Markdown
Member

@drk-ram #565 merged after my approval, and the only conflict is CHANGELOG.md: please rebase onto main keeping both Unreleased entries. Nothing else needs to change, and this merges once it is green.

@drk-ram

drk-ram commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

@imran-siddique Rebased onto \origin/main\ (\4c2883d) in \�51dc86, keeping both [Unreleased]\ entries in \CHANGELOG.md.

@drk-ram
drk-ram force-pushed the drk-ram-docs-close-go-verifier-findings-3-16-19 branch from 907f9ee to b51dc86 Compare October 20, 2026 15:31
noah-ing
noah-ing previously approved these changes Oct 20, 2026

@noah-ing noah-ing 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.

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 dinakarjs 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.

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.

imran-siddique
imran-siddique previously approved these changes Oct 21, 2026

@lywinged lywinged left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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. §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/json escapes <, > and & unless SetEscapeHTML(false) is called, by its own doc comment on Marshal and as run here on Go 1.24.7, where json.Marshal writes the string a<b>&c as a\u003cb\u003e\u0026c. An implementation of the five bullets as written over json.Marshal therefore 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 in test_rule_3_short_escapes_and_unescaped_slash, the test that lists the escapes. test_ascii_del_distinguishes_signing_and_anchor_bytes already holds every character below DEL equal to rfc8785.dumps through 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.loads accepts 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 record there is a signed record.

  2. The description, whose Type of change is what CONTRIBUTING's review timeline is read against. Item 3 still says the two stale 3.3.2 citations change to 3.3.3, and the 10-21 comment on #536 reports main that way after #554. The head cites 3.3.2 again, on purpose and for a different reason: v0.2's §3.3.2 is where evidence_hash and 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 a CHANGED marker 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.

@drk-ram

drk-ram commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

Thank you for the thorough review, @lywinged! Addressed on 89529c1 (rebased onto origin/main at a8e3559):

  1. spec/registry-anchor-v1.md §1 and tests/test_registry_anchor_canonical_rules.py:
    • Added every other character in `U+0020` to `U+007E` is written as itself, including `<`, `>` and `&`; right after `/` (`U+002F`) is not escaped; in §1's string-escaping bullet, pinned it in test_registry_anchor_spec_documents_all_five_canonical_rules, and added ("<>&", b"<>&") to test_rule_3_short_escapes_and_unescaped_slash.
    • Changed a schema-valid record cannot reach this to a signed record cannot reach this in §1's surrogate exclusion paragraph.
  2. PR description & rebase:
    • Rebased onto origin/main (a8e3559), keeping all [Unreleased] entries in CHANGELOG.md.
    • Updated the PR description to quote the updated §1 string-escaping bullet, state accurately that docs/crosswalks/acta-decision-receipts.md cites 3.3.2 (where evidence_hash and the external-evidence verification rule are defined) and 3.3.3 (the informative profile that builds on them), leave only Non-breaking spec change ticked under Type of change, check DCO sign-off and CHANGELOG.md updated (leaving the two breaking-only boxes unchecked) under Checklist, and update the measured test counts on 89529c1 (354 passed in the targeted root suite / 43 passed in test_registry_anchor_canonical_rules.py / 958 passed, 2 skipped, 5 xpassed in conformance/tests / 9 fail on behaviour, 0 fail on a missing name or file in review_bar.py / 16 source files in mypy).

@drk-ram
drk-ram dismissed stale reviews from imran-siddique and noah-ing via 89529c1 October 23, 2026 03:40
@drk-ram
drk-ram force-pushed the drk-ram-docs-close-go-verifier-findings-3-16-19 branch from b51dc86 to 89529c1 Compare October 23, 2026 03:40
lywinged
lywinged previously approved these changes Oct 23, 2026

@lywinged lywinged left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

lywinged added a commit to drk-ram/trace-spec that referenced this pull request Oct 23, 2026
…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>
lywinged
lywinged previously approved these changes Oct 23, 2026

@noah-ing noah-ing 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.

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 rajnisht7 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.

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 noah-ing 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.

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.

  1. spec/registry-anchor-v1.md §1 says the Python expression using raw json.dumps is exactly the canonical-claim operation. Given a string containing an unpaired U+D800 surrogate, that expression emits escaped JSON bytes, while both agentrust_trace.sign.anchor_bytes and trace_tests.inclusion.canonical_claim_bytes reject 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.

  2. examples/verifier-compatibility/README.md correctly calls Obligations 2 and 3 of #116 normative, consistent with the MUSTs in spec/trace-v0.2.md §3.3. But gen_vectors.py, all eight generated fixtures, and the VerificationResult documentation 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 dinakarjs 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.

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.

@drk-ram

drk-ram commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

Thank you @rajnisht7, @noah-ing, and @dinakarjs! Addressed both points in 2b98d83:

  1. spec/registry-anchor-v1.md §1 (json.dumps qualification): Qualified the Python snippet to state explicitly that it applies once the profile's input exclusions (object root, allowed JSON types, safe-integer bounds, and no unpaired UTF-16 surrogates) have been checked, and pinned that wording in tests/test_registry_anchor_canonical_rules.py.
  2. Verifier-compatibility status alignment (Obligations 2 and 3 normative in spec/trace-v0.2.md §3.3; Obligations 1 and 4 deferred): Updated examples/verifier-compatibility/gen_vectors.py, regenerated all 8 fixtures in examples/verifier-compatibility/*.json, aligned the VerificationResult docstrings in src/agentrust_trace/revocation.py, and updated tests/test_verifier_compatibility_fixtures.py.

@drk-ram
drk-ram force-pushed the drk-ram-docs-close-go-verifier-findings-3-16-19 branch from 9fc330a to 2b98d83 Compare October 29, 2026 14:51
…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>
@drk-ram
drk-ram force-pushed the drk-ram-docs-close-go-verifier-findings-3-16-19 branch from 2b98d83 to 608c2d6 Compare October 29, 2026 17:13

@dinakarjs dinakarjs 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.

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 noah-ing 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.

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 lywinged left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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=0 runs, the PYTHONOPTIMIZE=2 command and the two --help calls pass. 3.13, which Conformance CI also runs for the conformance package this change touches, is not installed here.
  • Running gen_vectors.py here reproduces the eight fixtures with no diff.
  • anchor_bytes and canonical_claim_bytes both refuse an unpaired surrogate in a value and in a key, write DEL as \u007f and a supplementary-plane character as a lowercase surrogate pair; on main both wrote the unpaired surrogate as \ud800. sign_record refuses the same input with CanonicalizationError from the RFC 8785 step.
  • Section 3.3.2 of spec/trace-v0.2.md is where evidence_hash is named, section 3.3 carries the accepted_profiles MUSTs the README now cites, and docs/verification.md has the "Step 4: Check the EAT profile" heading.
  • tools/review_bar.py reports 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 copies tests/ and not examples/.

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.py that count section 1's exclusions still say two.
  • anchor_bytes does not refuse a non-object root, which section 1 lists among the exclusions and canonical_claim_bytes refuses: 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 -0 is 0 before 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_bytes and canonical_claim_bytes now 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

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs/spec: close Go verifier findings on Anchor Format §1 escaping rules, verifier-compatibility README, and Acta crosswalk (#448 items 3, 16, 19)

6 participants