Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 58 additions & 4 deletions .github/workflows/aeoess-aps-conformance.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
# Agent Passport System TRACE exporter conformance workflow.
# Agent Passport System TRACE exporter workflow.
#
# Two jobs answering two questions (#169, #170):
# floating installs the latest released agentrust-io packages, unpinned on
# purpose, as drift detection ("harness gets pinned, subject does not").
# fixed installs exactly the versions named in integration.yaml
# tested_against, so that claim cannot move underneath itself.
# Neither job asserts a TRACE conformance level. This integration is an
# external-evidence-source; trace-tests --level 0 runs as a coverage report.
# Lives at repo root because GitHub Actions only discovers workflows there.
# Scoped to this integration via the paths filter.
name: aeoess-aps conformance
Expand All @@ -19,7 +27,8 @@ permissions:
contents: read

jobs:
conformance:
floating:
name: floating (latest releases, drift detection)
strategy:
fail-fast: false
matrix:
Expand All @@ -40,11 +49,56 @@ jobs:
run: pytest integrations/aeoess-aps/tests -q
- name: Emit a sample TRACE record
run: python integrations/aeoess-aps/examples/emit_record.py --out trust-record.jwt
- name: TRACE conformance level 0
- name: trace-tests level 0 (coverage report, not a conformance claim)
run: trace-tests verify --record trust-record.jwt --level 0
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: conformance-${{ matrix.os }}-py${{ matrix.python }}
name: floating-${{ matrix.os }}-py${{ matrix.python }}
path: |
trust-record.jwt
trust-record.jwt.signed.json

fixed:
name: fixed (tested_against versions)
strategy:
fail-fast: false
matrix:
python: ["3.11", "3.12", "3.13", "3.14"]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python }}
- name: Install the package set that reproduces the tested_against result
# integration.yaml records agentrust-trace and agentrust-trace-tests, the
# keys its schema supports; agent-passport-system is pinned here as part
# of the same reproducible snapshot.
run: |
pip install "agentrust-trace==0.10.0" "agentrust-trace-tests==0.5.1" "agent-passport-system==3.0.1"
- name: Pinned versions match integration.yaml
run: |
python - <<'PY'
import re, sys
from importlib.metadata import version
y = open("integrations/aeoess-aps/integration.yaml").read()
want = dict(re.findall(r'^ (agentrust-trace|agentrust-trace-tests): "([^"]+)"', y, re.M))
have = {k: version(k) for k in want}
print(want, have)
sys.exit(0 if want == have else 1)
PY
- name: Install this integration
run: pip install -e "integrations/aeoess-aps[test]"
- name: Integration tests
run: pytest integrations/aeoess-aps/tests -q
- name: Emit a sample record
run: python integrations/aeoess-aps/examples/emit_record.py --out trust-record.jwt
- name: trace-tests level 0 (coverage report, not a conformance claim)
run: trace-tests verify --record trust-record.jwt --level 0
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: fixed-${{ matrix.os }}-py${{ matrix.python }}
path: |
trust-record.jwt
trust-record.jwt.signed.json
51 changes: 33 additions & 18 deletions integrations/aeoess-aps/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,11 @@ One observed divergence, kept as tested against 0.9.0: an empty `RevocationStore
accepts and an omitted store skips the check, while APS treats "no artifacts observed"
as no evidence rather than as not revoked. TRACE's own section 3.2.3 takes the APS
position at the bundle level (a verifier with no bundle reports that it performed no
revocation check). The behaviour observed at `tested_against: 0.9.0` is retained here as a tested
revocation check). The behaviour observed at agentrust-trace 0.9.0 is retained here as a tested
implementation result; agentrust-io/trace-spec#246 records it as inconsistent with
section 3.2.3.
section 3.2.3. It cannot be re-observed on this exporter's record at 0.10.0, where
`verify_record` refuses the record on schema grounds before any revocation check
runs, so these rows are 0.9.0 results and are not restated for 0.10.0.

The APS artifacts examined come from aeoess/agent-passport-system#123 (revocation
verification corpus). Mapping questions and answers: agentrust-io/integrations#140.
Expand Down Expand Up @@ -138,17 +140,22 @@ convention here. The verdict is still carried, as `policy.enforcement_mode` and

See rules 2 and 4 in [CONTRIBUTING.md](../../CONTRIBUTING.md).

- **The record is a partial TRACE record.** `model`, `data_class` and
`build_provenance` are required by the v0.2 JSON Schema and are absent. An APS
policy decision carries no model identity, no data classification and no build
provenance, so any value there would be invented. The record passes
`trace-tests verify --level 0`, which does not grade those fields, and it does
not satisfy the full v0.2 schema. `tests/test_mapping.py` pins the exact set of
absent required fields so the gap cannot widen silently.
- **Level 0 carries an explicit `TR-SIG-005 UNVERIFIED` finding.** The graded
artifact is the unsigned record, so trace-tests reports that it is not
cryptographically verified. The signed form is written next to it as
`<out>.signed.json` and verifies with `agentrust_trace.verify_record`.
- **This integration is an `external-evidence-source`, not a `record-producer`,
and claims no conformance level.** An APS decision is signed external
evidence. The mapper places it into a TRACE record but cannot populate
`model`, `data_class` or `build_provenance`, which the v0.2 JSON Schema
requires, without inventing them. So the emitted record is not a TRACE Trust
Record, and from `agentrust-trace` 0.10.0 `verify_record` refuses it on
schema grounds. `tests/test_mapping.py` pins both the exact set of absent
required fields and that refusal, so neither can change silently. The same
role, for the same reason, is used by `nobulex`.
- **`trace-tests verify --level 0` is a coverage report here, not a claim.** It
reports 8 checks with an explicit `TR-SIG-005 UNVERIFIED` finding because the
graded artifact is the unsigned record. A clean Level 0 run means nothing the
suite could check went wrong. It does not mean the record verifies.
- **The signed form `<out>.signed.json` demonstrates the sign path only.** Its
signature is valid over the emitted claims, and `verify_record` still refuses
it, because signature validity is not schema validity.
- **`runtime.platform` is `software-only` and there is no hardware attestation.**
`runtime.measurement` is a digest of the signed APS decision, not a TEE
measurement. Per the v0.2 schema, `software-only` records must never be treated
Expand All @@ -157,11 +164,19 @@ See rules 2 and 4 in [CONTRIBUTING.md](../../CONTRIBUTING.md).
does not chain to a trusted issuer.
- **`transparency` is `urn:aps:transparency:none`.** This integration publishes
nothing to a transparency log, so there is no SCITT receipt to resolve.
- **No conformance level above 0 is claimed or configured.**
- **No conformance level is claimed or configured.**

## Conformance CI
## CI

`.github/workflows/aeoess-aps-conformance.yml` at the repository root, scoped to
`integrations/aeoess-aps/**`. It installs the released packages, runs the tests,
emits a record and runs `trace-tests verify --level 0` across Python 3.11 to
3.14.
`integrations/aeoess-aps/**`, runs two jobs across Python 3.11 to 3.14:

- **floating** installs the latest released packages, unpinned on purpose, as
drift detection. This is the "harness gets pinned, subject does not" rule
from #169.
- **fixed** installs exactly the versions named in `integration.yaml`
`tested_against`, checks that the installed versions match that file, and
runs the same steps. This is what backs the `tested_against` claim.

Both run the tests, emit a record, and run `trace-tests verify --level 0` as a
coverage report.
20 changes: 12 additions & 8 deletions integrations/aeoess-aps/aps_trace.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""aps_trace: export one signed APS policy decision as a TRACE Trust Record.
"""aps_trace: map one signed APS policy decision, as external evidence, into TRACE record shape.

The Agent Passport System (APS) evaluates an ActionIntent against a Values
Floor and returns a PolicyDecision: a dict signed by the evaluator, carrying a
verdict of ``permit``, ``narrow`` or ``deny``. This module maps exactly one
such decision onto one TRACE Trust Record dict (EAT profile
``tag:agentrust-io.com,2026:trace-v0.2``).
such decision onto one TRACE-shaped mapping output (EAT profile
``tag:agentrust-io.com,2026:trace-v0.2``). The output is not a TRACE Trust
Record: this integration is an ``external-evidence-source`` and claims no
conformance level (agentrust-io/integrations#170).

Two different signatures are involved and they are never the same key:

Expand Down Expand Up @@ -49,8 +51,9 @@
``model``, ``data_class`` and ``build_provenance`` are required by the TRACE
v0.2 JSON Schema and are absent here. An APS policy decision carries no model
identity, no data classification and no build provenance, so any value would be
invented. The record is therefore a partial TRACE record: it passes
``trace-tests verify --level 0``, and it does not satisfy the full v0.2 schema.
invented. The mapping output therefore does not satisfy the v0.2 Trust Record
schema and ``agentrust_trace.verify_record`` refuses it. ``trace-tests verify
--level 0`` runs on it as a coverage probe, not as a conformance claim.
``tests/test_mapping.py`` pins the exact set of absent required fields so the
gap stays deliberate. See the README section "What it does NOT claim".

Expand Down Expand Up @@ -111,7 +114,7 @@


def build_trace_record(decision: dict[str, Any], *, trace_jwk: dict[str, str]) -> dict[str, Any]:
"""Map one signed APS policy decision onto an unsigned TRACE Trust Record.
"""Map one signed APS policy decision onto an unsigned TRACE-shaped mapping output.

Args:
decision: The dict returned by ``agent_passport.policy.evaluate_intent``,
Expand All @@ -121,7 +124,8 @@ def build_trace_record(decision: dict[str, Any], *, trace_jwk: dict[str, str]) -
the APS evaluator key.

Returns:
An unsigned TRACE Trust Record dict.
An unsigned dict in TRACE v0.2 record shape. It omits ``model``,
``data_class`` and ``build_provenance`` and is not a TRACE Trust Record.

Raises:
ValueError: if the decision is malformed, its evaluator signature does
Expand Down Expand Up @@ -175,7 +179,7 @@ def build_trace_record(decision: dict[str, Any], *, trace_jwk: dict[str, str]) -


def _validate_decision(decision: dict[str, Any]) -> None:
"""Refuse anything that must not become a TRACE Trust Record.
"""Refuse anything that must not be mapped into TRACE record shape.

Checks shape, then the evaluator signature and expiry through
``verify_policy_decision``, then the verdict. Every rejection raises
Expand Down
18 changes: 9 additions & 9 deletions integrations/aeoess-aps/examples/emit_record.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#!/usr/bin/env python3
"""Emit a TRACE Trust Record from a freshly evaluated APS policy decision.
"""Emit a TRACE-shaped mapping output from a freshly evaluated APS policy decision.

Runs the real APS path with ephemeral keys: an agent declares an ActionIntent,
an evaluator evaluates it against the Values Floor with FloorValidatorV1, and
the resulting signed PolicyDecision is mapped onto a TRACE Trust Record by
the resulting signed PolicyDecision is mapped into TRACE record shape by
:func:`aps_trace.build_trace_record`. No network access and no credentials.

The decision is minted at run time rather than loaded from a committed fixture
Expand All @@ -19,8 +19,8 @@
that function enforces the full v0.2 schema, and this
record deliberately omits ``model``, ``data_class`` and
``build_provenance`` (see "Deliberately absent" in
aps_trace.py). ``trace-tests verify --level 0`` is the
gate this integration targets, and it passes.
aps_trace.py). ``trace-tests verify --level 0`` runs on
it as a coverage probe. No conformance level is claimed.

Usage:
python examples/emit_record.py --out trust-record.jwt
Expand Down Expand Up @@ -96,10 +96,10 @@ def main() -> int:
signed = agentrust_trace.sign_record(dict(record), key)

# Deliberately not calling verify_record here. Since agentrust-trace 0.10.0
# it enforces the full v0.2 schema, and this record is a documented partial:
# asking for a verdict it cannot honestly give would either fail the example
# or push someone to invent a model identity. Level 0 is the claim, and the
# conformance job runs it on the unsigned artifact immediately after this.
# it enforces the full v0.2 schema, and this mapping output is documented as
# schema-incomplete: asking for a verdict it cannot honestly give would either
# fail the example or push someone to invent a model identity. Level 0 is a
# coverage probe, and CI runs it on the unsigned artifact right after this.

out = Path(args.out)
out.write_text(
Expand All @@ -116,7 +116,7 @@ def main() -> int:
print(f"appraisal.status: {record['appraisal']['status']}")
print(f"subject: {record['subject']}")
print(f"unsigned (for trace-tests): {out}")
print(f"signed (signature only, partial record): {signed_out}")
print(f"signed (signature only, not a Trust Record): {signed_out}")
return 0


Expand Down
36 changes: 25 additions & 11 deletions integrations/aeoess-aps/integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,30 @@ marketplace:
category: Identity & trust
mark: AP
keywords: [passport, identity, policy]
# Level 0 passes with an explicit TR-SIG-005 UNVERIFIED finding: the graded
# artifact is the unsigned record, so trace-tests reports it as not
# cryptographically verified. The signed form is written alongside it and
# verifies via agentrust_trace.verify_record (see README).
trace_conformance_level: 0
# Role. APS decisions are Ed25519-signed external evidence. This integration
# maps one onto a TRACE record but cannot populate `model`, `data_class` or
# `build_provenance` without inventing them, so the output does not validate
# against the v0.2 schema and is not a TRACE Trust Record. Same case as
# nobulex: external-evidence-source, no conformance level (#170).
trace_roles:
- record-producer
- external-evidence-source
tested_against:
agentrust-trace: "0.9.0"
# trace-spec#236 (merged 2026-08-29, unreleased) changes the RevocationStore path:
# a callable returning a non-bool now fails closed instead of reading as not
# revoked. The revocation observations in README were made against 0.9.0 and
# are re-run on the release that carries #236.
agentrust-trace: "0.10.0"
agentrust-trace-tests: "0.5.1"
# This file records only the package keys its schema supports. The fixed CI
# job pins the complete package set needed to reproduce this result, which
# also includes agent-passport-system 3.0.1.
# Position at these versions: the emitted record carries every field the
# mapper can source from an APS decision and validates field by field against
# the v0.2 JSON Schema, but `model`, `data_class` and `build_provenance` are
# absent, so `agentrust_trace.verify_record` refuses it (pinned by
# tests/test_mapping.py::test_verify_record_refuses_schema_incomplete_mapping).
# `trace-tests verify --level 0` reports 8 checks with TR-SIG-005 UNVERIFIED;
# that is a coverage result, not a conformance claim.
# These versions are the fixed run in .github/workflows/aeoess-aps-conformance.yml.
# The floating run installs unpinned releases as drift detection (#169).
# The revocation observations in README were made against 0.9.0 and stand as
# 0.9.0 results. They cannot be re-run on this exporter's record at 0.10.0:
# verify_record refuses it on schema grounds before any revocation check runs
# (observed with an empty store, a callable returning a non-bool, and a
# callable returning True; all three raise the schema error first).
29 changes: 21 additions & 8 deletions integrations/aeoess-aps/tests/test_mapping.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""APS to TRACE mapping tests.

Covers the mapping itself, the refusals that keep an unverified APS decision
from becoming a TRACE record, and a level 0 conformance run against the emitted
record. Decisions are minted in-process with ephemeral keys, so no network
from being mapped into TRACE record shape, and a level 0 coverage run against
the mapping output. Decisions are minted in-process with ephemeral keys, so no network
access, no credentials and no committed fixtures are involved.

A committed decision fixture is impossible here on purpose: APS decisions
Expand Down Expand Up @@ -248,10 +248,10 @@ def test_non_dict_is_refused():

# --- TRACE sign and verify round-trip ---------------------------------------

def test_signed_record_passes_trace_tests_level_0(record):
"""Signing works and the signature checks out, on a deliberately partial record.
def test_signed_mapping_has_no_level0_findings(record):
"""Signing works and the signature checks out, on a schema-incomplete mapping output.

Level 0 is this integration's gate, as the README says. The unsigned record
Level 0 is a coverage probe here, not a conformance gate. The unsigned output
leaves TR-SIG-005 UNVERIFIED; once signed, nothing should be unverified.
"""
runner = pytest.importorskip("trace_tests.runner")
Expand All @@ -268,7 +268,7 @@ def test_signed_record_passes_trace_tests_level_0(record):
assert [f.code for f in findings if f.status is Status.UNVERIFIED] == []


def test_full_verify_record_refuses_a_partial_record(record):
def test_verify_record_refuses_schema_incomplete_mapping(record):
"""The boundary this integration lives on, asserted rather than assumed.

agentrust-trace 0.10.0 made ``verify_record`` enforce the full v0.2 schema,
Expand Down Expand Up @@ -330,8 +330,8 @@ def test_present_fields_all_validate_against_the_v02_schema(record):
assert missing == DOCUMENTED_ABSENT_REQUIRED


def test_record_passes_trace_tests_level_0(record):
"""Level 0 must pass, with TR-SIG-005 UNVERIFIED on the unsigned record."""
def test_mapping_level0_coverage_result(record):
"""Level 0 coverage result: 8 checks, TR-SIG-005 UNVERIFIED on the unsigned output."""
runner = pytest.importorskip("trace_tests.runner")
from trace_tests.result import Status

Expand All @@ -341,3 +341,16 @@ def test_record_passes_trace_tests_level_0(record):
assert [f.code for f in findings if f.status is Status.FAIL] == []
unverified = [f for f in findings if f.status is Status.UNVERIFIED]
assert [f.code for f in unverified] == ["TR-SIG-005"]


def test_integration_metadata_claims_no_level():
"""#170: external-evidence-source, and no conformance level anywhere in the manifest."""
import pathlib
import re
text = pathlib.Path(__file__).resolve().parents[1].joinpath("integration.yaml").read_text()
# Parsed with re rather than a YAML library so the test extras stay as they are.
body = "\n".join(line for line in text.splitlines() if not line.lstrip().startswith("#"))
roles = re.search(r"^trace_roles:\n((?: - .*\n?)+)", body, re.M)
assert roles is not None
assert [r.strip()[2:] for r in roles.group(1).strip().splitlines()] == ["external-evidence-source"]
assert re.search(r"^trace_conformance_level:", body, re.M) is None
Loading
Loading