Repository navigation
Four surfaces state the rules and nothing says which wins: schema, reference model, normative spec and docs disagree #247
Description
TRACE states its rules across four surfaces and nothing says which one wins when they disagree. Four reports in three days found four separate instances, all from producers who did the reasonable thing and validated against one of them.
The four surfaces
| Surface | Files |
|---|---|
| Normative spec | spec/trace-v0.2.md |
| JSON Schema | schema/trace-claim.json and its byte-identical copy src/agentrust_trace/schema/trace-v0.2.json |
| Reference model | src/agentrust_trace/models.py |
| Prose docs | docs/*.md, plus docs/rfcs/* which disclaim being normative |
The four instances
- schema: the software-only description states a narrower rule than §3.1.1 mandates #232 (PR #234) - the schema's
runtime.platformdescription states a narrower rule than §3.1.1's MUST. Schema description under-states the spec. - trust-levels: the all-zero measurement convention is stated with two different scopes #240 (PR #242) -
docs/trust-levels.mdis stale on the all-zero measurement convention. Docs under-state the spec. - schema: the subject pattern accepts identities the reference model refuses #244 - the schema's
subjectpattern is a prefix test wheremodels.py:343requires a full shape, so a record can be schema-conformant and refused bymodel_validate. Schema is looser than the model. - test: independent-producer evidence for the parent-record canonicalization boundary #245 -
parent_record_hashappears zero times in the normative spec, and its preimage is stated only in a draft that says "Binds nothing". No normative surface covers it at all.
Reported by @chernistry (four of them) and independently in the same window by @mohdibrahimaiml in #241, where the published 0.9.0 schema rejects §3.1.2 references on additionalProperties. Five, if that one is counted, and it is the same shape.
Each has been answered on its own merits. This issue is about the thing underneath them.
Why answering them individually is not enough
Every instance was found by a producer validating their records against one surface and discovering that a different surface disagreed. The schema check passing is not evidence the model will accept the record. The model accepting is not evidence the record is spec-conformant. A reader of the docs gets a fourth answer.
Two consequences worth stating plainly:
- Conformance is currently unfalsifiable. "Is this record TRACE-conformant" has no single answer, so a producer cannot be told they are wrong, and a verifier cannot be told it is being too strict.
- The failure is asymmetric and silent. The schema is the surface producers vendor and check against, and in three of the four instances it was the loose one. So the checks people actually run are the ones least likely to catch the divergence.
What this issue is for
Deciding the precedence rule and writing it somewhere a producer will find it. Not deciding the four individual answers, which are already ruled.
The obvious candidate: the normative spec wins; the schema and the reference model are derived artifacts and any divergence from the spec is a bug in the derived artifact. That is what the four rulings did in practice, and it is what §3.1.1 versus the schema description implies. But it needs to be true operationally, not just declared:
- The schema and the model currently drift because nothing checks them against each other. A test that generates the boundary cases from the spec, or at minimum a parity test between
schema/trace-claim.jsonandmodels.py, would have caught schema: the subject pattern accepts identities the reference model refuses #244 before a producer did. trace-spec#236 addedtests/test_the_schema_and_the_models_agree.pyin that direction; whether it covers thesubjectcase is worth checking first. - Where the normative spec says nothing, as in test: independent-producer evidence for the parent-record canonicalization boundary #245, precedence cannot resolve it. That is a coverage gap, and the answer is to write the spec text, not to promote a draft.
- The two schema copies being byte-identical is maintained by hand today.
Not in scope here
The individual rulings, and the release-versus-main gap that #241 sits on. That one is real and separate: a fix on main is not a fix for anyone until it is tagged, which is why #241's reporter hit a schema that is correct in this repository and wrong in the package they installed.
Activity
lywinged commentedon Aug 30, 2026
Checked. It does not, and the reason is structural rather than an omission, which I think matters for the precedence question.
MUTANTSintests/test_the_schema_and_the_models_agree.pyholds 24 values, 13 of them strings, and none begins withspiffe://ordid:. The oldsubjectpattern was a prefix test, so every one of those failed it as well as failing the model's shape: both validators rejected, and they agreed by rejecting. Splitting them needs a value that passes the prefix and fails the shape, and the matrix had none, for any field. The sweep ran 672 records and was green, correctly.Four such values are added in the PR for #244. Measured there: with the schema reverted, four failures, all on
subject; with the model loosened instead, the same four with the direction flipped. No other path moves, soDECLARED_DIVERGENCESgains no row.There is also a denominator for one leg of this issue. Resolving the model's aliases,
DigestStrandretention's_DURATION_RE, the schema carries ten patterns and the model has a counterpart for each. Nine were already identical strings.subjectwas the only one that differed, and after the fix that set is empty. That is the schema-against-model leg only; it says nothing about spec-against-schema or docs-against-schema.The docs leg is worth a line, because it has a mechanism already.
docs/schema.mddescribedsubjectwith the prefix reading, and had been stale since84df8ef("security: pre-launch hardening", #49, 2026-06-18) tightened the model and left the schema on the old string.tests/test_build_provenance_depth_doc.pyparses thebuild_provenancetable out of that same file and checks it against the schema, andtests/test_references_block.pydoes the same for thereferencestable and for one row of the top-level field table. So that top-level table, the one that went stale, is parsed for exactly one row, on its type and required cells, and never on a description cell. Generalising that to the rest of the table is small, and it is the operational half you are asking for rather than a declaration.PR: #248.
imran-siddique commentedon Aug 31, 2026
Narrowing this with a measurement rather than an opinion.
#248 merged, closing #244, and @lywinged did more than the fix: they audited all ten of the schema's patterns against the model and report that
subjectwas the only differing pair.So the schema-versus-model surface, which produced two of this issue's five instances (#244 here and the
100.0case raised on #206), is now clean rather than merely fixed in one place.Two things from that PR belong on this issue:
The parity test was structurally blind, and that is the reusable lesson.
tests/test_the_schema_and_the_models_agree.pyalready existed and did not catch #244, becausesubject's schema pattern was a prefix test: every value in the differential's matrix failed both surfaces, so the two validators "agreed by both rejecting". A differential that reports agreement is worth nothing unless its inputs can separate the two implementations. Any future parity work here needs discriminating inputs generated from the looser surface, not a fixed matrix.The drift was dated. The two patterns parted at
84df8ef("security: pre-launch hardening", #49, 2026-06-18), which tightened the model and left the schema on the old string. Ten weeks in which a producer validating against the published schema could emit a record the reference implementation refuses.What remains open on this issue, now narrower than when I filed it:
parent_record_hash.agentrust-trace 0.9.0on PyPI is not the schema in this repository, so a producer can be conformant to the artifact they installed and refused by currentmain. That one is not fixed by any parity test.Item 3 is the one that keeps producing reports, and it is a release rather than a code change.
chernistry commentedon Aug 31, 2026
Four of the five instances are mine, so the producer's-eye view is the part I can
add. Short version: I agree the normative spec wins, and I do not think saying so
changes anything on its own.
Why precedence alone does not close this. Every one of the four I filed was
found the same way — validate against one surface, ship, discover another surface
disagrees. Declaring which surface wins tells a producer who was right after
the divergence exists. It does not tell them a divergence exists, and that is the
part that costs the week. The rule is worth writing down, but it is the cheap
half.
@lywinged's finding is the reusable one, and it generalises past
subject.A parity test whose inputs fail both validators proves only that the two agree
on rejecting garbage. Agreement on a value neither accepts is not evidence of
agreement. The property a parity matrix has to have is the opposite of the one
this matrix had: for every constraint, at least one value that passes one
validator and fails the other. If no such value can be constructed for a
constraint, the two really do agree there and the test can say so; if it can, the
test either catches the split or it is blind by construction. That reframing
turns "the mutants happened not to cover
subject" into a property the matrixcan be checked against, which is what stops the next one.
What I can contribute rather than just propose. Our vectors on #231 are
emitted by a checked-in generator, never hand-written, which is what makes them
reproducible. The same generator can emit a disagreement-seeking set: for each of
the schema's ten patterns, values derived mechanically from the pattern that
satisfy it and violate the model's shape, and the inverse where the model is the
looser one. That is exactly the
subjectcase and, I believe, the100.0caseon #206. Say the word and I will send it as a PR against
tests/test_the_schema_and_the_models_agree.pyrather than as a suggestion.On #245, agreed and worth stating in the resolution. Precedence cannot
resolve a surface that says nothing. Promoting the draft would make a document
that disclaims binding into a binding one, which is the wrong move. If it helps,
I can post the exact preimage our producer computes for
parent_record_hash,with the byte layout, as input to the spec text — not as a proposal for what the
spec should say, but so whoever writes it has one real implementation to check
the wording against.
One addition to the "not in scope" line. The release-versus-main gap is not
only #241's problem; it makes a conformance claim ambiguous in the same way this
issue's four surfaces do. A producer saying "conformant to TRACE v0.2" today
cannot be checked unless they also say which tagged artifact they validated
against, because
mainand the published package are different rules. Whateverprecedence text lands, naming the tagged schema as the thing a claim is made
against would close that with one sentence.
lywinged commentedon Aug 31, 2026
@chernistry The word is said. Send the generator PR.
Three steers so it lands on the first pass:
What it replaces. The current matrix is a hand-written tuple of 28 values (
MUTANTS) swept over every path in one base record, with floors of 20 mutants and 25 paths so a sweep that found nothing to mutate cannot pass silently. Your per-constraint property checks the thing those floors cannot, which is discrimination. Keep a floor of the same kind anyway: for each of the ten patterns, either at least one value that splits the two validators, or an explicit assertion that no such value exists for that constraint.The declaration stays two-sided. Today an undeclared disagreement fails one test and a stale declared entry fails the other (
test_the_two_validators_agree_except_where_declared,test_no_declared_divergence_has_quietly_been_resolved). New splits your generator finds should enterDECLARED_DIVERGENCESwith a reason string, not a new exemption list. One thing you will meet when you open the file: it already carries one environment-conditional divergence,format: uriontransparencyandappraisal.verifier, handled by a computed exemption rather than a declared entry, because jsonschema enforcesformatonly when a checker for it is installed. That pair sits outside your ten patterns, so your set does not need to cover it, but you will want to know why the exemption is computed before you touch the assertions it feeds.On the tagged artifact sentence: agreed, and it is the version of the precedence rule that is actually checkable. "Conformant to TRACE v0.2" is not a claim anyone can verify while main and the published package disagree. "Conformant to the schema at tag X" is. That sentence belongs in the precedence text.
For the preimage bytes: yes, post them on #245. One real implementation to check the wording against is exactly what that issue is missing.
chernistry commentedon Aug 31, 2026
Sent: #270.
Two things I did not expect to find, both worth reading before the diff:
BASEomits —model.weights_digest,delegation.parent_record_hash,references[].retention,references[].digest,signature— so nothing ever mutated them. That is the same failure shape as schema: the subject pattern accepts identities the reference model refuses #244 one level up: not a disagreement nobody resolved, a constraint nobody asked about.models.pynames this file as the thing that holds its pattern constants mirrored, and this file did not hold them. Two of the ten were pinned intest_references_block.py; the other eight were hand-maintained under that comment.On steer 1, I made the floor stronger than "at least one splitting value per pattern", because for a mirrored constraint no such value can exist — demanding one would be demanding something unconstructible. The test asks for the fact that makes it unconstructible (the schema's pattern string is one the model carries) and falls through to requiring a declared splitter wherever that is not true. Three outcomes per constraint, none of them silent.
On steer 2:
DECLARED_DIVERGENCESis reused, no new entries, and the third case gets its own two-sided set rather than an exemption list. I read the computed_URI_FORMAT_ENFORCEDexemption before touching the assertions it feeds;formatis notpattern, so it stays outside these ten.On steer 3: agreed on the tagged-artifact sentence. One thing already true that the issue lists as open —
tests/test_validate.py:142pins the two schema copies against each other, so the by-hand part of that bullet is the published package versus main, not the two files in the repository.Preimage bytes for #245 are on that issue:
sha256:+ hex SHA-256 of the JCS canonical form of the complete signed parent record, signature included, verified across two hops of a committed delegation chain.chernistry commentedon Sep 1, 2026
A fifth instance, and it is the first one I have that sits downstream of the spec rather than inside it — which I think bears on the precedence sentence more than the four I filed here.
This week our own exporter documentation told operators to verify records with
--level 1and called that the level to use in production. Our conformance test had always invoked--level 0. Both had been true-looking for weeks; nobody compared them, because they are different surfaces and nothing said which one a reader should believe. Measured against the four committed vectors,--level 1failsTR-RTE-001andTR-RTE-004on every one — a software-only deployment cannot reach it, since both are properties of the deployment and of the verifier's call rather than of the record. Level 0 passes all four. The prose was the only thing that disagreed, and the prose is what a producer copies.The reason I am putting it here rather than filing it: it is the same shape as #232 and #240, but the disagreement had propagated out of the spec and into a consumer's own documentation. A producer reading our page would have published a Level 1 conformance claim in good faith, from a page that was wrong in the same way and for the same reason. That is the cost of the missing precedence sentence measured one hop further out than this issue currently measures it — and it argues that whatever ruling lands should be stated somewhere a downstream project can cite when writing its own docs, not only somewhere the spec's own four surfaces can be ranked against each other.
Concretely, the two things that made it detectable on our side were the conformance suite pinning a level and the vectors being committed. Both are things TRACE already gives producers. A one-line normative statement of which surface wins would let a producer's own test assert the claim its docs make, which is the loop that closed this for us.
Three of mine are still open and waiting on you, no rush intended: #234 (schema description vs §3.1.1), #242 (
docs/trust-levels.mdon the all-zero convention), and #270 (the pattern-constraint generator, approved). #234 and #242 are why #232 and #240 are still open — happy to rebase either if they have gone stale.imran-siddique commentedon Sep 2, 2026
This issue needs restating. I filed it as "four surfaces disagree and nothing says which wins". Two PRs merged today show the harder problem is that the tests meant to catch the disagreement were not able to detect it, and both were found by contributors auditing coverage rather than by anything failing.
trace-spec#270 (@chernistry).
test_the_schema_and_the_models_agree.pyanswers "do these two validators disagree about the values I tried", and I read it as answering "are these ten constraints consistent". Five of the ten patterns sit on optional fields absent fromBASE, so they were never mutated and never reached their own boundary:model.weights_digestdelegation.parent_record_hashreferences[].retentionreferences[].digestsignaturetrace-tests#91 (@opento-suggestions). The digest-parity guard
test_every_known_site_is_listedassertedlen(SCHEMA_SITES) == 6against another constant in the same module, so it compared the test's beliefs with the test's beliefs. Its docstring claimed a seventh digest field would fail it. @lywinged tested that claim by adding a seventh field to the schema with a drifted pattern and reported the suite stayed green.What this changes about this issue
When I recorded #248's ten-pattern audit as narrowing this issue, I was wrong to treat it as settled. The audit was real and the fix was real; what it could not establish is that the remaining nine patterns are consistent, because five of them were never exercised.
So the open work here is one item longer than it was:
parent_record_hash.Item 5 is the one that would have prevented items 1 and 2 being found by producers in production rather than by CI.
A note on how both were found. Neither came from a failing test, because neither test could fail. One came from a contributor asking what their own matrix actually reached; the other from a reviewer deliberately introducing the drift a guard claimed to catch. That is the technique worth generalising here, more than any particular fix.
chernistry commentedon Sep 2, 2026
Item 5 is the one worth being held to, so: the form that made it checkable on #270, and one leg where it still is not.
On #270 the evidence was per-commit rather than per-PR. Each commit adds the guard together with a test that fails for the stated reason on its parent and passes on it, so the counterfactual is something a reviewer runs — revert the one commit, watch the new test go red — rather than something the author asserts. @lywinged did it from the other side on both #248 and #270, reintroducing the drift by hand, which is stronger. Either satisfies item 5; a green run and a docstring does not, which is what both of today's cases were.
Where it is still not true: description text. Two tests parse the field tables in
docs/schema.md—test_build_provenance_depth_doc.pyandtest_references_block.py— and both read the same three cells: name, type, required. Nothing undertests/compares a description cell against the surface it describes. That is the class both #244 and #232 fall into: the rule stated in prose goes stale or narrow while every parsed cell around it stays correct, so nothing can go red. Ten weeks, for the first one.There is one precedent for holding description text, and it is small:
test_references_block.pyasserts everyrelenum value is named in that field's schema description, so adding a value without documenting it fails. What has no counterpart is the reverse direction, and the doc side of either.The honest reading of item 5 here is that a text-equality guard is unconstructible. Two hand-maintained prose surfaces cannot be compared mechanically, and one written anyway would report agreement for the same structural reason the old matrix did. The constructible version is to stop maintaining both by hand — derive the doc's field table, description cell included, from the schema — after which there is something to compare. Same shape as the mirrored-constraint case on #270: ask for the fact that makes a splitter impossible, rather than demand a splitter that cannot exist.
Scope is yours to set. Generating the whole table is a larger change to
docs/schema.mdthan anything I have sent here. A narrower one is a fraction of it and closes the coverage half without touching any prose: every field the schema gives a description has a row, no row describes a field the schema does not carry, and the enum-naming check generalised pastrel. Say which and I will send it.spec: state what delegation.parent_record_hash is a digest of (#245) (#…
imran-siddique commentedon Sep 7, 2026
The next scope decision is the offered narrow schema/doc field-and-enum coverage guard versus generated field-table prose. Preserve the counterfactual requirement for drift tests. The preimage definition merged in #276. The published references-schema gap in #241 is now verified fixed in the 0.10.0 wheel: the same references-bearing record rejected by 0.9.0 passes its schema. The precedence and remaining coverage questions stay open.
chernistry commentedon Sep 7, 2026
On the scope decision, I would take the narrow schema/doc field-and-enum coverage guard over generated field-table prose, for one reason that outweighs the convenience of generation: the drift test stays real only under the guard.
The counterfactual requirement is that introducing drift makes a test fail. Under a coverage guard that holds directly — remove a doc entry for a field or an enum value, the guard reds. Under generated prose it goes vacuous: generated text cannot drift from the source it was generated from, so the test ends up exercising the generator rather than the agreement between two surfaces. You would keep the requirement on paper and lose the thing it was protecting.
Two qualifications, both of which argue for keeping the guard narrow:
So: guard for coverage, an explicit sentence for precedence, and generation only if it is ever wanted for readability, never as the drift mechanism.
imran-siddique commentedon Sep 11, 2026
Ruling both, because you are right that one does not answer the other and leaving the second open would make the first unfalsifiable.
1. Guard scope: narrow field-and-enum coverage. Adopted.
Your counterfactual is the whole argument and it decides it. Generated prose cannot drift from the source it was generated from, so a drift test over generated text exercises the generator and not the agreement between two surfaces. The requirement survives on paper and the thing it protected is gone. Same shape as a conformance corpus that cannot fail in the direction it exists to test.
So: every field and every enum value in the schema must appear in the docs, checked directly, and removing a doc entry reds the guard. No generation.
2. Precedence. Ruled, and it is new normative text.
There is no precedence statement in the repository today. I checked: the only thing close is one line in
docs/verification.mdcalling §3.3 authoritative, which is a statement about one document rather than a rule.The order, from most authoritative to least:
spec/trace-v0.2.mdwins on meaning. It is the only surface that defines what a field means and when a verifier must refuse.schema/trace-claim.jsonis authoritative for what validates, and for nothing else. A producer may rely on it to know whether a record is well formed. It does not define meaning, and a schema description is not a normative statement.src/agentrust_trace/models.pyis an implementation. It is never authoritative. A divergence there is a bug in the implementation.docs/*.mdis explanatory and never authoritative.docs/rfcs/*already disclaims normativity and stays that way.The rule that matters more than the ordering. Where the schema and the spec disagree, the schema is defective, and the resolution is to fix the schema rather than to read the narrower of the two as the rule. This is the case all four of your instances share: #232, #240 and the rest were producers doing the reasonable thing and validating against a surface that under-stated the spec. Telling them they picked the wrong surface is not an answer, because there was no published order for them to consult.
And the consequence, stated so it is not discovered later. A schema that is narrower than the spec rejects conformant records. That is a defect with a severity, not a documentation nit, and it should be filed as one. A schema broader than the spec accepts non-conformant records, which is worse.
3. What this means for the guard
The guard proves coverage. The precedence rule decides conflicts. Neither is a substitute and both land: the guard as a test, the precedence order as a short normative section near the top of
spec/trace-v0.2.md, where a producer will actually meet it before they start validating.Happy for you to carry the guard. The precedence section I will write, since it is normative text and I answer for it.
chernistry commentedon Sep 16, 2026
The guard is up as #365, narrow as ruled: every property name and every enum value in
schema/trace-claim.jsonmust appear as an inline-code token underdocs/(rfcs excluded) and indocs/schema.mditself, checked directly, nothing generated. The counterfactual is run rather than asserted, in both directions.It found two gaps on arrival, both in
docs/schema.mdand both fixed in the same PR:cnfhad no member table, sokty,crv,x,y,n,eandkidappeared only inside a fenced example, and theenforcement_moderow named neitheradvisoryas a value nordeclaredat all. The precedence section stays yours, as ruled.test(docs): key the schema-doc coverage guard on position, not on the…
5 remaining items
chernistry commentedon Sep 21, 2026
Two specification questions from the differential run in #376, filed here rather than as bugs because the reference implementation is not wrong on either: the text leaves them open, and two implementations in two languages cannot agree until it settles them.
1. Number spelling. §3.2.2 fixes the safe-integer range but not what a number's spelling means.
iatwritten1785000000.0is an integer afterJSON.parse(JavaScript has one number type) and a float afterjson.loads; the reference rejects it (record has no valid integer 'iat' for freshness check), a JavaScript verifier accepts it, and both readings are defensible.1.0e+21meets the same fork from the other side: a float in Python, which RFC 8785 renders as1e+21; an integer beyond 2^53−1 in JavaScript, which §3.2.2 requires the verifier to reject. Proposed rule, either half works: (a) an integer-valued member MUST be spelled as a JSON integer, no fraction and no exponent, and a verifier MUST reject any other spelling; or (b) a verifier MUST decide by value after parsing, so1785000000.0is the integer 1785000000 everywhere. (a) is the smaller change and the one a language with one number type can implement without a custom parser.2. Canonical form of the
signaturemember. §3.1.3 digests the parent record with itssignaturemember present, so the spelling of the signature is inside the chain digest. RFC 4648 §3.5 makes the unused trailing bits of the last character canonical (zero); the schema pattern^[A-Za-z0-9_-]+$does not, and the reference decodes leniently. Reproducer on 0.10.0, base record from #379: re-spell the last character of the 86-character signature so its four unused bits are non-zero. The same 64 bytes decode, the record verifies under both spellings, and the two records have different chain digests (sha256:c071070d…againstsha256:cf1f58fd…). Proposed rule: thesignaturemember and every base64url JWK member (x) MUST be in RFC 4648 canonical form (no padding, unused bits zero), and a verifier MUST reject a non-canonical spelling before decoding. The chain digest then depends on the signature bytes rather than on one of their spellings. Backward compatibility: a correct base64url encoder already emits the canonical form; only a hand-edited record changes verdict.Neither touches the implementation fixes in #379–#382, which stand on the current text.
chernistry commentedon Sep 21, 2026
A third reading question from the same run, on the check-order contract rather than a rule; it is ledger entry 6 in #376, the one of the thirteen with nowhere else to go.
A
signaturemember present with a non-string value. Both implementations reject; the reference reportssignature_missingfor0,false,[]and{}, andsignature_malformedfor1,true,[0]and{"a": 1}, so its verdict on a present non-string is decided by the value's truthiness. The TypeScript reportssignature_malformedfor all eight. Reproducer on 0.10.0, base record from #379:§3.2.2 says a record MUST carry a signature, and the schema types the member as a string; neither says whether a present non-string is absent or malformed. Proposed: a present member of the wrong type is malformed, and "missing" names an absent member only. A caller told "missing" for a value that is there goes looking for the wrong defect. Non-normative either way; a one-line change to the presence test in the reference if the reading is accepted.
imran-siddique commentedon Sep 21, 2026
Alex, agreed. “Missing” should mean the signature field is absent. If it is present with the wrong type, report it as malformed. Both cases still reject the record; this makes the error accurate.
imran-siddique commentedon Sep 21, 2026
Alex, I support requiring one canonical encoding. Please open a focused specification proposal with test examples and explain which existing records would stop passing. Keep this separate from the number-spelling question.
imran-siddique commentedon Sep 21, 2026
Alex, I favor deciding by value: a whole number within the existing limits should count as an integer, including decimal or exponent spellings. Please bring this as a separate proposal with Python and JavaScript tests, including boundary cases. JavaScript’s normal parser loses the original spelling, so enforcing a spelling rule would require preserving that information.
chernistry commentedon Sep 22, 2026
Both proposals will come separately, as you asked. The signature-encoding one on Wednesday 24 September, the number one on Friday 26 September.
On which existing records would stop passing, since that is the part worth having before the proposal rather than inside it: §3.2.2 already says the embedded signature is base64url with no padding, so the proposal is not a choice of encoding but a rule about the canonical form of the one already named — whether a spelling with non-zero unused trailing bits, which decodes to the same 64 bytes, is a different record. I scanned everything published for a signature that is not already canonical: 216 signatures across this repository's
examples/and thetrace-testscorpus at3af2b53, and 4 intrace-registry, all canonical. So the tightening invalidates nothing that exists today; what it changes is what a conformant verifier must do with a record someone mints tomorrow.It is still a change to what verifiers must reject, so I would rather you set the class than assume it. I will bring it with the reproducer in both implementations, the vectors, and the scan above restated so it can be re-run.
spec(numbers): decide integer-valued members by value, with Python an…
docs(signature-encoding): classify the tightening, list the rejected …
spec(numbers): bound the by-value rule to the objects section 3.2.2 c…
Merge main; route the signature pattern through the shared pattern he…
docs,test(signature-encoding): account for the strict decoder of 0.11…
Merge main; run the range walk inside the recursion guard (agentrust-…
fix(integer-by-value): the range reaches the provenance record; name …
Proposal: require canonical base64url encoding for the embedded `sign…
spec(numbers): decide integer-valued members by value, with Python an…