Repository navigation
Description
Spec section affected
docs/integration/pic-trace-bridge-v1.md- Bridge verifier tests and the eventual conformance-corpus location
Problem
Issue #179 delivered the PIC/TRACE authorization bridge design, schema, reference verifier, and eight implementation tests. Its completion note records a narrower remaining gap: those tests use inline fixtures and are not a portable vector set that a second implementation can consume.
TRACE already has a general cross-language RFC 8785/JCS corpus from PR #126. The remaining gap is bridge-specific:
- Can another verifier reproduce the bridge-specific signed bytes and digests while using PR test(conformance): portable vectors for the RFC 8785 requirement #126 for shared canonicalizer conformance?
- Does it reach the same result at trust-key, time-window, scope, digest, and transcript boundaries?
- Does each negative case fail for the intended rule rather than an unrelated earlier error?
Proposed change
This would be a non-normative, informative conformance addition. I propose a versioned JSON corpus for pic-trace-bridge-v1, an independent verifier, and a differential runner.
The case contract would contain:
- bridge artifact and trusted-authorizer JWK;
- declaration and PIC verifier outputs;
- tool call and optional transcript;
- fixed verification time;
- expected semantic result and stable reason code; and
- bridge-specific canonical bytes and reproducibility metadata where relevant.
Vector families would cover:
- valid bound execution;
- malformed and unknown-field rejection;
- signature, trust-key, and
authorizer_key_idfailures; - deny, not-yet-valid, invalid-window, and expiry behavior;
- tool and impact scope mismatch;
- PIC, declaration, and tool-call digest mismatch;
- missing, partial, or contradictory transcript evidence; and
- cross-language canonicalization by reference to PR test(conformance): portable vectors for the RFC 8785 requirement #126, with new signed-object fixtures only where the bridge introduces an additional input or binding.
The independent verifier would not import agentrust_trace.intent_bridge. The runner would execute the corpus against both implementations and report agreement or a preserved ambiguity. A bounded mutation check would verify that each negative vector makes its named rule load-bearing.
I would appreciate maintainer guidance on whether the corpus should live beside the bridge in trace-spec, in trace-tests, or initially in a standalone repository until the case contract and outcomes are reviewed.
Backward compatibility
- Non-breaking (informative conformance corpus and tooling)
- Breaking
No change to the completed bridge schema or verification semantics is proposed. Any ambiguity found during differential testing would be raised for maintainer decision before becoming a claimed conformance rule.
Motivation
Agreement between a verifier and inline tests constructed in the same codebase does not establish cross-implementation agreement. A portable corpus would let a second implementation test trust configuration, temporal boundaries, scope, execution binding, and failure classification without inheriting the reference implementation's assumptions.
Reusing PR #126 keeps generic JCS portability in one place and makes this contribution specifically about the authorization-to-execution bridge. The documentation would continue to keep authorization, execution binding, runtime attestation, and real-world outcome verification separate.
Acceptance criteria
- A clean offline checkout regenerates byte-identical vectors.
- Test-only signing material and corpus hashes are published and clearly non-production.
- Every case validates against the versioned schema before semantic verification.
- The AgenTrust reference verifier and independent verifier agree on all settled cases.
- Differences are reported for maintainer decision rather than silently normalized.
- Shared RFC 8785 behavior is reused from PR test(conformance): portable vectors for the RFC 8785 requirement #126; any new canonicalization fixture exercises a bridge-specific input.
- The corpus is language-neutral and usable by a future TypeScript, Rust, or other SDK.
- Documentation preserves the boundary between authorization, execution evidence, attestation, and outcome claims.
Non-goals
- No redesign of the completed bridge semantics.
- No PIC dependency in the TRACE Trust Record schema.
- No runtime self-asserted authorization or embedded trust anchor.
- No claim that a valid authorization proves execution, outcome correctness, or TRACE attestation.
- No attempt to settle the gated
step_upanddeferdesign in spec: binding attributable human-approval outcomes to step_up and defer execution evidence #191.
Related issues or PRs
- Design origin and recorded portability gap: design: bind authorized pre-execution intent to TRACE execution evidence #179
- Existing portable RFC 8785/JCS corpus to reuse: test(conformance): portable vectors for the RFC 8785 requirement #126
- Adjacent gated-decision threat-model context: spec: binding attributable human-approval outcomes to step_up and defer execution evidence #191
Implementation offer and relevant work
I would like to contribute the initial case contract, deterministic generator, independent Python verifier, differential runner, and documentation. Relevant prior work:
- TriageCore Signed Intent evidence boundary: https://github.com/coreytshaffer/triagecore-signed-intent#evidence-boundary
- Agent Control-Plane Eval Kit: https://github.com/coreytshaffer/agent-control-evals
Activity
github-actions commentedon Aug 24, 2026
🟡 Contributor Check: MEDIUM
Automated check by AgenTrust Contributor Check.
imran-siddique commentedon Aug 26, 2026
Accepted. Please send it.
Checked your premises at pinned main rather than taking them: #179 closed on 2026-08-23, and all four artifacts it delivered are there,
docs/integration/pic-trace-bridge-v1.md,schema/pic-trace-bridge-v1.json,src/agentrust_trace/intent_bridge.py, andtests/test_intent_bridge.pycarrying exactly eight test functions. Your "eight implementation tests, inline fixtures, not portable" is accurate down to the count, and keeping this separate from PR #126's shared JCS corpus rather than duplicating it is the right boundary.Your third question was live, and it was broken
You asked:
Those eight tests were failing that test as of this morning. @lywinged's #204 merged a few hours ago and found that the bridge cases used bare
pytest.raises(IntentBridgeError), and sinceAuthorizationDeniedandAuthorizationMismatchboth subclass it, a case passed on any rejection rather than the one it named. A("tools", [""])case intended to exercise the item-emptiness guard was passing asAuthorizationMismatch: executed tool is outside the authorized tool scope. Separately, the scalar cases ran atnow=0against a fixture valid from 100, so four of five were tripping "authorization is not yet valid" before reaching the guard they were named for.Three of the five runtime guards could be deleted with the whole suite still green.
So you proposed, from outside and without seeing it, the exact property that turned out not to hold inside. That moves it from a nice-to-have in your corpus to the requirement it is built around.
Scope I am accepting
Your case contract as drafted, with one thing promoted from implicit to mandatory:
Every negative vector pins a stable reason code, and the runner asserts the code rather than the fact of rejection. A vector that only records "this must fail" is worth very little, because a second implementation can fail it for a reason neither of us intended and still look conformant. This is the property #204 had to retrofit; build it in.
The rest as you wrote it: versioned JSON, bridge artifact and trusted-authorizer JWK, declaration and PIC verifier outputs, tool call and optional transcript, fixed verification time, expected semantic result, and bridge-specific canonical bytes with reproducibility metadata. The fixed verification time matters more than it looks, for the reason #204 demonstrated: a vector set whose time base is the wall clock tests the clock.
Non-normative and informative, as you scoped it. No changes to
docs/integration/pic-trace-bridge-v1.md's normative content in the same PR.Two suggestions
Coordinate with @lywinged if you can. #204 just rewrote the inline tests your corpus is meant to supersede, and the
match=expectations it added are effectively a first draft of your reason codes. Starting from those rather than deriving them independently will save you a reconciliation pass.And prove the corpus is load-bearing the way #204 proved its own: take each rule the bridge enforces, remove it, and show which vectors go red. A corpus where every vector still passes with a guard removed is the same failure in portable form.
needs-review:MEDIUMon this issue is our contributor-check bot, not a judgement about the proposal.lywinged commentedon Aug 26, 2026
@coreytshaffer, on your third question, which is the one I could answer with a measurement.
"Does each negative case fail for the intended rule rather than an unrelated earlier
error?" I ran that against the bridge rather than reasoning about it. Each of the five
runtime guards in
src/agentrust_trace/intent_bridge.pywas removed one at a time, andtests/test_intent_bridge.pyrun against two trees: the one before #204, andmaintoday.maintoday_nonempty_stringcallsor not value, the empty-array halfand item, the empty-string halfauthorization[field] < 0Three of the five could be deleted with the file still green. The file held 19 cases then and
holds 21 now, the two added being the empty-array ones. The two causes are already on this
thread: bare
pytest.raises(IntentBridgeError)with two subclasses under it, and anow=0time base against a fixture authorized at 100. #204 is mine and fixed both, changingonly the test file. The right-hand column is what that bought.
So your fixed verification time is not hygiene. A wall-clock time base was one of the two
things that made a green suite meaningless here.
And one thing about why that file carries the weight it does.
agentrust_trace.intent_bridgeis exported in__all__, has no caller anywhere in thisrepository outside its own test, and the conformance suite has no bridge module at all: I
grepped
trace-testsfor one and found nothing. Sotests/test_intent_bridge.pyis the onlything checking the bridge today. That is what made three deletable guards more than untidiness,
and it is the gap your corpus closes rather than duplicates.
The draft reason codes, since they are worth having in one place. What #204's
match=expectations amount to:
authorization_id must be a non-empty string, same forauthorizerandauthorizer_key_idauthorized_at must be a non-negative integer, same forexpires_atmust not contain duplicatesmust be a non-empty array of non-empty stringsexpired,after,nowbefore/afterunknown fieldsThey are message fragments rather than codes, and
verify_bridgehas no code table to bindthem to. Turning them into codes is the part your corpus adds.
Where your contract already has precedent in this repository
trusted_keybeside a signedrecordin the boundary vectors,context.trusted_root_keysin delegationcontext.now, in all 23 delegation vectorsexpected: {classification, codes}, ten codes across those 23, one per registry ruleTwo of your acceptance criteria come free.
tests/test_generators_reproduce_fixtures.pydiscovers generators by globbing
examples/**/gen_*.py, empties the directory, runs thegenerator, and compares name sets before bytes. And your third question is enforced there
rather than intended: the delegation walk emits every code from one function, and
test_delegation_completenessasserts by AST that no other function appends to a failure orwarning list. It also wants two load-bearing vectors per rule and a declared defect that
separates them.
Three more of your criteria have partial answers here. Test-only material:
NOT_GENERATEDrecords that the
action-receipts/conformancefixtures 01-09 pin a key whose private half wasnever published, and that everything else there pins its own deterministic test key. Schema
before semantics: the delegation walk runs
iter_errorsfrom the library first and appliesits own rules after. Language neutrality:
examples/action-receipts/acta/gen.mjsis aJavaScript generator sitting beside three Python ones, and its fixtures are re-verified in CI
by
tests/test_acta_fixtures.py. The regeneration harness globsgen_*.pythough, so thebyte-identical guarantee is the one part that does not reach across languages today.
On canonical bytes, since you are carrying them: no corpus here publishes bytes. The
boundary vectors prove canonicalization through the signature, because a record signed over
its JCS bytes will not verify under a serializer that produces different ones, and
diverges_undernames the ones that break it. That is mostly a note about what yourreproducibility metadata has to do: keep published bytes from drifting away from the code
that produced them.
One correction on the 8785 dependency. What you would reuse is already merged:
examples/canonicalization-boundary/withtests/test_canonicalization_boundary.py, added in#160 and extended in #170. #126 itself is still open, and its head no longer shares history
with
main, so build againstmainrather than against that PR. It is mine, so I wouldrather say it than have you find it.
On placement: every corpus published here for other implementations sits in
examples/<topic>/with its generator beside it and the enforcing harness intests/.trace-testscarries five fixtures undertests/vectors/, but those are its own inputs forscoring a record. So the precedent is
examples/pic-trace-bridge-v1/. I tried thatliterally before saying it: a new directory there with one
gen_*.pyand one fixture besideit is collected as its own case with nothing registered anywhere, and changing a single byte
in the fixture fails that case and only that case.
coreytshaffer commentedon Aug 29, 2026
I have completed the issue #206 Gate 1 rejection inventory against current
main(
e7e2ecab68cf3534c7d5fcb7e9a6f089fcb7d592; #204 is in its ancestry). Before Igenerate bulk fixtures, I would like confirmation on the public v1 taxonomy.
Proposed runtime classifications remain
accepted,invalid,denied, andmismatch. The settled codes are:The deliberate groupings are: empty scope versus empty item; signed versus supplied
PIC digest syntax; all failures inside the current broad post-decode catch; non-
allowdecisions; and declaration/tool-call JCS failures.
signature_representation_invalidcovers missing, non-string, or decoder-rejectedsignature representations; its anchored raw-
ValueErrordecoder signal is normalizeddespite the documented exception-class inconsistency.
authorization_unverifiablecovers the later broad catch, including cases where malformed trusted-key material or
authorization canonicalization prevents Ed25519 verification from beginning.
I found four points I propose to leave explicitly unsettled rather than normalize:
be accepted by the permissive runtime decoder;
100.0as an integer while the Python runtime does not; andaftercurrently requires only an object, with no required members.I will keep these as documented divergences and will not change
src/agentrust_trace/intent_bridge.pyin this corpus contribution. Does this tableand treatment of the four divergences match the intended Gate 1 contract?
imran-siddique commentedon Aug 30, 2026
Taxonomy confirmed. Four runtime classifications and the code set as listed. Generate the fixtures.
The groupings you called deliberate are the right cuts, and two of them are the ones I would have asked for if you had not made them: empty scope versus empty item, and signed versus supplied PIC digest syntax. Both separate a producer error from a verifier error, which is the distinction a corpus exists to pin.
authorization_unverifiableas the name for the later broad catch is right, and worth keeping even after the catch narrows: "verification could not begin" is a different statement from "verification failed", and a corpus that conflates them teaches implementers to conflate them too.On the four divergences: two documented, two reclassified
Divergence 2 (safe-integer timestamp violations collapse into the broad signature failure) and divergence 4 (transcript
afterrequires only an object): document them as you propose. Both are diagnostic-quality and coverage gaps in the current implementation, neither makes two conformant implementations disagree, and fixing them is out of scope for a corpus contribution.Divergences 1 and 3 need different treatment, and it is not "fix them here". Do not change
intent_bridge.pyin this contribution; that boundary was right. But do not record them as expected behaviour either.ca2a's transport codec rejects exactly those: standard-base64+and/,=padding, and invalid lengths, failing closed and naming the field.100.0as an integer. JSON Schema treats a number with zero fractional part as an integer; the Python runtime does not. This is the third instance of that class today. cmcp#588 has{"n": 1}and{"n": 1.0}sharing one JCS canonical form while Cedar evaluates them differently andparameter_headers()raises on one. schema: the subject pattern accepts identities the reference model refuses #244 has the schema'ssubjectpattern accepting identitiesmodel_validaterefuses.Both belong to #247, which I opened this morning for exactly this: the normative spec, the JSON Schema, the reference model and the docs state the rules and nothing says which wins.
Why the label matters more than it sounds. A conformance corpus is a statement about what correct behaviour is. A fixture asserting that the permissive decoder accepts a padded signature does not document a divergence, it defines the permissive behaviour as correct, and the next implementer builds to the fixture rather than to the schema. That is the same failure I flagged on trace-tests#86 today, where a vector named
invalid_canonical_integer_out_of_rangewould have encoded a library defect as a conformance verdict.So: carry 1 and 3 in the corpus as known defects with a tracker reference, not as expected results. Whatever shape your metadata takes, a consumer should be able to tell "the implementation does this and that is correct" from "the implementation does this and it is a recorded defect". If your fixture format has no slot for that distinction, that is worth telling me before you generate in bulk, because retrofitting it across a corpus is expensive.
One thing I am not asking for
Do not block on 1 and 3 being fixed. The corpus is more useful landed with two rows marked as defects than held until the decoder is tightened. #247 is where the fix gets sequenced.
Thank you for pinning the SHA and confirming #204 is in its ancestry before inventorying. That is why I could answer this from the taxonomy rather than re-deriving the state first.
imran-siddique commentedon Sep 3, 2026
Linking #279. The SKIP-rather-than-PASS question raised here is one of six instances in this repository of the same primitive, and the corpus surface is the one where getting it wrong is most expensive.
The rule #279 carries that bears directly on a conformance corpus: no fixture may encode current behaviour as expected where a surface silently passes today. A fixture asserting the current behaviour defines it as correct, and the next implementer builds to the fixture rather than the spec.
#279
coreytshaffer commentedon Sep 4, 2026
Thanks for linking #279. I ran a read-only reconciliation audit of the held #206 corpus against
3c2f96375afaea6c615c49dea21d83a7969862e3before changing any expectations.One positive fixture needs clarification:
TRACE-PIC-BRIDGE-003(accepted-empty-after-object). It hastranscript_required: trueandafter: {}, and the corpus expectsacceptedwithtranscript_bound: true. That is the corpus's normalized result, not a field returned directly by the reference API. The generator explicitly declares this expectation; it does not learn it by running the verifier.With the other inputs held fixed, both verifier paths produce these normalized outcomes:
transcript_required: true)after: {}accepted,transcript_bound: trueafter: {"unrelated": true}accepted,transcript_bound: trueafter: nullmismatch / transcript_after_invalidafter, or absent transcriptmismatch / transcript_incompleteYour earlier guidance treated the object-shape-only behavior as a documented coverage gap. I am asking how to reconcile that with #279, without presuming it is a defect.
What positive claim should the corpus's
transcript_bound: truerepresent whenafteris required? Is structural completeness sufficient, or must the after-record establish a defined execution-result fact? If the latter, which fact, and what bridge-specific not-established outcome/reason should the corpus pin when the evidence is insufficient? A nonempty-object guard alone would not settle this, given the unrelated-object result above.Case 003 is held locally in an audit sidecar pending that decision. The fixture, both verifiers, taxonomy, runner, existing expectations, and frozen denominator remain unchanged. Zero-silent-PASS certification is withheld; I will not claim new conformance credit for 003 while its semantics remain unresolved.
imran-siddique commentedon Sep 7, 2026
The corpus is held on the explicit meaning of transcript_bound for an empty or unrelated after object. Keep case 003’s disputed positive claim out of new conformance credit until that maintainer decision is resolved; #279 supplies the absence-reporting constraint.
madeinplutofabio commentedon Sep 15, 2026
As PIC maintainer, I think the #206 hold now has a clean boundary after #338/#340.
From the PIC side,
allowmeans the pre-execution PIC contract passed for the declared action. It does not mean the tool executed, the action succeeded, or a successor state was established.So I would keep the bridge corpus on three separate axes:
pic_authorization_bound: the bridge consumed PIC-defined verifier outputs, includingPIC-CJSON/1.0,intent_digest, andargs_digest, and those matched the signed authorization.successor_envelope_boundorafter_digest_bound: the suppliedtranscript.afterobject matched the digest carried in the signed authorization.successor_outcome:established,contradicted, ornot-established, evaluated separately under observer trust, freshness, independence policy, and the transition predicate.With that split, an empty or unrelated
afterobject should not receive a positive successor/outcome claim. At most, a valid object can be digest-bound. Whether it establishes a transition is a separate sufficiency question.This also avoids a PIC interoperability problem: the corpus should treat
PIC-CJSON/1.0,intent_digest, andargs_digestas upstream PIC-defined verifier outputs, not redefine their semantics locally inside TRACE. In current PIC,args_digestis SHA-256 over PIC-CJSON canonical bytes ofproposal.action.args, whileintent_digestis SHA-256 over the raw UTF-8 bytes ofproposal.intent.I can help with the PIC-facing part without taking over the accepted #206 work: either review the fixture contract for the PIC fields, or send a small PR adding a PIC-source reference and a short note that the bridge consumes PIC verifier outputs rather than defining them.
coreytshaffer commentedon Sep 16, 2026
Case 003 reconciliation is now prepared and independently reviewed against the September 15 PIC maintainer decision.
Scope is intentionally narrow: only case 003 and its direct counterexamples are reconciled. The old
transcript_bound: truepositive credit remains withdrawn, the frozen conformance denominator remains 100, rows 101/102 remain diagnostics, and full-corpus certification remains held.The review patch separates the three axes discussed above:
pic_authorization_boundsuccessor_envelope_boundsuccessor_outcome(established/contradicted/not-established)The bounded vectors now cover:
after: {}-> exacttranscript_after_invalidrefusal, no outcome evaluationVerification after a packaging-only LF normalization:
Reviewed artifact hashes:
c7de8b3621aea7d85155a736005f6ed5fd7558d89176aed603ea8dfe439892bf49c5bd677da1e620315374eed41f4e349aa52c96fad1d676b48bba254e1124cb4d21ff8fd13f051a42b9a4963dd5af5eb270c8ced6d1248571c1a70ac743dd29The patch was prepared against TRACE
mainatcd92e16dde6658eedbe1058c903f3fce92bb3a58and does not claim wider corpus reconciliation or restored certification.I’m ready to put this into a reviewable PR next. My current GitHub integration has read-only repository access, so I can’t publish the branch from this session; if a maintainer has a preferred fork/branch path, I’ll use that exact route rather than broadening the change.
imran-siddique commentedon Sep 18, 2026
My hold of 7 September on case 003 is lifted: the PIC maintainer ruled on 15 September, and #363 carries the three-axis split that ruling set out. What remains is the review on #363, not the hold.