Repository navigation
Description
Spec section affected
None. This is about src/agentrust_trace/sign.py and src/agentrust_trace/provenance.py, measured at 5367970, and it proposes no normative text — no RFC 2119 keyword changes and nothing that binds an implementation. By GOVERNANCE.md that puts it in the no-sponsor-required set, alongside tooling and conformance tests.
Filed as a proposal rather than a PR because the useful outcome is an answer about direction, not a refactor landing.
Problem
Six verification properties have arrived one at a time, each landing on one side of a two-verifier codebase, and nothing structural stops the next one.
There are two signature verifiers here:
$ git grep -l "\.verify(" -- 'src/agentrust_trace/*.py'
src/agentrust_trace/provenance.py
src/agentrust_trace/sign.py
The registry anchor leaf hashes and verifies no signature; content_marking.verify_assertion verifies none either, by explicit design — "The C2PA manifest signature and the Trust Record signature are separate checks by separate keys, and this function performs neither." Neither is a third verifier.
The two that exist share _canonical_bytes, _pubkey_from_jwk and key_to_jwk. Every policy decision above those is implemented per format, or not implemented:
| Property | sign.py |
provenance.py |
|---|---|---|
| Key identified by RFC 7638 thumbprint | #157 | dict equality — #149 open |
| Revocation at verification time | #113 | absent — #164 open |
| Maximum record age | pre-existing | absent — #164 open |
| Future-dated record rejected | #155 | absent — #164 open |
| Schema enforced during verification | #156 | absent |
| Structural rules applied consumer-side | — | #146, after #142 |
Most of those landed in the last week. #157 is the sharpest instance: it landed
compare_digest(jwk_thumbprint(embedded_jwk), jwk_thumbprint(trusted_jwk))in sign.py on 2026-08-11, while provenance.py compared the same thing as Python dicts — and jwk_thumbprint was already exported from the package, covered by 16 tests, and the identifier a revocation store is keyed on. Not a knowledge gap. A sharing gap.
The argument #146 settled at function scope is the same one, in its own commit message:
two copies drift, and the copy that matters is the consumer's
That was about a rule enforced in build_record and not in verify_record. This is that sentence one level up, about a property enforced in one format and not the other.
Proposed change
No normative text, so nothing to paste. The shape:
An envelope layer implementing signature verification, key resolution and comparison, revocation, and freshness once, with each format declaring which of those it requires and supplying its own structural rules.
All the signed formats already sign JCS bytes over the record with the signature member removed, so the envelope exists implicitly. Making it explicit means a new format inherits the checks rather than re-deriving them, and a property added later propagates instead of having to be remembered twice.
Costs, which I would rather state than have found:
- It couples the two formats' release cadences. A change to the envelope is a change to both.
content_markingwould consume only part of it, since it verifies no signature — so the envelope cannot simply be "the thing every format uses".- It touches the two most security-sensitive functions in the library at once, which is the kind of change that wants to land alone rather than alongside anything else.
Backward compatibility
- Non-breaking (new optional field, informative addition)
- Breaking (removes or changes required field, changes wire format, changes algorithm set)
Non-breaking as scoped here: no wire format, schema, or conformance level changes, and no record that verifies today stops verifying. The properties provenance.verify_record is missing are being added as opt-in parameters in #164 with defaults chosen so nothing already passing begins to fail; the envelope would move where that code lives, not what it decides.
The one thing to watch is that a shared implementation makes it easier to change both formats at once by accident, which is a review question rather than a compatibility one.
Motivation
No conformant implementation produces or accepts an invalid record because of this, and once #149 and #164 land every individual gap in the table is closed. There is no live defect here.
The cost is that each gap has needed a separate report to notice, and each fix has needed a separate PR. That is a price paid per property rather than once, and it falls on whoever is reading both files closely enough to see that one of them is behind.
If the answer is "yes, but not before v1.0", that is a useful answer and I will stop sending these individually. If the answer is that the two formats should stay independent on purpose, I would like to know that too — it is a defensible position and it would change what I look for.
Related issues or PRs
#149 and #164 close three of the six rows. #146 settled the same argument at function scope after #142. #157, #155, #156 and #113 are the sign.py side of the table.
Activity
github-actions commentedon Aug 12, 2026
❔ Contributor Check: UNKNOWN
Automated check by AgenTrust Contributor Check.
imran-siddique commentedon Aug 21, 2026
Answering the question you actually asked, which is the direction rather than the refactor.
Yes in principle. Not before v1.0.
The envelope is the right shape. All the signed formats already sign JCS bytes over the record with the signature member removed, so it exists implicitly, and making it explicit is how a new format inherits the checks instead of re-deriving them. Your framing of #157 is the one that decides it for me:
jwk_thumbprintwas already exported, already covered by 16 tests, and already the identifier a revocation store keys on, whileprovenance.pywas comparing the same thing as Python dicts. Nobody failed to know. There was nowhere for the property to live once, so it had to be remembered twice, and remembering twice is the thing that fails.And you are right that this is my own argument one level up. "Two copies drift, and the copy that matters is the consumer's" was about a rule in
build_recordand not inverify_record. A property enforced in one format and not the other is the same sentence with a wider scope, and I do not get to hold it at function scope only because the wider version is more work.Deferring past v1.0 for two reasons, both from your own costs section rather than against it.
Every row in your table is now closed. #149, #164, #157, #113, #155, #156 and #146 are all merged, so there is no live divergence and nothing is currently wrong for any implementation. That removes the urgency without removing the argument.
More importantly, you named the reason yourself: this touches the two most security-sensitive functions in the library at once, which is a change that wants to land alone. It cannot land alone right now. #66 and #173 are building a release that revises the action-receipt corpus, and #178 will move pinned key material inside it. Putting a shared-verifier refactor next to that means a release where both the vectors and the verifier changed, and if something goes wrong there is no clean way to tell which. After v1.0 it can have its own release and its own attention.
Please do not stop sending the individual reports in the meantime. That is the part of your question I want to answer explicitly, because "yes, later" could reasonably be read as "stop". Until the envelope exists, a per-property report is the only mechanism that catches a divergence, and each one you have sent has been correct and has landed. The price you describe, paid per property rather than once, is real and I am choosing to keep paying it for now rather than pretending it is not there.
Two things to carry into the eventual design, so they are recorded while the reasoning is fresh:
content_markingconsuming only part of it is the constraint that shapes the whole thing. The envelope cannot be "what every format uses", because that function verifies no signature by explicit design and should not be made to look as though it might. Each format declaring which properties it requires, as you propose, is right, and the declaration has to be able to say "none of the signature ones".Your closing caution is the one I would build the review process around: a shared implementation makes it easier to change both formats at once by accident. Whatever lands should make a two-format change loud in the diff rather than possible in a one-line edit.
The UNKNOWN on the contributor check is the bot, not you. It carries no information and I do not read it as a signal.
imran-siddique commentedon Sep 7, 2026
The shared-verifier-envelope refactor remains explicitly deferred until after v1.0. Individual verifier defects continue independently; this is not a current release blocker.