test(conformance): portable vectors for the RFC 8785 requirement - #126
Merged
imran-siddique merged 1 commit intoAug 8, 2026
Merged
Conversation
Section 3.2.2 requires an RFC 8785-conformant canonicalizer and names
json.dumps(sort_keys=True) as insufficient. tests/test_sign.py already
covers that at unit level, with four literal-byte known-answer tests over
_canonical_bytes; a swap to json.dumps fails them. These vectors are not a
replacement for those and do not claim a gap in them.
They add two things the known-answer tests cannot do.
Portability. A known-answer test over a private function runs only from
Python, in this package. The roadmap targets Go, Rust and TypeScript
verification libraries for v1.0, and none of them can run test_sign.py.
These are signed Trust Records: any implementation runs them against its
own verifier. Every other record in the repository is ASCII-only with
schema-fixed keys, where all serializers agree byte-for-byte, so no
existing record's acceptance depends on canonicalizing correctly.
Key ordering. test_jcs_distinguishes_unicode_key_order_from_json_dumps
compares {"z": 1, "\U0001f600": 2}, which serializes in the same order
under RFC 8785's UTF-16 code-unit sort and under Python's code-point sort
— the docstring says so — so it detects divergence through ensure_ascii
escaping rather than through ordering. A canonicalizer that sorts by code
point and emits raw UTF-8 passes it. Vector 03 uses two keys sharing a
prefix and differing only after it, where the two orderings disagree; it
is the first object here that separates them.
The three vectors form a ladder: default separators fail on any record,
ensure_ascii fails on 01 and 02, and json.dumps with compact separators
and ensure_ascii=False survives both and fails only 03. Each declares
diverges_under and the test recomputes it rather than trusting it, and
asserts the set still catches every rung.
No number-formatting vector: no schema field is typed number, so RFC
8785's IEEE 754 serialization is unreachable inside a schema-valid record.
A test pins that and fails the day a numeric field is added.
gen_boundary_vectors.py regenerates the set byte-for-byte. Only public
JWKs appear in the files. 114 tests pass (105 before, plus 9).
Signed-off-by: lywinged <48041247+lywinged@users.noreply.github.com>
Contributor
|
❔ Contributor Check: UNKNOWN
Automated check by AgenTrust Contributor Check. |
2 tasks
imran-siddique
approved these changes
Aug 8, 2026
imran-siddique
left a comment
Member
There was a problem hiding this comment.
Good reasoning, and the honest note that test_sign.py already covers the requirement from Python is why this is worth having: the gap is portability, not coverage. Signed fixtures a Go, Rust or TypeScript verifier can run against its own canonicalizer are exactly what the v1.0 roadmap needs, and no existing ASCII-only example can distinguish a conformant canonicalizer from ad-hoc sorting.
4 tasks
lywinged
added a commit
to lywinged/trace-spec
that referenced
this pull request
Aug 10, 2026
Upstream merged agentrust-io#148, the last large piece this fork was carrying, which the maintainer rebased and opened himself under this fork's authorship. With agentrust-io#122, agentrust-io#125, agentrust-io#126, agentrust-io#136 and agentrust-io#137 already merged, most of the thirty-three commits here described work that now lives upstream with better provenance than this fork can give it: a PR number and a maintainer's review. `git rebase upstream/main` was tried first and abandoned. It stopped on the oldest commit in the set, a schema-and-version alignment whose content upstream has since taken, superseded and released three times over. Replaying thirty-three commits against fifteen of upstream's resolves early commits into shapes that no longer mean anything. So: main reset to upstream/main, fork-only material re-applied. The old history is tagged `archive/pre-576507b` and pushed rather than discarded. What is held here, and why it is held: - agentrust-io#117 gap disclosure: design note, two normative drafts, 18 vectors, generator - agentrust-io#116 verifier compatibility: 8 vectors, generator, normative draft - the normative crosswalk, mapping every RFC 2119 statement to whom it binds - `docs/conformance-method.md`, `coverage-report/` (historical), DECISIONS.md - the independent signature path and the package-consistency test Each is an unaccepted proposal or a method write-up, not unfinished work. Four files needed a real merge, and not in the same direction. `models.py` and `__init__.py` are upstream's plus this fork's two profile constants, because upstream had moved on with `origin` (agentrust-io#135) and the `declared` enforcement mode (agentrust-io#143) and a wholesale copy would have dropped both. `sign.py` and `test_sign.py` are this fork's `accepted_profiles` version, which supersedes the minimal cutover check upstream took from agentrust-io#125 -- a supersession CLAUDE.md predicted when agentrust-io#125 was offered. All four merged cleanly three-way against a817621, the last commit the two histories agree on. The crosswalk guard earned its place in the same run: upstream added two normative statements this fork had never seen, and `test_normative_crosswalk.py` failed until both had rows. A source-derived inventory noticing its subject moved is the property that document exists to have. 432 passed, 1 skipped. ruff and mypy clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: lywinged <48041247+lywinged@users.noreply.github.com>
2 tasks
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.
What this is not
§3.2.2 requires an RFC 8785-conformant canonicalizer and names
json.dumps(sort_keys=True)as insufficient.tests/test_sign.pyalready covers that, with four literal-byte known-answer tests over_canonical_bytes. I swapped the canonicalizer for each ad-hoc form in turn to check, and every one of them fails those tests. This PR is not reporting a hole in them and does not replace them.I mention it because my first read of this was that the requirement was untested, and that was wrong — I had not read
test_sign.pybefore concluding it.What it adds
1. Portability. A known-answer test over a private function runs only from Python, in this package.
ROADMAP.mdtargets Go, Rust and TypeScript verification libraries for v1.0, and none of them can runtest_sign.py. These three fixtures are signed Trust Records: another implementation runs them against its own verifier and finds out whether its canonicalizer agrees.That matters because no existing record in the repository can tell. Every example is ASCII-only with schema-fixed keys, and on such records every serializer produces identical bytes — so an implementation built on ad-hoc sorting verifies all of them correctly.
2. One vector separates key ordering, which nothing here currently does.
test_jcs_distinguishes_unicode_key_order_from_json_dumpscompares{"z": 1, "\U0001f600": 2}. Under RFC 8785's UTF-16 code-unit sort and under Python's code-point sort, that object serializes in the same order — the test's own docstring notes it ("order matches here") — so what it actually detects is theensure_asciiescaping, which it hardcodes on the comparison side:Vector
03uses two keys sharing azkprefix and differing only after it, where the orderings genuinely disagree:cnf.jwkis the one schema object open to additional members, which RFC 7517 permits, so this is expressible in a schema-valid record.The ladder
json.dumps(o, sort_keys=True)… separators=(",", ":")01,02,03… separators=(",", ":"), ensure_ascii=False03onlyEach fixture declares
diverges_under; the test recomputes it rather than trusting it, and separately asserts the set as a whole still kills every rung — so a vector that quietly stopped discriminating fails rather than passing while documenting a distinction it no longer makes.Deliberately no number-formatting vector
RFC 8785's IEEE 754 serialization is the other divergence §3.2.2 warns about, and it is unreachable inside a schema-valid record: no field in
schema/trace-claim.jsonis typednumber, and integers below 2^53 serialize identically everywhere.test_number_divergence_is_still_unreachablepins that and fails the day a numeric field is added, at which point the right response is a vector here rather than an edit to the test.Verification
ruff check src tests examplesclean; verified on Python 3.11 and 3.14gen_boundary_vectors.pyregenerates the set byte-for-byte; only public JWKs appear in the filesverify_record, so none can pass or fail for an unrelated reason