Repository navigation
spec(anchor)!: a Level 2 anchor verdict requires the registry entry - #533
imran-siddique wants to merge 4 commits into
Conversation
TR-ANC-002 judged the inclusion proof against a merkle_root and leaf_count that came from the receipt, so a pass tied the record to no registry entry. Registry Anchor Format section 6 now says a Level 2 verdict rests on the entry itself (the third input of section 5.1): the receipt's root and count must equal the entry's or the anchor is refused, and with no entry Level 2 is not earned. The suite takes the entry with --entry (run() and tr_anc.check() take entry=). A missing entry fails TR-ANC-002 the same way a missing receipt does. Nothing yet identifies an entry from the transparency URI, so the pass message names the entry and says that link was not checked; it stays open as finding 21 of #448. Breaking, proposed: this redefines what Level 2 requires, so it needs the 30-day comment period and Project Lead sign-off under GOVERNANCE.md. Refs #464 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Imran Siddique <45405841+imran-siddique@users.noreply.github.com>
dinakarjs
left a comment
There was a problem hiding this comment.
Independent technical review of 95e160a. Current-head CI and Conformance CI succeeded. Fresh hash-locked Python 3.12.14 dependencies: reference suite 4,033 passed / 48 skipped; conformance 799 passed / 5 xpassed. The new entry-binding module passes all 18 cases.
I separately built RFC 6962 trees by recursive largest-power-of-two splitting, rather than the test helper's level-by-level promotion, and checked every leaf position for sizes 1 through 33 (561 proofs), using records with non-ASCII content and an included signature field. Each valid proof passes; changing only the entry root, changing only its count, omitting the entry, or changing the claim signature each refuses (2,244 refusal twins). Disabling the new root comparison causes 1 failure / 17 passes; disabling the count comparison 2 failures / 16 passes; synthesizing a missing entry from receipt fields instead of refusing causes 4 failures / 14 passes. Symbols and valid controls were retained; temporary mutations restored.
The bounded guarantee is consistent with the new PASS diagnostic: receipt root/count match the supplied entry, and inclusion replays to that root. It does not establish that this is the entry named by transparency or authenticate the entry's origin. Controls with changed producer/batch_id but identical root/count still pass, as expected under the stated limitation. This does not close #448 finding 21.
One nonblocking shape-validation observation: parse_entry accepts ts='not-a-timestamp' as long as it is nonempty, while section 4 describes ISO-8601 UTC with a Z suffix. Either validate that field in a follow-up or make the parser's narrower shape-validation ceiling explicit; a passing inclusion result should not be read as timestamp validation.
No blocking defect found in the implemented root/count/entry-presence guarantees. This is technical evidence, not approval to bypass the breaking-change comment period, Project Lead decision, or remaining URI-binding work.
|
Non-blocking, for the comment period on Failing behaviour. For Smallest case. Registry-side: a RuntimeClaim anchored with the registry's own Expected. Registry Anchor Format §1: "The unit of anchoring is the complete signed claim object, signature included … if either the claim body or its signature changes, the anchor no longer matches." Consequence. For this format a Level 2 pass fixes the bytes of the inner trace, not the identity of the signer's envelope or the scope of the gateway facts bound to it. Two differently signed envelopes satisfy the same anchor.
The registry anchors the other object. Reproduced locally with synthetic Ed25519-signed RuntimeClaims; registry at Registry route — leaf and tree from the registry's own
Suite route — this PR's
In the two passing worlds, differently signed envelopes use the same inner trace, proof and supplied entry, so the checks do not establish inclusion of either complete signed RuntimeClaim. Question. Is the intended Level 2 anchor subject for This route is not reachable from cMCP's producer today: This concerns which bytes the inclusion proof covers, not a signature bypass: changing gateway fields needs a re-signature with the key in |
|
Independent follow-up to Loek's anchor-subject report at 95e160a. I reproduced the distinction locally on Python 3.12.14 with fresh synthetic Ed25519 keys, a single-leaf proof computed from canonical_claim_bytes, and the existing loader/signature/anchor modules:
Direct verify_inclusion over the complete original signed object passes; changing and re-signing the envelope with that original whole-object proof fails. This independently confirms the runner boundary: extract_trace removes the envelope before TR-ANC, while TR-SIG receives the complete record. These are module-level synthetic checks, not schema-complete or aggregate Level 2 acceptance, and I did not rerun the registry ingest pipeline. My recommendation is to retain registry-anchor-v1 §1's complete signed claim subject and pass an explicit anchor subject alongside the normalized trace into TR-ANC. Keep the URI/receipt shape checks on the trace and include a full-envelope positive twin plus re-signed-gateway and signature-change refusal twins. A nested-only interpretation would require explicit normative wording and registry alignment. @imran-siddique @lywinged please record the intended subject before implementation; my earlier receipt-to-entry review does not resolve or approve this broader semantic choice. Preserve the 30-day comment period and keep #448 item 6 and URI-to-entry binding item 21 distinct. No normative/runtime change or merge requested here. |
…ate the entry ts For cmcp-runtime the runner handed TR-ANC the nested trace while Anchor Format section 1 and the reference registry anchor the whole envelope, so a registry-valid anchor failed and a re-signed envelope with changed gateway fields still passed. TR-ANC now takes the complete record as its subject. parse_entry also rejects a ts that is not ISO-8601 UTC ending in Z. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Imran Siddique <45405841+imran-siddique@users.noreply.github.com>
|
@solloek369-arch @dinakarjs thanks, reproduced here too. Recording the subject: it is the complete signed claim, as Anchor Format §1 already says, and the next commit makes the suite follow it. For cmcp-runtime the runner now passes the whole envelope to TR-ANC as |
dinakarjs
left a comment
There was a problem hiding this comment.
Follow-up technical review at exact head fe5d01e closes the two findings from the previous discussion: the runner passes the complete signed record as the anchor subject, and parse_entry validates the UTC-Z timestamp shape/calendar date.
Independent Python 3.12, absolute PYTHONPATH to this checkout: full conformance suite 812 passed, 5 xpassed (XPASS is reported separately, not counted as additional passing requirements). Current-head hosted technical checks succeed. Separate synthetic-key probe used independently constructed sorted-key JSON preimages and a SHA-256 single-leaf proof: original whole-envelope anchor TR-SIG/TR-ANC pass/pass; nested-trace-only proof pass/fail; changed gateway session with a valid re-signature against the original full-envelope proof pass/fail; changed signature fail/fail. These are module-level controls, not aggregate Level 2 acceptance or registry ingest validation.
Regression sensitivity on temporary edits: removing subject=record from the runner fails the whole-envelope positive and nested-only refusal tests (2 failures). Removing timestamp validation fails all 7 malformed-date cases while both valid UTC timestamp cases pass. Sources restored, clean worktree; diff whitespace check passes.
No remaining blocking implementation finding in this bounded follow-up. The 30-day breaking-change comment period and Project Lead sign-off still apply; this is not authorization to bypass them. URI-to-entry binding (#448 finding 21), entry-origin authentication, and the separate signature-preimage item 6 remain outside this correction.
Registry Anchor Format section 5 defines the inclusion proof as leaf_index and audit_path, and the section 5.1 algorithm takes merkle_root and leaf_count from the registry entry. TR-ANC-002 still required both on the receipt, so a proof-only receipt with a matching entry was refused as malformed. TR-ANC-002 now replays the proof against the entry's root and count. A receipt that carries its own copies must still carry the entry's (section 6). parse_receipt keeps its contract for callers that replay a self-contained receipt without an entry. Signed-off-by: Sankalp Gilda <23521054+astrogilda@users.noreply.github.com>
A receipt may leave leaf_count out and take the entry's, but one that carries the field must carry an integer. An explicit null was read as absent and skipped the comparison with the entry. Signed-off-by: Sankalp Gilda <23521054+astrogilda@users.noreply.github.com>
dinakarjs
left a comment
There was a problem hiding this comment.
Re-review at 6a9d803: the entry-authoritative root/count correction behaves as intended, but the receipt fuzz oracle needs updating before the technical checks are green.
Blocking harness defect: conformance/.clusterfuzzlite/fuzz_receipt.py:124 calls parse_receipt(receipt) after TR-ANC-002 passes. A legitimate proof-only receipt can now pass using the supplied entry, whereas parse_receipt intentionally still requires self-contained root/count fields. Deleting leaf_count or merkle_root from the genuine receipt therefore raises InclusionError in this post-PASS assertion. Hosted ClusterFuzzLite run 38987830178 / job 117263304673 reports precisely the missing-leaf_count exception. This is a stale harness assumption, not evidence of an inclusion-verification bypass.
Please retain the earlier standalone parse_receipt exception-safety probe, but make the post-PASS oracle use parse_proof(receipt) for index/path and parse_entry(_ENTRY) for the authoritative root/count; independently replay verify_inclusion(claim, index, path, entry_count, entry_root), retaining the expected-claim and genuine-root assertions. Do not merely suppress the exception or skip proof-only inputs. Add deterministic harness coverage for omitted leaf_count, omitted merkle_root, and both omitted, then rerun ClusterFuzzLite.
Validation on this exact checkout, Python 3.12: 49 targeted anchor tests passed; full conformance suite 818 passed, 5 xpassed (reported separately). A deterministic probe loaded the actual fuzz fixture with only Atheris instrumentation stubbed: all three omission variants pass TR-ANC-002, raise in the old self-contained parser, and pass an entry-based independent replay. Wrong-claim, wrong-entry-root and wrong-entry-count controls remain refused. This was not a fresh Atheris campaign or a replay of the minimized byte input.
The 30-day breaking-change comment period and Project Lead sign-off remain required. URI-to-entry binding (#448 finding 21) and entry-origin authentication remain unverified; this review does not establish aggregate Level 2 acceptance.
|
Concrete companion for the fuzz-oracle review finding: #559, targeting this PR's branch at 6a9d803. Head bc3b465 changes only the harness and adds five deterministic callback tests. Full local conformance: 823 passed, 5 xpassed; Ruff passes. Restoring the old post-PASS oracle in memory makes all three optional-field omission regressions and the independent-replay refusal test fail, while the self-contained control passes. The standalone parser exception-safety probe is retained. Please incorporate/review the companion and rerun hosted ClusterFuzzLite; a fresh fuzz campaign is still required before the red check is resolved. Governance comment period and Project Lead sign-off remain unchanged. |
Refs #464. Breaking, proposed: this changes what Level 2 requires, so it waits out the 30-day comment period under GOVERNANCE.md.
Ruling of 2026-10-15: TR-ANC-002 judged the proof against a
merkle_rootandleaf_countthe receipt brought with it, so a pass tied the record to no entry. Registry Anchor Format §6 now requires the entry itself. The receipt's root and count must equal the entry's, and with no entry Level 2 is not earned. The suite takes it with--entry.Nothing identifies an entry from the
transparencyURI yet. The pass message says that link was not checked, and #448 finding 21 tracks it. That supersedes the 09-23 line on trace-tests#92 until the link exists.The worlds from trace-tests#92, in
tests/test_tr_anc_entry_binding.py:Removing each new comparison breaks at least one test. Conformance suite on Windows: 798 passed, plus one
test_manifest_ref_draft_vectorsfailure that main shows too.Overlap: #519 edits the same three docs pages.
🤖 Generated with Claude Code