integrations/aeoess-aps: add APS PolicyDecision to TRACE Level 0 mapping - #86
Conversation
Maps exactly one signed APS policy decision, the dict returned by agent_passport.policy.evaluate_intent, onto exactly one TRACE Trust Record at EAT profile tag:agentrust-io.com,2026:trace-v0.2. The mapper verifies the APS evaluator signature and expiry through verify_policy_decision before it maps anything, and raises rather than emit a record for a decision that fails either check. The APS evaluator signature and the TRACE record signature are separate keys and the module never conflates them. Conformance level 0. The graded artifact is the unsigned record, so TR-SIG-005 reports UNVERIFIED; the signed form is written alongside it and verifies with agentrust_trace.verify_record. model, data_class and build_provenance are absent because an APS policy decision carries none of them, and a test pins that set so it cannot widen silently.
|
🔴 Contributor Check: HIGH
Automated check by AgenTrust Contributor Check. |
…rings CodeQL flagged the startswith check as incomplete URL substring sanitization. The assertion now builds the expected spiffe URI from TRUST_DOMAIN and the quoted evaluator and decision ids and compares for equality, which also pins the path structure and the percent-encoding that the previous three assertions did not.
imran-siddique
left a comment
There was a problem hiding this comment.
Reviewed the adapter, the tests, and the conformance workflow. This is the shape a Community-tier integration should have.
What makes it acceptable:
- The APS evaluator signature and expiry are verified before any mapping happens, and a decision that fails either raises instead of producing a record. That is the important property: an unverified decision cannot become a record that looks appraised.
- The two signatures are kept clearly separate, and the module never applies the TRACE signature itself.
- The gap is declared rather than papered over.
model,data_classandbuild_provenanceare required by the v0.2 schema and absent here because an APS decision carries none of them, andtests/test_mapping.pypins the exact absent set so the gap cannot drift silently.integration.yamlstates Level 0 and the TR-SIG-005 UNVERIFIED finding openly. narrowmapping to EARwarningis a defensible reading, since TRACE has nonarrowandwarningis the status for an appraisal that affirms with reservations.- Versions are pinned and the upstream commit is named.
One housekeeping item: the CodeQL alert on this PR (py/incomplete-url-substring-sanitization, alert 85, tests/test_mapping.py:106) is a false positive. Line 106 builds an expected SPIFFE URI for an exact-equality assertion, so there is no substring sanitization to be incomplete. No code change needed. It should be dismissed as a false positive so the repo alert list stays meaningful.
Approving. Leaving the merge to Imran.
|
Thanks for the careful review. Small note on alert 85: it was raised against So the alert was resolved by the follow-up commit rather than needing a false-positive dismissal. Appreciate the approval. |
… pieces of work that were never entered Acting on the roadmap restructure report. Part 1, the lane consolidation, already landed at 500e880. This is Parts 2 and 3. IDENTIFIERS ADDED TO EXISTING CARDS Five cards described real work without naming the thing a reader could go and check. They now carry it: the goose rerun as aaif-goose/goose#10433 with its timestamp and the 76-commit, 3-sign-off history; the exporter as agentrust-io/integrations#86 with the boundary merge 83c3cb3 on pinned head d647cfa2; the bernstein merge as PR #2994 at 3f8d75a6 with the key-order finding filed as #3105 and the exact code-point ranges where the divergence occurs; the suite tag as v0.1.0 at 4b9dbb0; and the spend-unit guard with its independent verification, 3817 passing. The prose already there was kept and the identifiers appended, rather than swapping in shorter rewrites, because the existing descriptions were not wrong, only unverifiable. SIX CARDS FOR WORK THAT HAPPENED AND WAS NEVER RECORDED Day 144: the sprint reaching main as PR #65 at 1edad8c with the post-merge suite state; the Go implementation reproducing the TypeScript action_ref vectors 4 of 4 byte-identical at loader commit 501e5da; and the decision not to add a ConstraintFacet because 15 is a number already on the public record. Day 154: the from-scratch COSE and Merkle verifier run against the SCITT vectors at tag vectors-ietf126, including the vector it declines to reconstruct and records as unsupported instead. Day 18: the four-run role-separation experiment, carrying both limits, that the scoring is internal and that n is one per condition. Day 57: the contribution map, which names no participant, no employer and no city. Every number is quoted from the file it came from. Nothing was written from memory. FOUR RECOMMENDATIONS FROM THE REPORT DELIBERATELY NOT TAKEN The report proposed merging the two Day 123 cards as one build. The archive says Day 123 shipped BUILD 1 as spend-unit narrowing and BUILD 2 as verification_mode, so the temporal narrowing card is different work and stays separate. The report proposed a Day 157 rewrite saying a contract would follow. Day 170 resolved that no agreement applies to a lab, so that rewrite would have introduced a false claim. The live card already carries the correct framing. The report proposed a Day 170 rewrite naming two assertions on a thread as false against the record. That is a public contradiction of a person on a page we control, so it stays out. The report proposed a card for a pull request reverification. The handoff shows that work as a push to our own fork rather than the read-only review the card described, so it is dropped rather than published with a provenance that does not hold. 446 items, all ids unique, every item carrying its six required keys, YAML parses, zero em or en dashes, and the roadmap renders.
Relates to #46.
What this adds
A Community-tier integration mapping one signed APS policy decision onto a TRACE v0.2 Trust Record.
The scope is deliberately narrow:
The adapter verifies the APS decision signature and expiry before mapping, and refuses a decision that fails either. The example writes an unsigned record for the Level 0 runner and a signed sidecar verified with
agentrust_trace.verify_record.Versions
Built from upstream
4bcb30bbaec8f88fc232bc9e710e5edae453b25f. Tested withagent-passport-system==2.10.0,agentrust-trace==0.5.1,agentrust-trace-tests==0.4.1, profiletag:agentrust-io.com,2026:trace-v0.2.Conformance result
Integration tests:
26 passed.Reproduction
python -m pip install --upgrade pip pip install agentrust-trace agentrust-trace-tests agent-passport-system pip install -e "integrations/aeoess-aps[test]" pytest integrations/aeoess-aps/tests -q python integrations/aeoess-aps/examples/emit_record.py --out trust-record.jwt trace-tests verify --record trust-record.jwt --level 0The example also writes
trust-record.jwt.signed.json, which verifies throughagentrust_trace.verify_record.Build findings
Two released-package details differed from what was recorded in #46.
agentrust-trace==0.5.1does not expose arevocation=argument onverify_record. The signature is(record, public_key_or_jwk=None, *, allow_embedded_key=False, max_age_seconds=86400, expected_nonce=None). Revocation is out of scope here regardless, but the follow-up will need a different hook.agentrust-trace-tests==0.4.1accepts a plain record carrying a top-level signature and verifies it.loader.pyallows a baresignaturekey explicitly, and running the signed sidecar through Level 0 returns a verified TR-SIG result rather than UNVERIFIED. This PR still grades the unsigned record and keeps the signed form beside it, matching the current merged integration pattern inramen-ai-cmcpandspendguard. The signed run was a diagnostic only and changed nothing in the submission. Happy to switch which artifact is graded if you would rather the catalogue showed the stronger result.Deliberately absent fields
model,data_classandbuild_provenanceare required by the v0.2 schema and are absent. An APS policy decision carries no model identity, no data classification and no build provenance, and inventing values would be worse than the gap.tests/test_mapping.pypins the exact absent set so it cannot widen silently.What this does not claim
runtime.platformissoftware-only, which is not hardware or runtime attestationThose need separate mappings or upstream work.