fix(content-marking): establish presence of the required binding fields - #335
Merged
imran-siddique merged 1 commit intoSep 12, 2026
Merged
Conversation
verify_assertion() compared the duplicated binding fields with
`record.get("subject") != data.get("subject")` and the same line for
eat_profile. A comparison establishes that two reads agree, not that either
exists, and two absences compare equal. A peer-produced assertion omitting
data.subject, paired with a hash-matching record that also omitted subject,
agreed by mutual absence and the function returned the parsed record as a
successful binding.
spec/content-marking-v1.md section 2 marks both fields required and section 6
says a conforming consumer checks both against the fetched record. This layer
performs only the binding check and returns before any Trust Record signature
or schema verification, and a caller is allowed to run it on its own, so it has
to establish its own required shape rather than relying on a later verifier.
Presence is now checked on both sides. An assertion missing a required field is
ContentMarkingError, because a malformed assertion is the caller's own input and
RecordMismatch would point the reader at whoever serves the URL, which is the
reasoning test_an_int_no_longer_reports_a_record_mismatch already pins. A record
missing one is RecordMismatch, because it matched the declared hash and that URL
really is serving something that is not a conformant record. Two present values
that disagree are unchanged.
Regression coverage carries all six cases from the reproduction, including the
two single-side controls that make the hole precisely mutual absence, plus a
complete-pair control. The pair helper recomputes the hash, without which every
case would fail at the digest check and pass for the wrong reason.
Reported by @altrudev in #326, reproduced independently by @lywinged with the
six-case matrix and the check against #325's head.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X27MBo6tGVw1UmmchuGFnW
imran-siddique
deleted the
fix/content-marking-required-binding-presence
branch
September 12, 2026 04:41
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #326.
verify_assertion()compared the duplicated binding fields with.get()equality. A comparison establishes that two reads agree, not that either exists, and two absences compare equal.Reproduced at
0aefa1f, running @lywinged's six cases withrecord.hashrecomputed over the modified bytes each time so every pair stays self-consistent:The two single-side rows are what make the hole precisely mutual absence rather than something wider.
Why this layer has to establish its own shape
spec/content-marking-v1.mdsection 2 marks both fields required and section 6 says a conforming consumer checks both against the fetched record.verify_assertion()performs only the binding check and returns the parsed record before any Trust Record signature or schema verification, and a caller is allowed to run it on its own. Relying on a later verifier to reject the record is not available to it.Which exception, and why it differs by side
ContentMarkingError. A malformed assertion is the caller's own input.RecordMismatchmeans "the URL is serving something else" and would point the reader at whoever operates that server, which is the reasoningtest_an_int_no_longer_reports_a_record_mismatchalready pins in this file.RecordMismatch. It matched the declared hash, so that URL really is serving something that is not a conformant record.The only behaviour change for input that was already refused is the class on an assertion-side omission, from
RecordMismatchto itsContentMarkingErrorparent. No caller catching the documented contract loses anything, and one catchingRecordMismatchspecifically was being told the wrong party was at fault.Verified in both directions
tests/test_content_marking.py43 passed, repository 1437 passed.origin/main'scontent_marking.py: 7 of the new cases fail.test_generators_reproduce_fixtures.pyandtest_safe_integer_range.pyare pre-existing Windows path-separator artifacts.No overlap with #325, which touches
build_assertiononly.Reported by @altrudev. Reproduced independently by @lywinged, whose six-case matrix and check against #325's head is what this PR's coverage is built from.
🤖 Generated with Claude Code
https://claude.ai/code/session_01X27MBo6tGVw1UmmchuGFnW