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
39 changes: 36 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,55 @@ The section also records a correspondence worth more than the identity one: OCSF

**[SPEC] Section 9.1.2 maps EU AI Act Article 50, and maps it as a gap.** Article 50 transparency duties have applied since 2 August 2026: chatbot disclosure, machine-readable marking of synthetic output, emotion-recognition notice. It is the only AI Act obligation in force against an agent deployment today, and no document in this repository mentioned it. The manifest satisfies none of the four paragraphs, so the new subsection says that in those words, notes that Article 50 applies regardless of Annex III classification, and states that a manifest MUST NOT be read as Article 50 evidence. `docs/compliance/eu-ai-act.md` carries the same mapping for auditors. Marking that binds to the attested agent that produced the output, rather than to a strippable metadata field, is the design being pursued; it is not built.

**[SDK] COSE_Sign1 signing and verification for manifest version 0.2** ([`_cose.py`](python/src/agent_manifest/_cose.py), [ADR-0011](docs/adr/0011-signature-envelope.md), [ADR-0013](docs/adr/0013-cbor-library-for-cose.md)). Phase 2 of the envelope migration (issue #243). `verify_manifest()` now takes either a dict or bytes: **a dict is a v0.1 manifest and verifies exactly as it does today**, bytes are a v0.2 COSE envelope. The envelope follows the manifest `version`, never a flag, so no existing record is reinterpreted.

### Fixed

**[SPEC] The README pointed at a specification file that does not exist.** Three links and the spec badge referenced `spec/agent-manifest-spec-v0.1.md`, superseded by v0.2. All four now resolve.

### Changed

**Two documentation tables described a conformance ladder the specification does not define.** `docs/compliance/eu-ai-act.md` listed the levels as `0 software / 1 TPM / 2 SEV-SNP or TDX / 3 managed TEE`, and the hardware-attestation tutorial mapped providers to levels the same way. Section 8.1 defines the levels by what is bound and attested, not by silicon: any TEE provider satisfies Level 1, Level 2 is all ten artifacts plus HITL, delegation, Phase 2 cMCP and retention, and Level 3 is the post-quantum profile. As written, a reader on a TPM host would have concluded they could not reach Level 2 without changing hardware, and the tutorial's own guidance table put EU AI Act Art. 15 at Level 2 where the spec puts it at Level 1. Both tables now match the spec, with the differences between roots of trust stated as what a verifier can conclude rather than as a level. `AzureCVMProvider` is added to both, since it ships and was in neither.
What the envelope buys is structural rather than incremental. `alg` is in the protected header and covered by the signature, so the downgrade fixed in 0.6.0 by an explicit cross-check cannot be expressed at all. The payload is verified as received, so RFC 8785 is no longer an input to verification — it stays the producer-side determinism rule and the basis of the hash bound into hardware. Receipts (label 394), the TEE attestation report, and HITL approvals attach in the unprotected header, which retires the `signed_fields` coverage table, the `hitl_record.approvals` normalization rule, and the `transparency_log_entry` ordering rule together. Hardware now binds `sha256` of the payload bytes, with no field subset to keep in sync.

Hybrid is one `COSE_Sign` with two signers rather than two `COSE_Sign1` objects, so both signatures covering identical payload bytes is a property of the structure. A verifier that cannot perform ML-DSA-65 returns `UNVERIFIABLE` and never falls back to the classical entry.

**[SDK] New runtime dependency: `cbor2`.** Serialization only. The COSE structures are built in this repository and every signature stays on `cryptography` (and optionally `pyoqs`), so the SDK's crypto surface is unchanged. [ADR-0013](docs/adr/0013-cbor-library-for-cose.md) records why no COSE library was taken: neither `pycose` nor `cwt` ships the RFC 9964 code points that half this envelope needs, and both widen the dependency closure to do less.

### Fixed

**[SDK] The `pq` extra was uninstallable, and the module it imported is not the package it meant.** `pip install "agent-manifest[pq]"` required `pyoqs`, which is not published on PyPI, so the post-quantum profile could not be installed by following the documented instruction. Worse, the module name the SDK imports — `oqs` — belongs to an unrelated project on PyPI, and `_signing` treated a successful import as proof of liboqs. Installing that package turned every ML-DSA-65 call into an `AttributeError` (18 test failures) and made the SDK report a post-quantum capability it did not have.

**ML-DSA-65 now comes from `cryptography`**, which implements it through OpenSSL as of 47.0.0 and is already a required dependency. The `pq` extra is now `cryptography>=47` and installs cleanly. Deployments already carrying the liboqs bindings keep working: the backend is chosen by the key material, not by configuration, because the two differ in private key encoding — cryptography uses the 32-byte seed, liboqs the expanded secret key. **Public keys are the same 1952-byte encoding in both**, so `key_id`, the COSE `kid`, and every signature a third party verifies are unchanged and interoperable across backends. A build with neither backend still reports `UNVERIFIABLE` rather than accusing the manifest, unchanged from #245.

The capability check no longer trusts a module name: liboqs is now identified by its API. Thirteen post-quantum tests that skipped on every machine without liboqs — across signing, hybrid mode, evidence packs, and the COSE envelope — now execute against real FIPS 204 signatures.

### Changed

**[SPEC] EU AI Act mappings now carry the date each obligation applies from.** The mappings were written as if the high-risk obligations bind today. Under the current provisional timeline the Digital Omnibus amendments defer Annex III systems to around 2 December 2027 and Annex I to around August 2028, which `docs/compliance/eu-ai-act.md` already recorded and nothing else did. The mappings are correct and unchanged; only the applicability date is added, in section 9.1 (authoritative note), the section 8.1 conformance-level table, the section 8.1 log-retention requirement, `LIMITATIONS.md`, the HITL tutorial and example, `docs/index.md`, `docs/getting-started.md`, and the hardware-attestation tutorial. Where a deployment has an obligation in force today, the docs now lead with it: DORA for financial entities, HIPAA § 164.308(a)(5) for the HITL record.

**[SPEC][SDK] Ed25519 is identified by `-19`, not the deprecated `EdDSA` `-8`** ([ADR-0014](docs/adr/0014-fully-specified-ed25519-code-point.md)). **RFC 9864 (Standards Track, October 2025) deprecated the polymorphic `EdDSA` identifier** and registered fully-specified ones; the IANA COSE Algorithms registry marks `-8` deprecated. Issue #243's "code points are settled: EdDSA -8" was therefore already out of date when phase 1 was written — the ML-DSA-65 half (`-49`) was confirmed correct against IANA. A producer now signs with `-19`; a verifier accepts both and **keeps accepting `-8` indefinitely**, because manifests are audit records with regulated retention and a signature cannot be re-issued under a new identifier without re-signing.

`-8` and `-19` name one algorithm, so anything reasoning about *which* algorithm signed compares algorithms rather than code points: a `post-quantum` profile is satisfied by neither, and a `COSE_Sign` carrying one entry of each is rejected as a single algorithm signed twice rather than accepted as a hybrid signature.

The cost is recorded in the ADR rather than glossed: **no COSE library implements RFC 9864 yet**, so a `-19` manifest cannot currently be verified by third-party tooling — the interop check runs against `-8` fixtures over the same structures for as long as that holds.

**[SDK] `AM-VEC-COSE-001` pins the COSE_Sign1 encoding byte-for-byte.** A vector now carries either `manifest` (v0.1) or `envelope_hex` (v0.2 COSE), and the same conformance loop runs both, since the engine selects the procedure from what it is handed. The vector fixes every element — tag, protected header, `unprotected_hex` of `a0`, payload, signature, and the payload hash hardware binds — so another language's SDK has something to agree with rather than only its own round-trip. Ed25519 only: ML-DSA-65 signing is hedged, so post-quantum envelopes differ per run and only their structure is stable.

**[SDK] A version 0.2 manifest may not use the v0.1 envelope.** The version gate now binds in both directions: a manifest declaring `version: "0.2"` while carrying a detached `signature` block is rejected rather than verified under v0.1 rules. `signature` is not a v0.2 field at all, since the COSE structure is the signature, so such a document claims the new version while using the envelope with the unauthenticated algorithm identifier and the canonicalize-before-verify step that ADR-0011 moved away from. Accepting it would have made the gate advisory and left the phase 5 deprecation with nothing to enforce. Version 0.1 manifests are unaffected.

**[SDK] `manifest sign` and `manifest verify` handle version 0.2.** `sign` selects the envelope from the manifest's `version` field, so a `0.2` manifest is written as a COSE envelope in binary CBOR and a `0.1` manifest is unchanged; there is no flag, consistent with ADR-0011. `verify` detects the envelope from the CBOR tag rather than the file extension, since guessing a format by filename is the ambiguity the media-type rules exist to remove. Signing a `0.2` manifest requires `--output`, because binary CBOR down stdout would be corrupted by the terminal. New reference page: [COSE envelope](docs/api-reference/cose.md).

**[SDK] `POST /verify/cose` accepts a version 0.2 COSE manifest as raw CBOR.** The body is the `COSE_Sign1`/`COSE_Sign` object itself under `Content-Type: application/agent-manifest+cose` — the registered media type is the gate, rather than base64 inside a JSON wrapper, since the object is self-contained and the type exists to identify it. Three properties are deliberate: **only the exact media type is accepted** (a vendor-tree alias, `application/cbor`, and an absent type are all refused — the server never sniffs the body); **no key material crosses the wire**, so trust comes from a `cose_context` configured server-side when the router is built, and an unconfigured endpoint returns `UNVERIFIABLE` rather than `VALID`; and **the body is bounded before it is parsed**, with `Content-Length` checked when present and the stream capped regardless, because a declared length is attacker-controlled. A malformed or unverifiable envelope is a verdict (200 with a non-`VALID` result), not a transport error, and parser detail is never reflected back, so the endpoint cannot be used as an oracle for the decoder. Results carry `Cache-Control: no-store` and `X-Content-Type-Options: nosniff`. Authentication, authorization and rate limiting remain deployment concerns (spec 5.1: mTLS with the agent's SPIFFE SVID).

**[SPEC] `version` MUST be `"0.2"`, resolving a contradiction inside the specification.** Section 2.4's compatibility matrix already planned for `0.2` manifests while the section 3 field table still required `"0.1"`, so a producer following the specification could never emit a manifest the COSE envelope governs. The field table now says `"0.2"`; `"0.1"` continues to identify a v0.1 manifest, which stays verifiable. The `@context` change had described v0.2 as differing from v0.1 "in the `@context` value alone" — accurate for that change in isolation, but v0.2 as a release also carries the COSE envelope, and [ADR-0012](docs/adr/0012-context-uri-moved-to-controlled-domain.md) is amended with a note saying so.

**[SDK] `0.2` is now a supported manifest version.** `AM-VEC-007`, which used `0.2` as its stand-in for a version from the future, now uses `0.3`. Its expected result is unchanged.

**[SPEC][SDK] BREAKING: the `@context` URI moves to `https://manifest.agentrust-io.com/v0.2/context.json`**, and the specification is republished as v0.2 ([`spec/agent-manifest-spec-v0.2.md`](spec/agent-manifest-spec-v0.2.md), [ADR-0012](docs/adr/0012-context-uri-moved-to-controlled-domain.md)). The v0.1 URI `https://agentmanifest.agentrust.io/v0.1/context.json` named `agentrust.io`, a domain this project has never controlled: registered to a third party behind Domains By Proxy, paid through mid-2027, and it has never resolved. Every manifest issued to date was therefore identified under somebody else's name, which is untenable in an identity specification.

Consumers **cut over rather than dual-accepting**, matching the TRACE v0.2 profile migration (`agentrust-io/trace-spec#107`) that fixed the identical defect. An implementation that kept honouring the v0.1 URI would keep validating manifests named on a domain we do not own. Manifests already issued under v0.1 stay checkable against the v0.1 specification, which remains published.

**Nothing else about the manifest format changed.** No field is added, removed or re-typed; v0.2 differs from v0.1 in the `@context` value alone. The version bump exists to force the cut-over. Serving the context document at the new URL is follow-up work; what changes today is that the domain is ours to serve from.
**No field is added, removed or re-typed** by the `@context` change itself. Serving the context document at the new URL is follow-up work; what changes today is that the domain is ours to serve from. Note that v0.2 as a release is not only this: ADR-0011 assigns the COSE signature envelope to manifest version `0.2` as well, so a v0.2 manifest carries the new `@context` **and** is signed as a COSE object. The two changes share a version number.

## [0.10.0] — 2026-08-01

Expand Down Expand Up @@ -272,4 +305,4 @@ Initial developer preview. Launching at Confidential Computing Summit, June 23 2
- CLI: `manifest keygen`, `create`, `sign`, `attest`, `verify`, `revoke`
- Post-quantum support via `pyoqs`: `pip install "agent-manifest[pq]"`
- Verification server: `pip install "agent-manifest[server]"`
- Python 3.11, 3.12, 3.13 support
- Python 3.11, 3.12, 3.13 support
14 changes: 11 additions & 3 deletions docs/adr/0012-context-uri-moved-to-controlled-domain.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,17 @@ the same: an implementation that continued honouring the v0.1 URL would keep val
manifests named under a domain we do not own, which is precisely the condition being
fixed. A permissive migration would leave the defect in place indefinitely.

**The manifest format itself does not change.** No field is added, removed or
re-typed. v0.2 differs from v0.1 in the `@context` value alone. The version bump exists
to force the cut-over, not to signal a schema change.
**The manifest schema itself does not change.** No field is added, removed or
re-typed. The version bump exists to force the cut-over, not to signal a schema change.

*Amended 2026-08-05:* as written, this section said v0.2 "differs from v0.1 in the
`@context` value alone". That was true of this change in isolation and is not true of
v0.2 as a release. ADR-0011 had already assigned the COSE envelope to manifest version
`0.2`, so the two changes share a version number: v0.2 carries the new `@context` **and**
the COSE signature envelope. Leaving the claim unqualified produced a contradiction
inside the specification - section 2.4 planned for `0.2` manifests while the section 3
field table still required `"0.1"` - which is corrected in the same change as this note.
The schema statement stands; the "value alone" statement does not.

## Consequences

Expand Down
Loading
Loading