diff --git a/amdp-spec/CHANGELOG.md b/amdp-spec/CHANGELOG.md new file mode 100644 index 0000000..03d3289 --- /dev/null +++ b/amdp-spec/CHANGELOG.md @@ -0,0 +1,52 @@ +# Changelog — AMDP + +All notable changes to the AMDP specification will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +While AMDP is in Draft (v0.x), breaking changes between MINOR versions are explicitly permitted. See [SPECIFICATION.md section 10](SPECIFICATION.md#10-versioning-policy) for the versioning policy. + +--- + +## [0.1.0] — 2026-05-17 + +### Added + +- Initial draft specification. +- Mandate Document JSON Schema (Draft 2020-12). +- Verticals taxonomy v0.1: `advertising`, `procurement`, `equity-research`, `public-services`. +- Actions taxonomy v0.1: 13 initial actions across the four verticals. +- Constraints taxonomy v0.1: `max_amount`, `time_window`, `vendor_whitelist`/`vendor_blacklist`, `asset_classes`/`categories`, `geo_regions`, `data_classes`. +- Signature algorithms: `hybrid-ed25519-mldsa65` (RECOMMENDED), `ed25519` (legacy), `ml-dsa-65` (PQC-only). +- COSE_Sign1 wrapping per RFC 9052. +- JWKS-based key distribution per RFC 7517. +- Resolver Endpoint OpenAPI 3.1 spec (`/.well-known/amdp/verify`, `/.well-known/amdp/revoke`). +- Discovery Endpoint OpenAPI 3.1 spec (`/.well-known/amdp/discover`). +- Stable error-code set (15 codes). +- SemVer-based versioning policy with pre-1.0 contract. +- Conformance specification with role-based MUST/SHOULD/MAY requirements (Issuer, Verifier, Resolver, Discovery Endpoint). +- Test-vector plan (15 vectors planned for Phase 4 reference test suite). +- Security threat model (T1-T6) with explicit mitigations. +- Post-quantum cryptography migration strategy (4-phase plan). +- Audit-log requirements (resolver-side, verifier-side, audit-trail-endpoint format). +- Five reference example mandates: advertising publisher mandate, equity-research family-office, procurement cross-vendor, multi-vertical family-office, public-services citizen request. + +### Authored by + +- digital opua GmbH (CHE-435.289.702, Switzerland) via Nexbid. +- Initial editor: Holger von Ellerts. + +### Submission intents + +- IAB Tech Lab AAMP (Agent-Authority Marketplace Protocol) — Curation-Protocol window Q2 2026 (30.06.2026). +- Linux Foundation Agent-Infrastructure-WG. + +### Known open items for v0.2.0 + +- Test-vector files in `examples/test-vectors/` (15 vectors enumerated in CONFORMANCE.md section 6). +- npm package `@protocol-commerce/amdp-conformance` (CLI + library). +- Audit-event signing scheme details (deferred from v0.1.0). +- Resolver version-range advertisement on `/discover` responses. +- Multi-source attribution for sub-delegated mandates (analogous to AdCP multi-source attribution planned for v0.2.0). +- Lean 4 reference implementation announcement once protocol-commerce repo includes it. diff --git a/amdp-spec/CONFORMANCE.md b/amdp-spec/CONFORMANCE.md new file mode 100644 index 0000000..cfeb954 --- /dev/null +++ b/amdp-spec/CONFORMANCE.md @@ -0,0 +1,196 @@ +# AMDP — Conformance + +**Version:** 0.1.0 +**Status:** Draft +**License:** MIT (specification) + +> This document defines testable conformance requirements for AMDP implementations. RFC 2119 keywords (MUST, SHOULD, MAY) apply. + +--- + +## 1. Conformance roles + +An AMDP implementation conforms to one or more of the following roles. A single deployment MAY implement multiple roles. + +| Role | Responsibility | +|------|----------------| +| **Mandate Issuer** | Produces signed mandate documents on behalf of a principal. | +| **Verifier** | Receives mandates from agents and calls a resolver to verify them. | +| **Resolver** | Verifies signatures, evaluates constraints, answers revocation queries. | +| **Discovery Endpoint** | Maps (vertical, action) pairs to resolvers. OPTIONAL role. | + +The remainder of this document specifies the MUST/SHOULD/MAY requirements for each role. + +--- + +## 2. Mandate Issuer conformance + +An Issuer MUST: + +- **I-1** Produce mandate documents that validate against the JSON Schema in SPECIFICATION.md section 2.1. +- **I-2** Generate `mandate_id` as a UUID v7 per [draft-ietf-uuidrev-rfc4122bis](https://datatracker.ietf.org/doc/draft-ietf-uuidrev-rfc4122bis/). +- **I-3** Canonicalize the mandate per [RFC 8785](https://www.rfc-editor.org/rfc/rfc8785) before signing. +- **I-4** Wrap the signature in a COSE_Sign1 structure per [RFC 9052](https://www.rfc-editor.org/rfc/rfc9052). +- **I-5** Publish the principal's public keys via a JWKS at `principal.verifier_url`. +- **I-6** Use `expires_at` no later than 12 months from `issued_at`. Issuers MAY use shorter windows; they MUST NOT use longer. +- **I-7** Reject scope combinations where an action is not defined for the vertical (section 4 of SPECIFICATION.md). + +An Issuer SHOULD: + +- **I-8** Use `hybrid-ed25519-mldsa65` for all new mandates. +- **I-9** Include at least one constraint in every mandate. +- **I-10** Include an `audit_trail_endpoint` for mandates that will be used in regulated verticals (`equity-research`, `public-services`, `procurement` over a stakeholder threshold). +- **I-11** Document the issuance process so an auditor can reproduce a mandate's provenance. + +An Issuer MAY: + +- **I-12** Sub-delegate by issuing a mandate with a `delegation_chain`. Each chain link MUST narrow scope. + +--- + +## 3. Verifier conformance + +A Verifier MUST: + +- **V-1** Validate the mandate document against the schema in SPECIFICATION.md section 2.1 BEFORE any further processing. Return `AMDP_INVALID_SCHEMA` on failure. +- **V-2** Fetch the principal's JWKS from `principal.verifier_url` and independently validate the signature (NOT relying solely on the resolver's `valid=true`). +- **V-3** Call the resolver's `/verify` endpoint with the `mandate_id`, the proposed `action`, and an action `context` payload sufficient for constraint evaluation. +- **V-4** Treat any non-200 response from the resolver as authorization-denied. +- **V-5** Re-verify on every action attempt; MUST NOT cache positive verifications for longer than the resolver's `Cache-Control: max-age`. +- **V-6** Reject mandates whose `amdp_version` major component is outside the verifier's supported range (`AMDP_VERSION_INCOMPATIBLE`). +- **V-7** Refuse to bypass the resolver and locally compute constraint satisfaction. The resolver is the single source of truth for constraint state. See SECURITY.md threat T3. + +A Verifier SHOULD: + +- **V-8** Send action audit events to the principal's `audit_trail_endpoint` if one is configured. +- **V-9** Implement exponential backoff with jitter when retrying after `AMDP_RATE_LIMITED` or `AMDP_INTERNAL`. +- **V-10** Surface the human-readable `reason` from a `valid=false` response to the agent and (where applicable) to the principal. + +A Verifier MAY: + +- **V-11** Cache the principal's JWKS for up to 24 hours per RFC 7517 conventions. +- **V-12** Maintain a local audit log of mandates seen, in addition to forwarding to the principal's audit endpoint. + +--- + +## 4. Resolver conformance + +A Resolver MUST: + +- **R-1** Implement `GET /.well-known/amdp/verify` per the OpenAPI spec in SPECIFICATION.md section 7.1. +- **R-2** Implement `POST /.well-known/amdp/revoke` per the OpenAPI spec in SPECIFICATION.md section 7.1. +- **R-3** Validate mandate documents against the schema in SPECIFICATION.md section 2.1 before any verification work. +- **R-4** Validate mandate signatures against the principal's current JWKS. +- **R-5** Evaluate constraints provided in the `context` query parameter, returning `constraints_match: false` (in a 200 response) when constraints fail. The mandate is still considered "verifiable" — only the proposed action is denied. +- **R-6** Implement revocation as a one-way state transition (no un-revocation). +- **R-7** Return errors per the Problem Details format ([RFC 9457](https://www.rfc-editor.org/rfc/rfc9457)) with the error codes defined in SPECIFICATION.md section 9. +- **R-8** Reject revocation requests not signed by the current principal key (`AMDP_INVALID_SIGNATURE`). +- **R-9** Support the entire `hybrid-ed25519-mldsa65` signature scheme. Resolvers MAY additionally support `ed25519` for legacy mandates. +- **R-10** Refuse mandates exceeding 16 KiB (`AMDP_MANDATE_TOO_LARGE`). +- **R-11** Resolve every link in a `delegation_chain`, verifying that each link strictly narrows scope vs. its parent. Return `AMDP_DELEGATION_WIDENS_SCOPE` if any link widens. +- **R-12** Rate-limit requests per source (IP, API key, or DID) and return `AMDP_RATE_LIMITED` on excess. + +A Resolver SHOULD: + +- **R-13** Implement a webhook or pub/sub mechanism to push revocation events to subscribed verifiers, in addition to the polling-based `/verify` interface. +- **R-14** Support ETag conditional GETs on `/verify` to reduce verifier bandwidth. +- **R-15** Log every verify and revoke call with the mandate ID, source identifier (IP or API key), and outcome, retained per the principal's data-retention policy. +- **R-16** Expose operational metrics (verify latency, error rate, cache hit rate) for monitoring. + +A Resolver MAY: + +- **R-17** Cache constraint evaluations for identical (mandate_id, action, context) tuples for up to 60 seconds. +- **R-18** Co-locate with the issuer's identity provider for efficient JWKS access. + +--- + +## 5. Discovery Endpoint conformance + +A Discovery Endpoint MUST: + +- **D-1** Implement `GET /.well-known/amdp/discover` per the OpenAPI spec in SPECIFICATION.md section 8.1. +- **D-2** Return only resolvers it has confirmed are reachable within the last 24 hours. +- **D-3** Set appropriate `Cache-Control` and `ETag` headers to permit efficient caching. + +A Discovery Endpoint SHOULD: + +- **D-4** Document its aggregation policy (which resolvers it lists, how new resolvers are added, removal criteria). +- **D-5** Support filtering by `vertical` (required) and `action` (optional) per the spec. +- **D-6** Implement health checks against listed resolvers and remove resolvers failing health for over 24 hours. + +A Discovery Endpoint MAY: + +- **D-7** Federate with other discovery endpoints by including their resolvers in its own response. +- **D-8** Charge for inclusion in a federated discovery endpoint, provided the policy is documented. + +--- + +## 6. Test vectors + +The following test vectors will be published alongside the spec at `examples/test-vectors/` (planned for v0.2.0). All conformant implementations MUST validate every test vector correctly. + +| Vector | Purpose | Expected result | +|--------|---------|-----------------| +| TV-001 | Minimal valid mandate, no constraints | Verifier: `valid=true` | +| TV-002 | Mandate expired by 1 second | Resolver: `AMDP_EXPIRED` (403) | +| TV-003 | Mandate with `max_amount=50000 USD`, action `context.amount=49999` | `constraints_match=true` | +| TV-004 | Mandate with `max_amount=50000 USD`, action `context.amount=50001` | `constraints_match=false`, reason cites max_amount | +| TV-005 | Mandate signed with wrong key | `AMDP_INVALID_SIGNATURE` (401) | +| TV-006 | Mandate with `delegation_chain` that widens scope | `AMDP_DELEGATION_WIDENS_SCOPE` (422) | +| TV-007 | Mandate with `vendor_whitelist=["a.example"]`, context vendor=`b.example` | `constraints_match=false` | +| TV-008 | Mandate with both `vendor_whitelist` AND `vendor_blacklist` | `AMDP_CONSTRAINT_VIOLATION` (422) | +| TV-009 | Mandate revoked, /verify call | `AMDP_REVOKED` (410) | +| TV-010 | Mandate with unknown constraint key `mystery_constraint` | `AMDP_UNKNOWN_CONSTRAINT` (422) | +| TV-011 | Hybrid signature with valid Ed25519 but invalid ML-DSA-65 | `AMDP_INVALID_SIGNATURE` (401) | +| TV-012 | Hybrid signature with invalid Ed25519 but valid ML-DSA-65 | `AMDP_INVALID_SIGNATURE` (401) | +| TV-013 | Mandate version `99.0.0` against resolver supporting only major 0 | `AMDP_VERSION_INCOMPATIBLE` (426) | +| TV-014 | Mandate document at 17 KiB | `AMDP_MANDATE_TOO_LARGE` (413) | +| TV-015 | Cross-vertical mandate `vertical=equity-research, actions=[submit_creative]` | `AMDP_OUT_OF_SCOPE` (422) | + +Each test vector ships with: + +- The input mandate document (JSON file). +- The signing key material (for signature verification tests). +- The expected resolver response (status code, body). +- A short prose description. + +--- + +## 7. Reference test suite (planned) + +A conformance test suite is planned for Phase 4 of the AMDP roadmap (ADR-040). The suite will: + +- Be available as an npm package under `@protocol-commerce/amdp-conformance`. +- Run as both a CLI tool and a library. +- Test all conformance roles against a candidate implementation's deployed endpoints. +- Produce a Pass/Fail report per requirement (I-1, V-1, R-1, etc.). +- Be available under Apache 2.0 (separate from the MIT-licensed spec). + +Implementations passing the test suite MAY display an "AMDP Conformant v0.X" badge linking to the test report. + +--- + +## 8. Interoperability matrix + +The following matrix tracks interoperability between AMDP-conformant implementations. It will be populated as implementations register. + +| Implementation | Role | AMDP version | Last tested | Status | +|----------------|------|--------------|-------------|--------| +| Nexbid (reference) | Issuer, Verifier, Resolver | v0.1.0 | _planned Phase 4_ | _draft_ | +| _(open)_ | | | | | + +--- + +## 9. Reporting non-conformance + +If an implementation claims AMDP conformance but fails one or more requirements: + +1. Open an issue in `nexbid-dev/protocol-commerce` with title `amdp: non-conformance — [implementation name]`. +2. Include the failing requirement IDs (e.g. `R-4, R-9`), the test vector(s) that failed, and the implementation's response. +3. Tag with `area:amdp` and `type:conformance`. + +The implementation maintainer SHOULD respond within 14 days. If unresolved within 90 days, the implementation MUST drop conformance claims publicly. + +--- + +End of conformance specification. diff --git a/amdp-spec/README.md b/amdp-spec/README.md new file mode 100644 index 0000000..45d6d92 --- /dev/null +++ b/amdp-spec/README.md @@ -0,0 +1,190 @@ +# AMDP — Agent Mandate Discovery Protocol + +> An open protocol for discovering, verifying, and revoking cross-vertical agent mandates. + +**Version:** 0.1.0 +**Status:** Draft — open for feedback, breaking changes expected +**License:** MIT (specification) / Apache 2.0 (reference implementations) +**Spec authoring org:** [digital opua GmbH](https://nexbid.dev) (Switzerland) via [Nexbid](https://nexbid.dev) +**Submission targets:** IAB Tech Lab AAMP · Linux Foundation Agent-Infrastructure-WG + +--- + +## What is AMDP? + +AMDP is an open protocol that answers a single, structurally-difficult question across the entire agent-commerce stack: + +> Which agent is authorized to do what, on behalf of whom, with which constraints, in which vertical — and how do third parties verify and revoke that authorization? + +Today, every vertical (advertising, procurement, equity research, public services) reimplements its own mandate logic. AMDP standardizes the layer **underneath** verticals, not next to them. It is not a marketplace, not a capability registry, not a transaction protocol — it is the **mandate-discovery layer** that those three depend on. + +## Status: v0.1.0 Draft + +This is a **working draft**, not a final standard. The protocol surface, schemas, error codes, and taxonomy will change as the spec moves toward v1.0.0. Implementers should expect breaking changes between minor versions during the v0.x phase. + +| Version range | Stability promise | +|---------------|-------------------| +| `v0.x.x` | Draft. Breaking changes between minor versions are expected. Feedback welcome. | +| `v1.0.0` | Stable. Breaking changes only in major versions. | + +Current version: **v0.1.0** (initial draft, 2026-05-17). + +## Position in the Agent-Commerce Stack + +AMDP is the fourth, currently-missing layer in the agent-commerce stack: + +``` ++----------------------------------------------------+ +| AI Agent Layer | +| Claude · ChatGPT · Gemini · Custom | ++----------------------------------------------------+ +| Marketplace Layer (existing) | +| Circle Agent Marketplace · Shoppable UCM · ... | ++----------------------------------------------------+ +| Capability-Registry Layer (existing) | +| IAB Tech Lab Agent Registry · MCP Registry | ++----------------------------------------------------+ +| Transaction-Protocol Layer (existing) | +| UCP · ACP · AdCP · AP2 · x402 | ++====================================================+ +| Mandate-Discovery Layer (this spec) | +| AMDP | ++----------------------------------------------------+ +``` + +The three upper layers all rely on an answer to "is this agent authorized to act?" but none of them define that answer in a cross-vertical, third-party-verifiable way. AMDP fills that gap. + +## Three components + +``` ++------------------+ +------------------+ +-------------------+ +| Mandate Document |----->| Resolver Endpoint|----->| Discovery Endpoint| ++------------------+ +------------------+ +-------------------+ + Signed JSON GET /.well-known/ GET /.well-known/ + issued by principal amdp/verify amdp/discover + carried by agent POST .../revoke (federated lookup) +``` + +1. **Mandate Document** — A signed JSON document a principal (user, company, family office) issues to authorize an agent for one or more vertical-bound actions under explicit constraints. See [Mandate Schema](SPECIFICATION.md#2-mandate-document-schema). + +2. **Resolver Endpoint** — A well-known HTTP endpoint a relying party (merchant, exchange, public-service portal) calls to verify a mandate's signature, expiration, revocation status, and constraint applicability for a proposed action. See [Resolver Spec](SPECIFICATION.md#7-resolver-endpoint-spec). + +3. **Discovery Endpoint** — An optional federated lookup that returns the list of resolver endpoints supporting a given vertical-action pair, so an agent can find a compatible resolver without hard-coded URLs. See [Discovery Spec](SPECIFICATION.md#8-discovery-endpoint-spec). + +## Quick Start + +### Minimal mandate document + +```json +{ + "amdp_version": "0.1.0", + "mandate_id": "01904ad8-5e1e-7d2a-8b1c-4f5e6a7b8c9d", + "principal": { + "id": "did:web:family-office.example", + "verifier_url": "https://family-office.example/.well-known/amdp/jwks" + }, + "agent": { + "id": "did:web:agent.example/agents/research-bot-1", + "name": "Research Bot 1" + }, + "scope": { + "vertical": "equity-research", + "actions": ["make_investment_decision"], + "constraints": { + "max_amount": { "value": 500000, "currency": "USD" }, + "time_window": { + "from": "2026-05-17T00:00:00Z", + "to": "2026-08-17T00:00:00Z" + }, + "asset_classes": ["mining-equity", "energy-equity"] + } + }, + "issued_at": "2026-05-17T10:00:00Z", + "expires_at": "2026-08-17T00:00:00Z", + "signature": { + "algorithm": "hybrid-ed25519-mldsa65", + "value": "..." + } +} +``` + +### Verify a mandate (resolver-side) + +```bash +curl -s "https://family-office.example/.well-known/amdp/verify?mandate_id=01904ad8-5e1e-7d2a-8b1c-4f5e6a7b8c9d" \ + -H "Accept: application/json" +``` + +```json +{ + "valid": true, + "reason": null, + "constraints_match": true, + "remaining_actions": ["make_investment_decision"] +} +``` + +A status code of `200` means the mandate is currently valid for the queried action. `401` means signature failed, `403` means expired, `404` means unknown mandate, `410` means revoked. See [Error Codes](SPECIFICATION.md#9-error-codes). + +## Specification + +| Document | Description | +|----------|-------------| +| [SPECIFICATION.md](SPECIFICATION.md) | Main specification: terminology, schemas, taxonomies, signature algorithms, endpoint specs, error codes, versioning policy | +| [CONFORMANCE.md](CONFORMANCE.md) | MUST / SHOULD / MAY requirements for AMDP-conformant verifiers, resolvers, and discovery endpoints; test-vectors plan | +| [SECURITY.md](SECURITY.md) | Threat model (T1-T6), PQC migration strategy, audit-log requirements | +| [CHANGELOG.md](CHANGELOG.md) | Version history | +| [REFERENCES.md](REFERENCES.md) | Cross-references to related ADRs, RFCs, IAB Tech Lab, Linux Foundation | +| [examples/](examples/) | Validated reference mandate documents covering five concrete scenarios | + +## Why a separate protocol? + +A reasonable reader will ask: why not extend an existing protocol like ACP, UCP, AdCP, or AP2 to cover mandates? + +The honest answer is that each of those protocols handles a different layer: + +- **ACP / UCP** answer *how a transaction is executed* (cart, checkout, settlement). They assume the agent is already authorized. +- **AdCP** answers *how an agent discovers and ranks commerce inventory* (search, scoring, attribution). It also assumes authorization exists. +- **AP2** answers *how to bind an intent to a payment instrument* at a single point in time. It is mandate-adjacent but transaction-bound and single-vertical. +- **x402** answers *how to settle a micropayment via HTTP*. Out of scope for authorization. + +What none of them define is the **cross-vertical, revocable, third-party-verifiable mandate** that a relying party in one vertical can use to verify an agent acting on behalf of a principal whose primary identity lives in another vertical. AMDP fills that specific gap, and is designed to interoperate with all four (mandate produced once, consumed by ACP carts, UCP checkouts, AdCP attributions, AP2 payment bindings). + +## Relationship to existing precedents + +AMDP generalizes patterns established in two earlier specifications: + +- **ADR-008 Universal Purchase Mandate** (Nexbid, 2026-04-23) defined a single-vertical (commerce / payment) Ed25519-signed mandate format. AMDP extends that pattern cross-vertical and adds a discovery layer. See [REFERENCES.md](REFERENCES.md). + +- **ADR-025 Crypto-Agility and PQC Migration** (Nexbid, 2026-04-29) established the Hybrid Ed25519 + ML-DSA-65 signature scheme used in AMDP for post-quantum readiness. AMDP signatures conform to that scheme. + +## License + +- **Specification:** MIT — free to read, implement, fork, distribute. See [LICENSE](../LICENSE). +- **Reference implementations** (planned, separate repos): Apache 2.0 — explicit patent grant. + +## Co-Maintainers + +Open. Initial draft authored by digital opua GmbH (CHE-435.289.702, Switzerland) via Nexbid. Submission to IAB Tech Lab AAMP and Linux Foundation Agent-Infrastructure-WG is in progress; co-maintainership will reflect those affiliations once submission is accepted. + +| Role | Holder | Affiliation | +|------|--------|-------------| +| Initial editor | Holger von Ellerts | digital opua GmbH / Nexbid | +| AAMP submission contact | TBD | IAB Tech Lab (post-submission) | +| LF Agent-Infra WG contact | TBD | Linux Foundation (post-submission) | +| EU sovereignty co-maintainer | TBD | (open seat) | + +## Contributing + +1. **Spec feedback:** Open an issue in `nexbid-dev/protocol-commerce` describing the problem and proposed change. Tag with `area:amdp`. +2. **Schema contributions:** PRs against [SPECIFICATION.md](SPECIFICATION.md) and [examples/](examples/). Examples must validate against the Mandate schema in `SPECIFICATION.md` section 2. +3. **New verticals / actions / constraints:** Propose via issue, discuss, then PR. See the IANA-style taxonomy registry in `SPECIFICATION.md` sections 3-5. +4. **Reference implementations:** Build a verifier, resolver, or discovery endpoint — list it under "Reference implementations" once interoperability tests pass. + +## Cross-repo links + +- ADR-040 (this protocol's rationale): `github.com/Baldri/nexbid/blob/main/docs/knowledge-base/adr/040-amdp-agent-mandate-discovery-protocol.md` +- Strategy note (positioning): `github.com/Baldri/nexbid/blob/main/docs/strategy/2026-05-17-amdp-google-fuer-agenten-ehrliche-positionierung.md` +- IAB Tech Lab submission draft: `github.com/Baldri/nexbid/blob/main/docs/outreach/2026-05-17-iab-amdp-submission-draft.md` +- ADR-008 (single-vertical precedent): `github.com/Baldri/nexbid/blob/main/docs/knowledge-base/adr/008-payment-authorization-and-purchase-mandate.md` +- ADR-025 (PQC migration): `github.com/Baldri/nexbid/blob/main/docs/knowledge-base/adr/025-crypto-agility-and-pqc-migration.md` diff --git a/amdp-spec/REFERENCES.md b/amdp-spec/REFERENCES.md new file mode 100644 index 0000000..80f7533 --- /dev/null +++ b/amdp-spec/REFERENCES.md @@ -0,0 +1,152 @@ +# AMDP — References + +**Version:** 0.1.0 +**License:** MIT (specification) + +This document collects external and internal references relevant to the AMDP specification. + +--- + +## 1. AMDP authoring context + +### ADRs (`Baldri/nexbid` repo) + +- **ADR-040 — AMDP as Open Standard** + `github.com/Baldri/nexbid/blob/main/docs/knowledge-base/adr/040-amdp-agent-mandate-discovery-protocol.md` + Rationale, decision, alternatives, consequences, and revisit triggers for AMDP. Authored 2026-05-17. + +- **ADR-008 — Payment Authorization and Universal Purchase Mandate** + `github.com/Baldri/nexbid/blob/main/docs/knowledge-base/adr/008-payment-authorization-and-purchase-mandate.md` + Single-vertical (commerce/payment) Ed25519-signed mandate format. AMDP generalizes this pattern cross-vertical. + +- **ADR-025 — Crypto-Agility and PQC Migration** + `github.com/Baldri/nexbid/blob/main/docs/knowledge-base/adr/025-crypto-agility-and-pqc-migration.md` + Hybrid Ed25519 + ML-DSA-65 signature scheme. AMDP signatures conform to this scheme. + +- **ADR-006 — Compliance Manifest (Ed25519 infrastructure)** + `github.com/Baldri/nexbid/blob/main/docs/knowledge-base/adr/006-compliance-manifest.md` + Original Ed25519 signing infrastructure that ADR-008 and ADR-025 built on. + +### Companion documents + +- **Strategy note (positioning):** + `github.com/Baldri/nexbid/blob/main/docs/strategy/2026-05-17-amdp-google-fuer-agenten-ehrliche-positionierung.md` + Honest positioning of AMDP: not "Google for agents", but the missing fourth layer of the agent-commerce stack. + +- **IAB Tech Lab submission draft:** + `github.com/Baldri/nexbid/blob/main/docs/outreach/2026-05-17-iab-amdp-submission-draft.md` + Submission pitch for IAB Tech Lab AAMP inclusion. + +--- + +## 2. Sibling protocols in `nexbid-dev/protocol-commerce` + +- **AdCP — Agentic Discovery Commerce Protocol** + `./adcp-spec/` (this repository) + Adjacent protocol: agent-mediated commerce discovery, ranking, and attribution. AMDP and AdCP share the `nexbid-dev/protocol-commerce` repo and are designed for interoperability. An agent acting on an AdCP `create_media_buy` flow MAY carry an AMDP mandate authorizing the buy. + +- **Protocol Commerce manifesto** + `./manifesto/` + Principles, landscape, and rationale for open protocols in agent-mediated commerce. + +--- + +## 3. IETF / W3C standards + +- **RFC 2119** — Key words for use in RFCs to indicate requirement levels. + https://www.rfc-editor.org/rfc/rfc2119 + Source of MUST/SHOULD/MAY semantics. + +- **RFC 3339** — Date and time on the Internet: Timestamps. + https://www.rfc-editor.org/rfc/rfc3339 + Used for `issued_at`, `expires_at`, `time_window`. + +- **RFC 7517** — JSON Web Key (JWK) and JWK Set (JWKS). + https://www.rfc-editor.org/rfc/rfc7517 + Principal public-key distribution. + +- **RFC 8032** — Edwards-Curve Digital Signature Algorithm (EdDSA). + https://www.rfc-editor.org/rfc/rfc8032 + Ed25519 component of `hybrid-ed25519-mldsa65`. + +- **RFC 8785** — JSON Canonicalization Scheme (JCS). + https://www.rfc-editor.org/rfc/rfc8785 + Deterministic byte-stream for signing. + +- **RFC 9052** — CBOR Object Signing and Encryption (COSE). + https://www.rfc-editor.org/rfc/rfc9052 + COSE_Sign1 wrapping format for AMDP signatures. + +- **RFC 9457** — Problem Details for HTTP APIs. + https://www.rfc-editor.org/rfc/rfc9457 + Error response format on resolver endpoints. + +- **draft-ietf-uuidrev-rfc4122bis** — UUID v7. + https://datatracker.ietf.org/doc/draft-ietf-uuidrev-rfc4122bis/ + Time-ordered, unique mandate identifiers. + +- **W3C DID Core v1.0** — Decentralized Identifiers. + https://www.w3.org/TR/did-core/ + Principal and agent identification. + +--- + +## 4. NIST / FIPS + +- **FIPS 204** — Module-Lattice-Based Digital Signature Standard (ML-DSA). + https://csrc.nist.gov/pubs/fips/204/final + ML-DSA-65 component of `hybrid-ed25519-mldsa65`. Standardized August 2024. + +- **NIST PQC project** — Post-Quantum Cryptography standardization. + https://csrc.nist.gov/projects/post-quantum-cryptography + +--- + +## 5. Industry standards / WG / coalitions + +- **IAB Tech Lab AAMP — Agent-Authority Marketplace Protocol** + https://iabtechlab.com (AAMP project page TBD) + Submission target for AMDP. Curation-Protocol Q2 2026 window closes 30.06.2026. + +- **Linux Foundation Agent-Infrastructure-WG** + Submission target. x402 (HTTP 402 Payment Standard) is a precedent for LF-hosted agent protocol stewardship. + +- **UCP — Universal Commerce Protocol** + https://ucp.dev + Transaction-layer protocol. UCP coalition (Google + Shopify + Amazon + Meta + Microsoft + Salesforce + Stripe + Etsy + Target + Wayfair as of 2026-04-24). AMDP is the mandate-discovery layer UNDER UCP — orthogonal, not competing. + +- **ACP — Agentic Commerce Protocol (OpenAI + Stripe)** + Transaction-layer protocol. Apache 2.0. AMDP mandates can authorize ACP-bound agent purchases. + +- **AP2 — Agent Payments Protocol (Google → FIDO Foundation, donated 2026-05-11)** + Transaction-layer protocol focused on intent-to-payment-instrument binding. Single-vertical and single-transaction-bound; AMDP is cross-vertical and longer-lived. + +- **x402 — HTTP 402 Payment Standard** + https://x402.org (Linux Foundation) + Payment-rail standard. Out of scope for AMDP authorization. + +- **MCP — Model Context Protocol (Anthropic)** + https://modelcontextprotocol.io + Agent-tool transport. AMDP-aware MCP servers MAY expose mandate-verification tools (planned reference implementation). + +--- + +## 6. EU and regulatory context + +- **EU AI Act (Regulation (EU) 2024/1689)** — Art. 14 (Human Oversight), Art. 50 (Transparency obligations for AI systems). + Mandate-discovery is a structural mechanism supporting both articles for high-risk AI systems. + +- **nDSG (Swiss Data Protection Act, revised, effective 2023-09-01)** + Applicable to Swiss-hosted resolvers and verifiers handling personal data. + +- **GDPR (Regulation (EU) 2016/679)** + Applicable when mandates handle EU-resident personal data. AMDP itself imposes no PII obligations; implementations handling PII alongside AMDP MUST comply with GDPR independently. + +--- + +## 7. Editorial conventions + +- Cross-repo links use `github.com///blob/main/` form. +- Internal links within `amdp-spec/` use relative paths (e.g., `[SPECIFICATION.md](SPECIFICATION.md)`). +- Each spec file carries a status header (Version, Status, License). +- RFC 2119 keywords appear in ALL CAPS only in normative sections. diff --git a/amdp-spec/SECURITY.md b/amdp-spec/SECURITY.md new file mode 100644 index 0000000..9ae679a --- /dev/null +++ b/amdp-spec/SECURITY.md @@ -0,0 +1,271 @@ +# AMDP — Security Considerations + +**Version:** 0.1.0 +**Status:** Draft +**License:** MIT (specification) + +> This document describes the AMDP threat model, mitigations, the post-quantum cryptography migration strategy, and audit-log requirements. + +--- + +## 1. Threat model + +The following six threats define the AMDP security boundary. Each threat is mitigated by specific design decisions in the protocol; mitigations are normative (MUST) unless otherwise noted. + +### T1 — Mandate Forgery + +**Threat:** An attacker produces a mandate document that appears to authorize an agent for actions the principal did not approve. + +**Vectors:** + +- Tampering with mandate fields after signing. +- Constructing a new mandate and signing with a stolen or compromised principal key. +- Algorithm-downgrade attacks (force a verifier to validate a weaker signature than the mandate specifies). +- Selection of an obscure or non-standard signature algorithm to bypass verifiers that fall back to "accept". + +**Mitigations:** + +- **M1.1** All mandates MUST be signed per section 6 of SPECIFICATION.md. Unsigned mandates MUST be rejected. +- **M1.2** Verifiers MUST validate the signature BEFORE any other processing — schema validation comes first, then signature. +- **M1.3** Verifiers MUST refuse algorithms outside the allowed set in section 6.1-6.3. Specifically, an `algorithm: "none"` or any value not in `{ed25519, ml-dsa-65, hybrid-ed25519-mldsa65}` MUST be rejected with `AMDP_INVALID_SIGNATURE`. +- **M1.4** Verifiers MUST canonicalize the mandate per RFC 8785 before signature verification. Implementations MUST NOT verify signatures over non-canonical bytes. +- **M1.5** For `hybrid-ed25519-mldsa65`, BOTH component signatures MUST verify. Either-or fallback is forbidden. +- **M1.6** Principal keys MUST be distributed via JWKS at `principal.verifier_url`. JWKS responses MUST be served over TLS 1.2+ with valid certificate. + +### T2 — Replay Attack + +**Threat:** An attacker intercepts a valid mandate (e.g., via network capture, leaked log, exposed agent) and re-uses it after the original action was completed, after revocation, or in a different context than intended. + +**Vectors:** + +- Replaying a mandate after `expires_at`. +- Replaying a mandate that has been revoked but whose verifier had cached a positive verify. +- Re-using a "single-use" mandate (a mandate intended for one specific action). +- Cross-resolver replay (using a mandate against a different resolver than the one whose context it was created for). + +**Mitigations:** + +- **M2.1** Every mandate has a `mandate_id` (UUID v7) and an `expires_at`. Resolvers MUST check both on every `/verify` call. +- **M2.2** Resolvers SHOULD maintain a server-side cache of consumed actions for single-use mandates, indexed by `mandate_id`. The cache is the source of truth for `remaining_actions`. +- **M2.3** Verifiers MUST NOT cache positive verifications beyond the resolver's advertised `Cache-Control: max-age`. Stale cache hits constitute replay. +- **M2.4** Resolvers MUST treat revocation as an atomic state transition (no soft-delete, no resurrection). +- **M2.5** Mandates SHOULD use the shortest `expires_at` that still allows the intended workflow. Mandates with multi-month windows are explicitly higher-risk and SHOULD use audit-log endpoints. +- **M2.6** Verifiers SHOULD subscribe to resolver revocation webhooks where available, to invalidate cached verifications proactively. + +### T3 — Constraint Bypass + +**Threat:** An attacker constructs an action that should fail constraint evaluation but succeeds because constraints are evaluated client-side, locally, or with stale data. + +**Vectors:** + +- An agent computes constraint satisfaction locally and presents only the result to a verifier. +- A verifier accepts the agent's constraint-pass claim without calling the resolver. +- A verifier passes an incomplete or modified `context` to the resolver's `/verify` endpoint. +- An attacker exploits constraint-evaluation race conditions (e.g., cumulative `max_amount` not yet updated when a second concurrent action is verified). + +**Mitigations:** + +- **M3.1** Constraint evaluation MUST happen server-side at the resolver. Agents and verifiers MUST NOT compute constraint satisfaction locally for authorization purposes. +- **M3.2** Verifiers MUST pass the complete action `context` to the resolver. Verifiers MUST NOT redact or transform context fields. +- **M3.3** Resolvers MUST evaluate cumulative constraints (e.g., `max_amount` with `mode=cumulative`) under a serializable transaction or equivalent isolation. Stale-read race conditions on cumulative state MUST be prevented. +- **M3.4** Unknown constraint keys MUST cause resolvers to fail closed (`AMDP_UNKNOWN_CONSTRAINT`). Resolvers MUST NOT silently ignore unrecognized constraints. +- **M3.5** Verifiers SHOULD log the full context passed to the resolver, so post-hoc audit can detect verifier-side constraint stripping. + +### T4 — Compromised Principal Key + +**Threat:** A principal's private signing key is exposed, allowing an attacker to issue arbitrary mandates in the principal's name. + +**Vectors:** + +- Key extraction from a poorly secured HSM, software wallet, or signing-service. +- Insider threat at the principal's organization. +- Long-term cryptanalysis (relevant for ed25519 in a post-CRQC world; see T5). + +**Mitigations:** + +- **M4.1** Principals MUST be able to rotate keys at any time by publishing an updated JWKS at `principal.verifier_url`. Verifiers MUST re-fetch JWKS on signature verification failure to catch rotation. +- **M4.2** Principals SHOULD include `key_id` (`kid`) in the signature object so a specific key can be retired without invalidating all mandates. +- **M4.3** Mandates signed with a rotated-out key remain valid until `expires_at` UNLESS explicitly revoked. Principals SHOULD revoke mandates issued with a compromised key as a separate cleanup step. +- **M4.4** Resolvers MUST honor revocation requests authenticated by a CURRENT (post-rotation) key. The revocation request signature scheme MUST match section 6 of SPECIFICATION.md. +- **M4.5** Principals operating in high-stakes verticals (`equity-research`, `procurement` over a stakeholder threshold) SHOULD use HSM-backed key storage and audit key access. + +### T5 — Compromised Agent + +**Threat:** An agent is compromised (its code, its runtime, its DID-binding keys) and used to perform unauthorized actions while presenting a valid mandate. + +**Vectors:** + +- Agent runtime is hijacked (prompt injection, supply-chain attack, container escape). +- Agent's DID-binding keys are extracted and used by an attacker-controlled process. +- An attacker presents a valid mandate to a verifier while masquerading as the legitimate agent. + +**Mitigations:** + +- **M5.1** Mandates MUST bind to a specific `agent.id` (DID). Verifiers MUST validate that the agent presenting the mandate matches the `agent.id` in the mandate. +- **M5.2** Agent authentication to verifiers is OUT OF SCOPE of AMDP — the verifier MUST establish agent identity through a transport-layer mechanism (mTLS, signed JWT with agent DID, etc.) before accepting a mandate. +- **M5.3** Mandates SHOULD declare `audit_trail_endpoint` so the principal can detect anomalous mandate usage in near-real-time. +- **M5.4** Verifiers SHOULD log enough metadata about each action (verifier identity, action context, timestamp) to support forensic reconstruction. +- **M5.5** Principals SHOULD use short `expires_at` (hours to days, not months) for agent-bound mandates in high-stakes verticals. +- **M5.6** Agent `model` MAY be included in the mandate. Verifiers MAY use this to refuse actions from unexpected models (e.g., a mandate authorizing `anthropic/claude-opus-4-7` should not be honored when the requester identifies as `unknown/jailbroken-fork`). + +### T6 — Cross-Vertical Privilege Escalation + +**Threat:** An attacker uses a mandate issued for one vertical to perform actions in a different vertical (e.g., a mandate for `advertising.create_media_buy` to attempt `procurement.approve_order`). + +**Vectors:** + +- A verifier in vertical B accepts a mandate scoped to vertical A by failing to check `scope.vertical`. +- A discovery endpoint routes a request to the wrong resolver, which then accepts the cross-vertical mandate. +- A vertical owner extends the action taxonomy with an action name colliding with another vertical's action. + +**Mitigations:** + +- **M6.1** Resolvers MUST enforce strict (vertical, action) pair validation. A mandate with `vertical=A` listing an action defined for `vertical=B` MUST be rejected with `AMDP_OUT_OF_SCOPE`. +- **M6.2** Verifiers MUST check that the mandate's `scope.vertical` matches the vertical in which the action is being attempted. +- **M6.3** Action identifiers are scoped to their vertical. Two verticals MAY define identically-named actions; resolvers MUST treat them as distinct. +- **M6.4** Discovery endpoints MUST return only resolvers whose `supported_verticals` includes the queried vertical. Discovery responses MUST NOT include resolvers for unrelated verticals as "fallback". +- **M6.5** Sub-delegated mandates (with `delegation_chain`) MUST NOT change the vertical from their parent. Resolvers MUST reject vertical-changing delegation as scope-widening (`AMDP_DELEGATION_WIDENS_SCOPE`). + +--- + +## 2. Post-quantum cryptography migration strategy + +AMDP v0.1.0 uses `hybrid-ed25519-mldsa65` as the RECOMMENDED signature scheme. This section explains the migration path and the rationale, with explicit reference to [ADR-025 — Crypto-Agility and PQC Migration](https://github.com/Baldri/nexbid/blob/main/docs/knowledge-base/adr/025-crypto-agility-and-pqc-migration.md). + +### 2.1 Hybrid signatures, why now + +A "harvest now, decrypt later" attacker captures signed payloads today and waits for a sufficiently capable quantum computer (CRQC) to break Ed25519. For data with long validity (e.g., a 12-month mandate authorizing financial decisions), the relevant horizon is the mandate's `expires_at` PLUS the audit-retention window of the relying party. Realistic timelines for CRQC range from 5 to 20 years; the conservative posture is to begin hybrid signing now. + +ML-DSA-65 (NIST FIPS 204, standardized August 2024) is the current PQC signature standard. Pure ML-DSA-65 deployments have limited library and HSM support as of 2026; hybrid Ed25519 + ML-DSA-65 deployments combine the maturity of classical signing with the future-proofing of PQC. + +### 2.2 Migration phases + +| Phase | Period | Verifier behavior | Issuer behavior | +|-------|--------|--------------------|-----------------| +| **Phase 0 (current)** | v0.1.0 | MUST support hybrid; MAY support pure Ed25519 for legacy. | SHOULD use hybrid. | +| **Phase 1** | TBD (v0.x — based on adoption) | MUST support hybrid; SHOULD warn on pure Ed25519. | MUST use hybrid for any mandate with `expires_at > issued_at + 90d`. | +| **Phase 2** | TBD (after CRQC plausible) | MUST reject pure Ed25519. | MUST use hybrid or pure ML-DSA-65. | +| **Phase 3** | TBD (post-CRQC) | MUST reject all classical-only signatures. | MUST use pure ML-DSA-65 or successor. | + +Phase transitions are governed by: + +- NIST PQC guidance (FIPS updates, CRQC threat assessments). +- AMDP governance consensus. +- A 12-month notice period before each transition. + +### 2.3 Library and HSM support + +Implementers SHOULD reference the AMDP wiki (planned) for a current list of: + +- Libraries supporting `hybrid-ed25519-mldsa65` in their target language. +- HSMs offering ML-DSA-65 hardware-backed signing. +- Test vectors for cross-implementation interoperability. + +### 2.4 Cryptographic algorithm registry + +AMDP MAY add new signature algorithms via the same IANA-style registry process used for verticals and actions. Proposed criteria for new algorithms: + +- NIST-approved or equivalent national-standard approval (e.g., BSI, ETSI). +- Library availability in at least three production-grade implementations. +- Hybrid composition with at least one currently-supported algorithm. +- Community review through the AMDP governance process. + +--- + +## 3. Audit-log requirements + +Audit logging is the protocol's primary defense against undetected misuse after a mandate has been issued. This section defines what implementations MUST, SHOULD, and MAY log. + +### 3.1 Resolver audit logging + +A Resolver MUST log: + +- **AL-R1** Every `/verify` call: timestamp, `mandate_id`, requesting verifier identity (IP / API key / DID), proposed `action`, redacted `context`, response status and reason. +- **AL-R2** Every `/revoke` call: timestamp, `mandate_id`, requester identity, signature verification outcome. +- **AL-R3** Every JWKS rotation event: timestamp, retired `kid`, added `kid`. + +A Resolver SHOULD log: + +- **AL-R4** Sub-delegation chain resolution (which links were followed, which rejected). +- **AL-R5** Constraint evaluation details for failed evaluations (which constraint failed, with what context value). + +Resolver logs MUST be retained per the principal's data-retention policy, with a minimum of 90 days for the regulated verticals (`equity-research`, `procurement` over a stakeholder threshold, `public-services`). + +### 3.2 Verifier audit logging + +A Verifier SHOULD log: + +- **AL-V1** Every mandate it accepts: `mandate_id`, action, context, resolver response. +- **AL-V2** Every mandate it rejects: `mandate_id`, reason, agent identity. +- **AL-V3** Every JWKS fetch result. + +If the mandate has an `audit_trail_endpoint`, the Verifier MUST forward AL-V1 and AL-V2 events to that endpoint, fire-and-forget, with retry on failure. + +### 3.3 Audit-trail endpoint format + +Audit events sent to a principal's `audit_trail_endpoint` use the following shape: + +```json +{ + "amdp_version": "0.1.0", + "event_type": "verify_accepted", + "event_id": "01904ad8-5e1e-7d2a-8b1c-4f5e6a7b8c9d", + "mandate_id": "01904ad8-5e1e-7d2a-8b1c-4f5e6a7b8c9e", + "timestamp": "2026-05-17T10:23:45Z", + "verifier": { + "id": "did:web:merchant.example", + "ip": "203.0.113.42" + }, + "agent": { + "id": "did:web:agent.example/agents/research-bot-1" + }, + "action": "make_investment_decision", + "context": { + "amount": 49500, + "currency": "USD", + "asset": "ABC.LON" + }, + "outcome": "accepted" +} +``` + +`event_type` values: + +- `verify_accepted` — Verifier received a 200 `valid=true` response and proceeded with the action. +- `verify_denied` — Verifier received a 200 `valid=false` or non-200 response. +- `verify_error` — Verifier encountered an error reaching the resolver. + +Principals MAY use audit events for fraud monitoring, anomaly detection, billing reconciliation, and regulatory reporting. + +### 3.4 Audit-event signing + +Audit events SHOULD be signed by the verifier (using the verifier's DID-bound key) so the principal can detect tampering. The signing scheme is the same as section 6 of SPECIFICATION.md. This is not strictly required at v0.1.0 and is a candidate for hardening in v0.2.0. + +--- + +## 4. Privacy considerations + +While AMDP is an authorization protocol (not a privacy protocol), several privacy-relevant choices are intentional: + +- **No user-identifying payload.** Mandates carry DIDs (which MAY be self-managed) and explicit fields. They do NOT require government IDs, biometric data, or tracking identifiers. +- **Principal-controlled JWKS distribution.** Key distribution is principal-hosted, not platform-hosted. There is no central registry collecting principal metadata. +- **No cross-vertical correlation by the protocol.** A single agent acting under separate mandates in different verticals presents separate `mandate_id`s; the protocol provides no mechanism to link them unless the principal chooses to. +- **Audit-log scope.** The audit-trail endpoint receives events about the principal's OWN mandates. There is no third-party aggregation by default. + +Implementers handling PII alongside AMDP (e.g., `public-services` verticals with `data_classes=["pii"]`) MUST comply with applicable data-protection law (GDPR, nDSG, etc.). AMDP itself imposes no PII obligations beyond the structural choices above. + +--- + +## 5. Reporting security issues + +Security issues in the AMDP specification (not implementations) should be reported to `security@nexbid.dev` (PGP key TBD) or via private vulnerability disclosure on `nexbid-dev/protocol-commerce`. + +- DO NOT open a public issue for an undisclosed vulnerability. +- DO include a description, reproduction steps if applicable, and any proposed mitigation. +- A response is committed within 7 days. + +For implementation-specific vulnerabilities, contact the implementation's maintainers directly. + +--- + +End of security considerations. diff --git a/amdp-spec/SPECIFICATION.md b/amdp-spec/SPECIFICATION.md new file mode 100644 index 0000000..88e9ca5 --- /dev/null +++ b/amdp-spec/SPECIFICATION.md @@ -0,0 +1,746 @@ +# AMDP — Specification + +**Version:** 0.1.0 +**Status:** Draft +**License:** MIT (specification) + +> This document is the normative specification for the Agent Mandate Discovery Protocol (AMDP). All sections marked MUST, SHOULD, and MAY follow [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) keywords. + +--- + +## Table of Contents + +1. [Terminology](#1-terminology) +2. [Mandate Document Schema](#2-mandate-document-schema) +3. [Verticals Taxonomy v0.1](#3-verticals-taxonomy-v01) +4. [Actions Taxonomy v0.1](#4-actions-taxonomy-v01) +5. [Constraints Taxonomy v0.1](#5-constraints-taxonomy-v01) +6. [Signature Algorithms](#6-signature-algorithms) +7. [Resolver Endpoint Spec](#7-resolver-endpoint-spec) +8. [Discovery Endpoint Spec](#8-discovery-endpoint-spec) +9. [Error Codes](#9-error-codes) +10. [Versioning Policy](#10-versioning-policy) + +--- + +## 1. Terminology + +The following terms have precise meanings throughout this specification. + +| Term | Definition | +|------|-----------| +| **Principal** | The entity (natural person, legal person, organization, system account) that issues a mandate. The principal is the legal-effect owner of any action an agent takes under the mandate. Identified by a Decentralized Identifier (DID) and exposes a JWKS for key distribution. | +| **Agent** | The software actor authorized by a principal to perform actions. Identified by a DID. An agent is NOT the principal; it acts on the principal's behalf. | +| **Mandate** | A signed JSON document binding an agent to a principal under explicit scope and constraints, with explicit issuance and expiration times. The full Mandate Document schema is defined in section 2. | +| **Vertical** | A bounded domain of agent activity for which actions and constraints have well-defined semantics. Initial verticals are defined in section 3. | +| **Action** | A named operation within a vertical. An action's semantics are defined by the vertical's specification. Initial actions per vertical are defined in section 4. | +| **Constraint** | A condition on an action, evaluated by a resolver to determine whether a proposed action falls within mandate scope. Initial constraints are defined in section 5. | +| **Scope** | The triple (vertical, actions, constraints) that defines what the agent may do under the mandate. | +| **Resolver** | An HTTP service operated by (or on behalf of) the principal that verifies mandate signatures, evaluates constraints, and answers revocation queries. Defined in section 7. | +| **Verifier** | A relying party (merchant, exchange, public-service portal) that calls a resolver to verify a mandate before allowing an action. A verifier MUST trust the resolver's response, but MUST also independently validate the mandate's signature against the principal's JWKS. | +| **Discovery Endpoint** | An optional HTTP service that maps (vertical, action) pairs to resolver endpoints, enabling federated lookup. Defined in section 8. | +| **JWKS** | JSON Web Key Set per [RFC 7517](https://www.rfc-editor.org/rfc/rfc7517), used to distribute the principal's public keys. | +| **Mandate ID** | A UUID v7 ([draft-ietf-uuidrev-rfc4122bis](https://datatracker.ietf.org/doc/draft-ietf-uuidrev-rfc4122bis/)) identifying a single mandate document. Time-ordered, globally unique, contains no principal-identifying information. | +| **Delegation Chain** | An optional ordered list of mandate IDs documenting how a mandate was derived from a parent mandate (sub-delegation). Each link MUST narrow scope, never widen it. | + +--- + +## 2. Mandate Document Schema + +A Mandate Document is a JSON object that conforms to the schema below. Implementations MUST validate mandate documents against this schema before any further processing. + +### 2.1 JSON Schema (Draft 2020-12) + +```json +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://nexbid-dev.github.io/protocol-commerce/amdp-spec/mandate.json", + "title": "AMDP Mandate Document", + "description": "A signed authorization issued by a principal to an agent for a bounded scope of vertical-bound actions.", + "type": "object", + "required": [ + "amdp_version", + "mandate_id", + "principal", + "agent", + "scope", + "issued_at", + "expires_at", + "signature" + ], + "properties": { + "amdp_version": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "description": "SemVer version of the AMDP spec this mandate conforms to." + }, + "mandate_id": { + "type": "string", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", + "description": "UUID v7. Time-ordered. Globally unique." + }, + "principal": { + "type": "object", + "required": ["id", "verifier_url"], + "properties": { + "id": { + "type": "string", + "pattern": "^did:[a-z0-9]+:.+$", + "description": "Decentralized Identifier of the principal." + }, + "verifier_url": { + "type": "string", + "format": "uri", + "description": "URL returning a JWKS containing the principal's public keys." + }, + "display_name": { + "type": "string", + "description": "Human-readable principal name (optional)." + } + }, + "additionalProperties": false + }, + "agent": { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "pattern": "^did:[a-z0-9]+:.+$", + "description": "Decentralized Identifier of the agent." + }, + "name": { + "type": "string", + "description": "Human-readable agent name (optional)." + }, + "model": { + "type": "string", + "description": "Underlying model identifier (e.g. 'anthropic/claude-opus-4-7', 'openai/gpt-5'). Optional but RECOMMENDED for audit." + } + }, + "additionalProperties": false + }, + "scope": { + "type": "object", + "required": ["vertical", "actions"], + "properties": { + "vertical": { + "type": "string", + "description": "Vertical identifier from section 3 taxonomy.", + "enum": [ + "advertising", + "procurement", + "equity-research", + "public-services" + ] + }, + "actions": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + }, + "description": "Action identifiers from section 4 taxonomy. MUST be valid for the vertical." + }, + "constraints": { + "type": "object", + "description": "Constraints from section 5 taxonomy. All listed constraints MUST be satisfied for the mandate to authorize an action.", + "additionalProperties": true + } + }, + "additionalProperties": false + }, + "issued_at": { + "type": "string", + "format": "date-time", + "description": "RFC 3339 timestamp of mandate issuance." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "RFC 3339 timestamp after which the mandate is no longer valid." + }, + "revocation_url": { + "type": "string", + "format": "uri", + "description": "Optional URL of the resolver endpoint (defaults to principal.verifier_url base + /.well-known/amdp/verify)." + }, + "audit_trail_endpoint": { + "type": "string", + "format": "uri", + "description": "Optional URL where the principal accepts audit-event submissions from verifiers (see SECURITY.md)." + }, + "delegation_chain": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + }, + "description": "Optional ordered list of parent mandate IDs. Each successive mandate MUST narrow scope (no widening). Verifiers MUST resolve every link." + }, + "signature": { + "type": "object", + "required": ["algorithm", "value"], + "properties": { + "algorithm": { + "type": "string", + "enum": [ + "ed25519", + "ml-dsa-65", + "hybrid-ed25519-mldsa65" + ], + "description": "Signature algorithm. See section 6." + }, + "value": { + "type": "string", + "description": "Base64url-encoded COSE_Sign1 structure per RFC 9052." + }, + "key_id": { + "type": "string", + "description": "Optional 'kid' value pointing to a specific key in the principal's JWKS." + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} +``` + +### 2.2 Required vs optional fields + +The following fields are REQUIRED and MUST be present in every mandate: + +- `amdp_version` — Allows resolvers to refuse mandates from incompatible spec versions. +- `mandate_id` — Globally unique, used as the primary key for verification and revocation. +- `principal` (and its `id`, `verifier_url`) — Anchors authorization to a verifiable identity. +- `agent` (and its `id`) — Binds the mandate to a specific agent. +- `scope` (and its `vertical`, `actions`) — Defines what is authorized. +- `issued_at`, `expires_at` — Bounds the temporal validity. +- `signature` (and its `algorithm`, `value`) — Makes the mandate verifiable. + +The following fields are OPTIONAL: + +- `principal.display_name`, `agent.name`, `agent.model` — Audit and UX metadata. +- `scope.constraints` — Mandates without constraints are valid but maximally broad within their action set; implementers SHOULD always include at least one constraint. +- `revocation_url` — If absent, the verifier derives it from `principal.verifier_url`. +- `audit_trail_endpoint` — Required only if the principal wants third-party audit ingestion. +- `delegation_chain` — Required only for sub-delegated mandates. + +### 2.3 Canonical JSON for signing + +To produce a deterministic byte-stream for signing, implementations MUST serialize the mandate document (excluding the `signature` field) using JSON Canonicalization Scheme ([RFC 8785](https://www.rfc-editor.org/rfc/rfc8785)). This guarantees identical signing input across implementations. + +### 2.4 Size limits + +A conformant mandate document MUST NOT exceed 16 KiB after canonicalization. Resolvers MAY reject larger mandates with `AMDP_MANDATE_TOO_LARGE`. + +--- + +## 3. Verticals Taxonomy v0.1 + +A vertical is a bounded domain with shared action and constraint semantics. Initial v0.1 verticals are listed below. The taxonomy is maintained as an IANA-style registry; additions go through the AMDP governance process (see [README.md](README.md#contributing)). + +| Identifier | Description | Compatible transaction protocols | +|------------|-------------|----------------------------------| +| `advertising` | Agent-mediated advertising operations: media-buying, creative submission, campaign management. | AdCP, UCP, ACP | +| `procurement` | B2B purchasing: vendor selection, order approval, terms negotiation. | UCP, ACP, native procurement APIs | +| `equity-research` | Financial research and investment-decision delegation. | Native broker APIs, custodian APIs | +| `public-services` | Government / civic / agency interactions: form submission, record queries, request handling. | National e-government protocols, OAuth-based portals | + +Each vertical's semantics are defined by the vertical's owner(s). The AMDP spec defines only that the vertical-action pair MUST be consistent and that constraints MUST be evaluable. + +### 3.1 Adding a new vertical + +A new vertical requires: + +1. A stable identifier (kebab-case, lowercase ASCII). +2. A description of the bounded domain. +3. At least one defined action with semantics. +4. At least one defined constraint with evaluation rules. +5. Identification of at least one transaction protocol the vertical is compatible with. +6. A community review through the contribution process. + +--- + +## 4. Actions Taxonomy v0.1 + +The initial v0.1 action set per vertical. Action identifiers are kebab-case ASCII, scoped to their vertical (an action's semantics are vertical-specific). + +### 4.1 advertising + +| Action | Description | +|--------|-------------| +| `create_media_buy` | Initiate a paid media placement on behalf of the principal. Subject to `max_amount` constraint. | +| `pause_campaign` | Halt an active campaign. | +| `submit_creative` | Upload creative assets (image, video, copy) for a campaign. | +| `approve_invoice` | Approve a billing line item from a publisher or exchange. Subject to `max_amount`. | + +### 4.2 procurement + +| Action | Description | +|--------|-------------| +| `approve_order` | Approve a purchase order. Subject to `max_amount`, `vendor_whitelist`. | +| `select_vendor` | Choose a vendor from an approved list for a quote request. | +| `negotiate_terms` | Initiate or respond in a terms negotiation. Read/propose only; the principal retains final approval unless explicitly delegated. | + +### 4.3 equity-research + +| Action | Description | +|--------|-------------| +| `make_investment_decision` | Execute a buy/sell/hold decision against a brokerage or custodian. Subject to `max_amount`, `asset_classes`. | +| `rebalance_portfolio` | Adjust portfolio allocations within an explicit policy. Subject to `max_amount` per individual trade. | +| `subscribe_research_report` | Subscribe to or unsubscribe from a research subscription. | + +### 4.4 public-services + +| Action | Description | +|--------|-------------| +| `submit_request` | File a form or request with a public-service portal. | +| `approve_form` | Acknowledge or approve a returned form on behalf of the principal. | +| `query_records` | Read public records on behalf of the principal. Subject to `data_classes`. | + +### 4.5 Action conformance rules + +- An action identifier in `scope.actions` MUST be defined for the `scope.vertical`. Resolvers MUST return `AMDP_OUT_OF_SCOPE` for an action-vertical mismatch. +- An action MUST be evaluable by the resolver. Resolvers MAY refuse to verify mandates referencing unknown actions with `AMDP_UNKNOWN_ACTION`. +- Action semantics are owned by the vertical's spec maintainers, not the AMDP spec. AMDP defines only the identifier and the cross-cutting verification surface. + +--- + +## 5. Constraints Taxonomy v0.1 + +Constraints are conditions a resolver evaluates against a proposed action context. All listed constraints MUST be satisfied for the mandate to authorize the action. + +### 5.1 `max_amount` — Monetary cap + +```json +{ + "max_amount": { + "value": 50000, + "currency": "CHF" + } +} +``` + +| Field | Type | Description | +|-------|------|-------------| +| `value` | number (>0) | Maximum cumulative amount in the specified currency. Resolvers MAY enforce per-action or cumulative semantics; the mandate SHOULD specify which via `mode`. | +| `currency` | ISO 4217 code | Currency. Supported: `USD`, `EUR`, `CHF`, `GBP`, `JPY`, `AUD`, `CAD`. | +| `mode` | enum (optional) | `per_action` (default) or `cumulative`. | + +### 5.2 `time_window` — Temporal validity + +```json +{ + "time_window": { + "from": "2026-05-17T00:00:00Z", + "to": "2026-08-17T23:59:59Z" + } +} +``` + +| Field | Type | Description | +|-------|------|-------------| +| `from` | RFC 3339 timestamp | Earliest moment the action MAY occur. | +| `to` | RFC 3339 timestamp | Latest moment the action MAY occur. MUST be later than `from`. | + +`time_window` is in addition to `expires_at` on the mandate itself; the action time MUST be within BOTH windows. + +### 5.3 `vendor_whitelist` / `vendor_blacklist` — Counterparty restriction + +```json +{ + "vendor_whitelist": ["vendor-a.example", "*.trusted-vendor.example"] +} +``` + +| Field | Type | Description | +|-------|------|-------------| +| `vendor_whitelist` | array of patterns | Only matching vendors are permitted. Patterns support wildcard `*.` prefix. | +| `vendor_blacklist` | array of patterns | Matching vendors are forbidden. | + +A mandate MUST NOT specify both `vendor_whitelist` and `vendor_blacklist`. Resolvers MUST return `AMDP_CONSTRAINT_VIOLATION` if both are present. + +### 5.4 `asset_classes` / `categories` — Subject-matter restriction + +```json +{ + "asset_classes": ["mining-equity", "energy-equity"] +} +``` + +```json +{ + "categories": ["office-supplies", "software-subscriptions"] +} +``` + +Asset-class identifiers (for `equity-research`) and category identifiers (for `procurement`, `advertising`) are vertical-specific. Resolvers MUST return `AMDP_CONSTRAINT_VIOLATION` for an action whose subject is not in the listed set. + +### 5.5 `geo_regions` — Geographic restriction + +```json +{ + "geo_regions": ["CH", "DE", "AT", "EU"] +} +``` + +Identifiers are ISO 3166-1 alpha-2 codes, plus the special supranational codes `EU`, `EEA`, `EFTA`, `UK`. The action's geo-context MUST be in the listed set. + +### 5.6 `data_classes` — Data-sensitivity restriction (public-services) + +```json +{ + "data_classes": ["public", "personal-self-only"] +} +``` + +| Class | Description | +|-------|-------------| +| `public` | Public records (no sensitivity restriction). | +| `personal-self-only` | Records about the principal themselves. | +| `personal-family` | Records about the principal's declared family members (requires separate proof). | +| `pii` | General PII access (NOT permitted by default; requires explicit class). | +| `phi` | Protected Health Information (NOT permitted by default; requires explicit class). | +| `financial` | Financial records (NOT permitted by default; requires explicit class). | + +### 5.7 Constraint conformance rules + +- Constraints are AND-combined: every listed constraint MUST be satisfied. +- Unknown constraint keys MUST cause resolvers to return `AMDP_UNKNOWN_CONSTRAINT` (fail-closed). +- Constraint evaluation happens server-side at the resolver. Verifiers MUST NOT bypass the resolver and locally compute constraint satisfaction (see SECURITY.md threat T3). + +--- + +## 6. Signature Algorithms + +AMDP supports three signature algorithms. Implementations MUST support `hybrid-ed25519-mldsa65` for new mandates; pure `ed25519` is supported for legacy interop, and pure `ml-dsa-65` is reserved for post-quantum-only environments. + +### 6.1 `hybrid-ed25519-mldsa65` (RECOMMENDED) + +A composite signature scheme: the mandate is signed with both Ed25519 ([RFC 8032](https://www.rfc-editor.org/rfc/rfc8032)) and ML-DSA-65 ([FIPS 204](https://csrc.nist.gov/pubs/fips/204/final)). Both signatures are concatenated and encoded. + +Rationale (per [ADR-025](https://github.com/Baldri/nexbid/blob/main/docs/knowledge-base/adr/025-crypto-agility-and-pqc-migration.md)): + +- **Classical-validation-fallback:** If a future weakness is found in ML-DSA-65 before its broad deployment, the Ed25519 signature still provides classical security. +- **PQC-readiness:** When sufficiently capable quantum computers exist (CRQC), the ML-DSA-65 signature remains valid. +- **No window of vulnerability:** A hybrid signature is no weaker than the strongest of its components. + +Verifiers MUST validate BOTH signatures. If either fails, the verification result is invalid. + +### 6.2 `ed25519` (legacy interop) + +Pure Ed25519 signature per RFC 8032. Permitted for compatibility with implementations not yet supporting PQC. Implementations producing new mandates SHOULD use `hybrid-ed25519-mldsa65`. + +### 6.3 `ml-dsa-65` (PQC-only) + +Pure ML-DSA-65 signature per FIPS 204. Permitted in environments where classical signatures are explicitly disallowed. NOT recommended for general deployment in v0.1 because of limited library availability. + +### 6.4 Signing format: COSE_Sign1 + +All AMDP signatures use COSE_Sign1 ([RFC 9052](https://www.rfc-editor.org/rfc/rfc9052)) as the wrapping format. The payload is the canonical JSON-serialized mandate document (per section 2.3) with the `signature` field removed. The COSE_Sign1 structure carries the algorithm identifier, an optional `kid` (key ID), and the signature value(s). + +For `hybrid-ed25519-mldsa65`, the COSE_Sign1 payload contains both signature values in a deterministic order: Ed25519 first, ML-DSA-65 second. + +### 6.5 Public-key distribution: JWKS + +Principal public keys are distributed via JSON Web Key Sets ([RFC 7517](https://www.rfc-editor.org/rfc/rfc7517)) at `principal.verifier_url`. The JWKS MUST contain a key entry for each algorithm the principal signs with, identified by `kid`. + +Verifiers MUST cache JWKS responses per their `Cache-Control` header, but MUST NOT cache for longer than 24 hours. Verifiers MUST re-fetch JWKS on signature verification failure (handles key rotation). + +### 6.6 Key rotation + +Principals MAY rotate keys at any time by publishing an updated JWKS. Mandates signed with rotated-out keys remain valid until their `expires_at`, unless explicitly revoked. + +--- + +## 7. Resolver Endpoint Spec + +A resolver is an HTTP service operated by (or on behalf of) the principal. Its purpose is to verify mandates and answer revocation queries. + +### 7.1 OpenAPI 3.1 inline + +```yaml +openapi: 3.1.0 +info: + title: AMDP Resolver Endpoint + version: 0.1.0 +paths: + /.well-known/amdp/verify: + get: + summary: Verify a mandate + parameters: + - name: mandate_id + in: query + required: true + schema: + type: string + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ + - name: action + in: query + required: false + description: | + Specific action being proposed. If provided, the resolver checks + whether this action is within mandate scope. + schema: + type: string + - name: context + in: query + required: false + description: | + Base64url-encoded JSON of the action context (e.g. amount, vendor, + geo). Used by the resolver to evaluate constraints. + schema: + type: string + responses: + '200': + description: Mandate is valid + content: + application/json: + schema: + type: object + required: [valid, constraints_match, remaining_actions] + properties: + valid: + type: boolean + reason: + type: [string, "null"] + constraints_match: + type: boolean + remaining_actions: + type: array + items: + type: string + '401': + description: Signature verification failed + '403': + description: Mandate has expired + '404': + description: Unknown mandate ID + '410': + description: Mandate has been revoked + /.well-known/amdp/revoke: + post: + summary: Revoke a mandate (principal-only) + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [mandate_id, principal_signature] + properties: + mandate_id: + type: string + principal_signature: + type: object + required: [algorithm, value] + description: | + A signature over the canonical revocation-request JSON, + produced by the principal's current signing key. The + signature scheme MUST match section 6. + responses: + '200': + description: Revocation accepted + '401': + description: Revocation request signature invalid + '404': + description: Unknown mandate ID +``` + +### 7.2 Response shape + +A `200 OK` response from `/verify` has the following shape: + +```json +{ + "valid": true, + "reason": null, + "constraints_match": true, + "remaining_actions": ["make_investment_decision"] +} +``` + +- `valid` — Boolean. The mandate's signature, freshness, and (if `action` was provided) action-scope check all passed. +- `reason` — String or null. If `valid=false` but the response is still `200`, `reason` explains why (e.g. `"constraints_match=false: amount exceeds max_amount"`). +- `constraints_match` — Boolean. If `context` was provided, this reflects the constraint evaluation outcome. +- `remaining_actions` — Array of actions in the mandate's scope that are still permitted (after subtracting any single-use actions that have been consumed). + +### 7.3 Caching + +Resolvers SHOULD respond with `Cache-Control: max-age=60, public` on successful verifies and `Cache-Control: no-store` on failed verifies. Verifiers MAY cache successful verifications for the indicated duration, but MUST re-verify on revocation status changes (out-of-band signal via webhook, or polling). + +### 7.4 Error response shape + +Non-200 responses use the [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) Problem Details format: + +```json +{ + "type": "https://protocol-commerce.dev/amdp/errors/expired", + "title": "Mandate has expired", + "status": 403, + "detail": "Mandate 01904ad8-... expired at 2026-04-30T00:00:00Z", + "instance": "/verify?mandate_id=01904ad8-..." +} +``` + +### 7.5 Revocation semantics + +A revocation is a one-way transition: once revoked, a mandate cannot return to valid. Revocation MUST be: + +- Authenticated — only the principal (proven via signature with a current JWKS key) MAY revoke. +- Atomic — visible to all subsequent `/verify` calls. +- Permanent — recorded in the resolver's persistent state. +- Distributed via the audit-trail endpoint if one is configured. + +--- + +## 8. Discovery Endpoint Spec + +Discovery is OPTIONAL. Its purpose is to let an agent find a resolver supporting a specific (vertical, action) pair when the resolver URL is not embedded in the mandate. + +### 8.1 OpenAPI 3.1 inline + +```yaml +openapi: 3.1.0 +info: + title: AMDP Discovery Endpoint + version: 0.1.0 +paths: + /.well-known/amdp/discover: + get: + summary: Discover resolvers supporting a vertical-action pair + parameters: + - name: vertical + in: query + required: true + schema: + type: string + enum: + - advertising + - procurement + - equity-research + - public-services + - name: action + in: query + required: false + schema: + type: string + responses: + '200': + description: List of resolvers + headers: + Cache-Control: + schema: + type: string + example: "public, max-age=3600" + ETag: + schema: + type: string + content: + application/json: + schema: + type: object + required: [resolvers] + properties: + resolvers: + type: array + items: + type: object + required: [resolver_url, supported_verticals, jwks_url] + properties: + resolver_url: + type: string + format: uri + supported_verticals: + type: array + items: + type: string + supported_actions: + type: array + items: + type: string + jwks_url: + type: string + format: uri + operator: + type: string + description: Human-readable operator name +``` + +### 8.2 Federation + +Discovery endpoints MAY federate by including resolvers operated by other parties. A discovery endpoint that aggregates entries SHOULD document its aggregation policy. + +### 8.3 Caching + +`/discover` responses SHOULD be cacheable for up to 1 hour. Discovery endpoints SHOULD support ETag conditional GETs. + +--- + +## 9. Error Codes + +AMDP uses a stable set of error codes carried in the `type` field of Problem Details responses (section 7.4) and in resolver-side logging. + +| Code | HTTP status | Meaning | +|------|-------------|---------| +| `AMDP_INVALID_SIGNATURE` | 401 | Mandate signature failed verification against the principal's current JWKS. | +| `AMDP_INVALID_SCHEMA` | 400 | Mandate document does not conform to the schema in section 2. | +| `AMDP_EXPIRED` | 403 | Current time is after `expires_at`. | +| `AMDP_NOT_YET_VALID` | 403 | Current time is before `issued_at` (or before `time_window.from` if set). | +| `AMDP_REVOKED` | 410 | Mandate was revoked. | +| `AMDP_NOT_FOUND` | 404 | Resolver has no record of this `mandate_id`. | +| `AMDP_OUT_OF_SCOPE` | 422 | Proposed action is not in `scope.actions` or not valid for `scope.vertical`. | +| `AMDP_CONSTRAINT_VIOLATION` | 422 | Proposed action context fails one or more constraints. | +| `AMDP_UNKNOWN_VERTICAL` | 422 | `scope.vertical` is not in the resolver's known taxonomy. | +| `AMDP_UNKNOWN_ACTION` | 422 | `scope.actions` contains an action the resolver does not recognize for the vertical. | +| `AMDP_UNKNOWN_CONSTRAINT` | 422 | `scope.constraints` contains a constraint key the resolver does not recognize. Fail-closed. | +| `AMDP_VERSION_INCOMPATIBLE` | 426 | `amdp_version` is from a major version the resolver does not support. | +| `AMDP_MANDATE_TOO_LARGE` | 413 | Mandate document exceeds the 16 KiB limit. | +| `AMDP_DELEGATION_WIDENS_SCOPE` | 422 | A delegation-chain link attempts to widen scope vs. its parent. | +| `AMDP_RATE_LIMITED` | 429 | Verifier is sending too many requests. | +| `AMDP_INTERNAL` | 500 | Resolver-side error. Verifiers MAY retry with exponential backoff. | + +--- + +## 10. Versioning Policy + +AMDP follows [Semantic Versioning](https://semver.org/) at the spec level. + +### 10.1 Version semantics + +- **MAJOR (`X.0.0`):** Incompatible schema, transport, or semantics change. Existing mandates may need to be reissued. Resolvers MAY reject mandates with unsupported major versions (`AMDP_VERSION_INCOMPATIBLE`). +- **MINOR (`0.X.0`):** Backward-compatible additions (new verticals, new actions, new constraints, new optional fields). v0.x is in Draft phase; minor versions MAY introduce breaking changes until v1.0.0. +- **PATCH (`0.0.X`):** Clarifications, editorial fixes, non-normative corrections. + +### 10.2 Migration path + +When a new major version is published: + +1. A migration note is added to [CHANGELOG.md](CHANGELOG.md) documenting all breaking changes. +2. The previous major version remains supported by published resolvers for at least 12 months. +3. Mandates with `amdp_version` from the previous major version remain verifiable for the duration of their `expires_at`, even after the support window closes. + +### 10.3 Pre-1.0 contract + +Until v1.0.0 is published, the following looser rules apply: + +- Breaking changes between MINOR versions are explicitly permitted. +- Every MINOR version increment carries a migration note in CHANGELOG.md. +- Examples in [examples/](examples/) are updated atomically with each MINOR version. +- Implementers SHOULD pin to a specific MINOR version and upgrade explicitly. + +### 10.4 Negotiating versions across the protocol + +A verifier presented with a mandate at `amdp_version=X.Y.Z`: + +1. MUST validate the schema for that version. +2. MAY refuse to verify mandates whose major version differs from the resolver's supported major versions. +3. SHOULD accept any minor version within a supported major. + +Resolvers SHOULD advertise their supported version range via the `/.well-known/amdp/discover` response (an extension field, planned for v0.2.0). + +--- + +End of normative specification. See [CONFORMANCE.md](CONFORMANCE.md) for testable requirements, and [SECURITY.md](SECURITY.md) for the threat model. diff --git a/amdp-spec/examples/README.md b/amdp-spec/examples/README.md new file mode 100644 index 0000000..287d468 --- /dev/null +++ b/amdp-spec/examples/README.md @@ -0,0 +1,37 @@ +# AMDP — Example Mandates + +**Version:** 0.1.0 +**Status:** Draft +**License:** MIT + +This folder contains five reference mandate documents covering the breadth of AMDP v0.1.0. Each example pairs a `*.json` mandate file with a `*.md` companion explaining the scenario, the constraints, and why the mandate is shaped this way. + +## Index + +| Example | Vertical | Scenario | +|---------|----------|----------| +| [advertising-publisher-mandate](advertising-publisher-mandate.json) ([explanation](advertising-publisher-mandate.md)) | `advertising` | Brand X authorizes agent for CHF 5'000 Q2 media buy. | +| [equity-research-family-office](equity-research-family-office.json) ([explanation](equity-research-family-office.md)) | `equity-research` | Family office authorizes agent for mining-equity decisions up to USD 500'000. | +| [procurement-cross-vendor](procurement-cross-vendor.json) ([explanation](procurement-cross-vendor.md)) | `procurement` | Company authorizes agent for whitelisted-vendor orders up to USD 50'000. | +| [multi-vertical-family-office](multi-vertical-family-office.json) ([explanation](multi-vertical-family-office.md)) | `equity-research` + `procurement` (via sub-delegation) | Family-office parent mandate plus a narrowed procurement sub-mandate. | +| [public-services-citizen-request](public-services-citizen-request.json) ([explanation](public-services-citizen-request.md)) | `public-services` | Citizen authorizes agent for tax-form submissions to a municipal portal. | + +## Validation + +All `*.json` files in this folder MUST validate against the Mandate Document JSON Schema in [SPECIFICATION.md section 2.1](../SPECIFICATION.md#2-mandate-document-schema). You can validate locally with `ajv`: + +```bash +# Extract schema (one-time, from SPECIFICATION.md) +# Then validate each example: +npx ajv-cli validate -s mandate.schema.json -d examples/*.json +``` + +Signature values in these examples are placeholders (string `"base64url:placeholder-..."`) — they are NOT valid signatures. Real signatures require principal key material, which is not included. A future `examples/test-vectors/` folder will include signed mandates with bundled key material for end-to-end signature-verification testing (planned for v0.2.0). + +## How to use + +These examples are pedagogical, not production-ready. Implementers SHOULD: + +1. Read the companion `.md` file to understand the scenario. +2. Inspect the JSON to see how vertical, actions, constraints, and metadata combine. +3. Generate their own mandates using their issuer's key material — do NOT copy mandate IDs or DIDs from these examples. diff --git a/amdp-spec/examples/advertising-publisher-mandate.json b/amdp-spec/examples/advertising-publisher-mandate.json new file mode 100644 index 0000000..8ce7f96 --- /dev/null +++ b/amdp-spec/examples/advertising-publisher-mandate.json @@ -0,0 +1,51 @@ +{ + "amdp_version": "0.1.0", + "mandate_id": "01904ad8-5e1e-7d2a-8b1c-4f5e6a7b8c9d", + "principal": { + "id": "did:web:brand-x.example", + "verifier_url": "https://brand-x.example/.well-known/amdp/jwks", + "display_name": "Brand X Marketing GmbH" + }, + "agent": { + "id": "did:web:nexbid.dev/agents/media-buyer-bot-7", + "name": "Media Buyer Bot 7", + "model": "anthropic/claude-opus-4-7" + }, + "scope": { + "vertical": "advertising", + "actions": [ + "create_media_buy", + "submit_creative", + "approve_invoice" + ], + "constraints": { + "max_amount": { + "value": 5000, + "currency": "CHF", + "mode": "cumulative" + }, + "time_window": { + "from": "2026-04-01T00:00:00Z", + "to": "2026-06-30T23:59:59Z" + }, + "categories": [ + "food-beverages", + "household-goods" + ], + "geo_regions": [ + "CH", + "DE", + "AT" + ] + } + }, + "issued_at": "2026-03-28T14:00:00Z", + "expires_at": "2026-06-30T23:59:59Z", + "revocation_url": "https://brand-x.example/.well-known/amdp/verify", + "audit_trail_endpoint": "https://brand-x.example/.well-known/amdp/audit", + "signature": { + "algorithm": "hybrid-ed25519-mldsa65", + "value": "base64url:placeholder-ed25519-sig-followed-by-mldsa65-sig", + "key_id": "brand-x-2026-q2" + } +} diff --git a/amdp-spec/examples/advertising-publisher-mandate.md b/amdp-spec/examples/advertising-publisher-mandate.md new file mode 100644 index 0000000..ce82fb0 --- /dev/null +++ b/amdp-spec/examples/advertising-publisher-mandate.md @@ -0,0 +1,44 @@ +# Example — Advertising Publisher Mandate + +**File:** [advertising-publisher-mandate.json](advertising-publisher-mandate.json) +**Vertical:** `advertising` +**License:** MIT + +## Scenario + +Brand X Marketing GmbH wants to delegate Q2 2026 media-buying decisions to an autonomous agent (Media Buyer Bot 7, running on Claude Opus 4.7). The agent is authorized to: + +- Create paid media placements (`create_media_buy`). +- Upload creative assets (`submit_creative`). +- Approve publisher invoices (`approve_invoice`). + +The agent is NOT authorized to pause campaigns — that requires a separate mandate (or principal action). + +## Constraints + +- **`max_amount: 5000 CHF cumulative`** — Total spend across all actions over the mandate lifetime cannot exceed CHF 5'000. This is the strictest version of the constraint; once exhausted, further actions are denied with `AMDP_CONSTRAINT_VIOLATION` even if other constraints pass. +- **`time_window: 2026-04-01 to 2026-06-30`** — The agent may only act during Q2 2026. Note that `time_window` is independent of `expires_at`, which here happens to coincide. +- **`categories: [food-beverages, household-goods]`** — Only campaigns in these product categories are authorized. A campaign targeting (say) electronics or pharmaceuticals is denied. +- **`geo_regions: [CH, DE, AT]`** — Campaigns may only target Switzerland, Germany, or Austria (DACH region). + +## Why this shape + +This mandate models a realistic mid-market brand-agent scenario: + +- **Vertical-scoped:** Only `advertising` actions. A publisher-side `approve_form` action from `public-services` would be rejected with `AMDP_OUT_OF_SCOPE`. +- **Bounded financial risk:** CHF 5'000 cumulative cap caps total exposure even if the agent or its runtime is compromised. +- **Time-bounded:** Three-month window matches a Q2 campaign cycle. Renewal requires a fresh mandate (and a fresh principal-side review). +- **Audit-trail-enabled:** The `audit_trail_endpoint` lets Brand X monitor agent actions in near-real-time, which is good practice for high-frequency action verticals like advertising. + +## Verification flow + +A publisher's auction server (verifier) receives an agent request to create a CHF 200 media buy for category `food-beverages` in Switzerland. The verifier: + +1. Validates the mandate against the JSON Schema in SPECIFICATION.md section 2.1. +2. Fetches the JWKS at `https://brand-x.example/.well-known/amdp/jwks`. +3. Verifies the hybrid signature (both Ed25519 and ML-DSA-65 components). +4. Calls `GET https://brand-x.example/.well-known/amdp/verify?mandate_id=01904ad8-...&action=create_media_buy&context=`. +5. Receives `200 {"valid": true, "constraints_match": true, "remaining_actions": [...]}`. +6. Proceeds with the auction. + +The verifier also sends an audit event to `https://brand-x.example/.well-known/amdp/audit` with `event_type=verify_accepted`. diff --git a/amdp-spec/examples/equity-research-family-office.json b/amdp-spec/examples/equity-research-family-office.json new file mode 100644 index 0000000..15cacff --- /dev/null +++ b/amdp-spec/examples/equity-research-family-office.json @@ -0,0 +1,54 @@ +{ + "amdp_version": "0.1.0", + "mandate_id": "01904b1a-7a3c-7e5d-9c2e-1f8a4b5c6d7e", + "principal": { + "id": "did:web:family-office-alpha.example", + "verifier_url": "https://family-office-alpha.example/.well-known/amdp/jwks", + "display_name": "Family Office Alpha" + }, + "agent": { + "id": "did:web:research-firm.example/agents/mining-equity-researcher", + "name": "Mining Equity Researcher", + "model": "anthropic/claude-opus-4-7" + }, + "scope": { + "vertical": "equity-research", + "actions": [ + "make_investment_decision", + "rebalance_portfolio", + "subscribe_research_report" + ], + "constraints": { + "max_amount": { + "value": 500000, + "currency": "USD", + "mode": "per_action" + }, + "time_window": { + "from": "2026-05-17T00:00:00Z", + "to": "2026-11-17T00:00:00Z" + }, + "asset_classes": [ + "mining-equity", + "energy-equity", + "rare-earth-equity" + ], + "geo_regions": [ + "CA", + "AU", + "CH", + "EU", + "UK" + ] + } + }, + "issued_at": "2026-05-17T10:00:00Z", + "expires_at": "2026-11-17T00:00:00Z", + "revocation_url": "https://family-office-alpha.example/.well-known/amdp/verify", + "audit_trail_endpoint": "https://family-office-alpha.example/.well-known/amdp/audit", + "signature": { + "algorithm": "hybrid-ed25519-mldsa65", + "value": "base64url:placeholder-ed25519-sig-followed-by-mldsa65-sig", + "key_id": "family-office-alpha-2026-h2" + } +} diff --git a/amdp-spec/examples/equity-research-family-office.md b/amdp-spec/examples/equity-research-family-office.md new file mode 100644 index 0000000..2c5ddfd --- /dev/null +++ b/amdp-spec/examples/equity-research-family-office.md @@ -0,0 +1,47 @@ +# Example — Equity-Research Family Office + +**File:** [equity-research-family-office.json](equity-research-family-office.json) +**Vertical:** `equity-research` +**License:** MIT + +## Scenario + +Family Office Alpha delegates mining/energy/rare-earth equity research to an external research firm's agent (Mining Equity Researcher, on Claude Opus 4.7). The agent may: + +- Execute buy/sell/hold decisions (`make_investment_decision`). +- Rebalance the portfolio within the policy (`rebalance_portfolio`). +- Subscribe to or unsubscribe from research reports (`subscribe_research_report`). + +This is the canonical use case discussed in ADR-040: cross-vertical mandate clarity for high-stakes financial agent delegation. + +## Constraints + +- **`max_amount: 500'000 USD per_action`** — Each individual trade decision is capped at USD 500'000. The `mode=per_action` semantics differ from `cumulative`: the agent could execute many CAD 500'000 trades over six months, as long as no single decision exceeds the cap. +- **`time_window: 2026-05-17 to 2026-11-17`** — Six-month delegation, matching a typical research-engagement cycle. +- **`asset_classes: [mining-equity, energy-equity, rare-earth-equity]`** — Sector-bounded. A decision on (say) software equity would be rejected with `AMDP_CONSTRAINT_VIOLATION`. +- **`geo_regions: [CA, AU, CH, EU, UK]`** — Restricted to jurisdictions where Family Office Alpha holds custody arrangements. + +## Why this shape + +- **`mode=per_action` for max_amount** is appropriate here because individual investment decisions are discrete and reviewable. A `cumulative` cap would prevent the agent from running an active strategy over a six-month window. +- **Sector-bounded by `asset_classes`** keeps the agent inside its declared competence area. A research firm specializing in mining equity should not be authorized to trade software equity, even within the cap. +- **Geographic constraint** reflects custody / regulatory realities. The Family Office's prime broker may only support specific jurisdictions. +- **Audit trail is critical** for regulated verticals; the `audit_trail_endpoint` lets the principal monitor trade signals against this mandate alongside their internal compliance system. +- **Six-month `expires_at`** is at the upper end of advisable for high-stakes mandates. SECURITY.md recommends shorter windows for highest-stakes scenarios; six months is a pragmatic compromise here. + +## Verification flow + +The research firm's execution platform (verifier) receives an agent request to make a `make_investment_decision` for USD 480'000 in `mining-equity` traded on the TSX (Canada, CA). The verifier: + +1. Validates schema. +2. Fetches JWKS. +3. Verifies hybrid signature. +4. Calls `/verify?mandate_id=01904b1a-...&action=make_investment_decision&context=`. +5. Receives `200 {"valid": true, "constraints_match": true, ...}`. +6. Routes the order to the broker. + +If the agent had instead proposed USD 600'000 (above the cap), the resolver would return `200 {"valid": false, "reason": "constraints_match=false: amount exceeds max_amount", "constraints_match": false, ...}` and the verifier would deny the trade. + +## Note on sub-delegation + +This mandate is the parent in [multi-vertical-family-office.json](multi-vertical-family-office.json) — see that example for how Family Office Alpha sub-delegates a narrow procurement scope while keeping this equity-research scope intact. diff --git a/amdp-spec/examples/multi-vertical-family-office.json b/amdp-spec/examples/multi-vertical-family-office.json new file mode 100644 index 0000000..8c62872 --- /dev/null +++ b/amdp-spec/examples/multi-vertical-family-office.json @@ -0,0 +1,57 @@ +{ + "amdp_version": "0.1.0", + "mandate_id": "01904d3e-ac5b-7c7f-b4ef-3f9c6d7e8f9a", + "principal": { + "id": "did:web:family-office-alpha.example", + "verifier_url": "https://family-office-alpha.example/.well-known/amdp/jwks", + "display_name": "Family Office Alpha" + }, + "agent": { + "id": "did:web:research-firm.example/agents/mining-equity-researcher", + "name": "Mining Equity Researcher", + "model": "anthropic/claude-opus-4-7" + }, + "scope": { + "vertical": "procurement", + "actions": [ + "approve_order" + ], + "constraints": { + "max_amount": { + "value": 10000, + "currency": "USD", + "mode": "per_action" + }, + "time_window": { + "from": "2026-05-17T00:00:00Z", + "to": "2026-11-17T00:00:00Z" + }, + "vendor_whitelist": [ + "research-data-feeds.example", + "*.bloomberg.example", + "*.refinitiv.example" + ], + "categories": [ + "research-data-subscriptions", + "research-software" + ], + "geo_regions": [ + "CH", + "EU", + "UK" + ] + } + }, + "issued_at": "2026-05-17T10:15:00Z", + "expires_at": "2026-11-17T00:00:00Z", + "revocation_url": "https://family-office-alpha.example/.well-known/amdp/verify", + "audit_trail_endpoint": "https://family-office-alpha.example/.well-known/amdp/audit", + "delegation_chain": [ + "01904b1a-7a3c-7e5d-9c2e-1f8a4b5c6d7e" + ], + "signature": { + "algorithm": "hybrid-ed25519-mldsa65", + "value": "base64url:placeholder-ed25519-sig-followed-by-mldsa65-sig", + "key_id": "family-office-alpha-2026-h2" + } +} diff --git a/amdp-spec/examples/multi-vertical-family-office.md b/amdp-spec/examples/multi-vertical-family-office.md new file mode 100644 index 0000000..36ef98c --- /dev/null +++ b/amdp-spec/examples/multi-vertical-family-office.md @@ -0,0 +1,67 @@ +# Example — Multi-Vertical Family Office (Sub-Delegated) + +**File:** [multi-vertical-family-office.json](multi-vertical-family-office.json) +**Verticals involved:** `equity-research` (parent) + `procurement` (this mandate) +**License:** MIT + +## Scenario + +Family Office Alpha has already issued the Mining Equity Researcher agent an `equity-research` mandate (see [equity-research-family-office.json](equity-research-family-office.json), `mandate_id: 01904b1a-...`). + +The same agent now needs to autonomously subscribe to research-data feeds (Bloomberg, Refinitiv, etc.) to do its research. That's a procurement action — distinct vertical — but tightly coupled to the equity-research mandate's purpose. + +Rather than issue a wholly independent mandate, the family office issues a **sub-delegated** procurement mandate that references the equity-research mandate as its parent. This documents the chain of authority and lets verifiers in the procurement vertical see why this mandate exists. + +## Constraints + +- **`max_amount: 10'000 USD per_action`** — Much smaller cap than the parent equity-research mandate's USD 500'000. Sub-mandates MUST narrow scope, never widen it. +- **`time_window` matches the parent** — Cannot extend beyond the parent's time bounds. +- **`vendor_whitelist`** — Restricted to data-feed and research-data vendors only. No general procurement authority. +- **`categories`** — Restricted to `research-data-subscriptions` and `research-software`. No office supplies, no industrial parts, no anything-else. +- **`geo_regions: [CH, EU, UK]`** — Narrowed from the parent's broader `[CA, AU, CH, EU, UK]`. Procurement happens in the family office's home jurisdiction. + +## The delegation chain + +```json +"delegation_chain": ["01904b1a-7a3c-7e5d-9c2e-1f8a4b5c6d7e"] +``` + +This single-element chain points back to the equity-research mandate. A resolver verifying THIS mandate MUST: + +1. Verify this mandate's own signature. +2. Resolve every link in the chain (here: just `01904b1a-...`). +3. Verify that this mandate's scope is strictly NARROWER than its parent. + +The "narrower" check is non-trivial across verticals. AMDP v0.1.0 takes the conservative position that: + +- The vertical MAY differ from parent if the actions are operationally subordinate to the parent's purpose. (Buying research data IS subordinate to making research decisions.) +- The `max_amount` MUST be lower than the parent's. +- The `time_window` MUST be a subset of the parent's. +- Vendor/category/geo restrictions MUST be narrower or equal. + +A future v0.2.0 will tighten cross-vertical sub-delegation semantics — see open items in [CHANGELOG.md](../CHANGELOG.md). + +## Why use sub-delegation here + +- **Audit trail clarity:** A procurement verifier sees both this mandate and its parent, understanding that the data purchase is in service of an equity-research delegation. +- **Single revocation point:** If Family Office Alpha revokes the parent equity-research mandate, this child mandate also becomes ineffective in practice (the agent's reason for procurement disappears). Resolvers MAY treat parent revocation as implicit child revocation. +- **Tighter blast-radius bound:** Even if this sub-mandate is somehow misused, the tight USD 10'000 cap + narrow vendor list limits damage. + +## Anti-pattern: scope widening + +If this sub-mandate's `max_amount` were USD 1'000'000 instead of USD 10'000, a resolver MUST reject it with `AMDP_DELEGATION_WIDENS_SCOPE` because USD 1'000'000 > the parent's USD 500'000 cap (even though the parent is in a different vertical, both `max_amount` constraints translate to a comparable financial exposure dimension). + +If the sub-mandate were `geo_regions: [US, CN, JP]` (none of which appear in the parent's `[CA, AU, CH, EU, UK]`), a resolver MUST reject it. + +## Verification flow + +A data-feed vendor (e.g. `bloomberg-data.example`) receives an order from the agent for a USD 8'000 quarterly research-data subscription. The verifier: + +1. Validates the schema. +2. Fetches the JWKS (one fetch — same principal as the parent). +3. Verifies this mandate's hybrid signature. +4. Notices the `delegation_chain` field. Calls `/verify?mandate_id=01904b1a-...` to confirm the parent is still valid (not revoked, not expired). +5. Verifies that this child's scope is strictly narrower than parent on every dimension. +6. Calls `/verify?mandate_id=01904d3e-...&action=approve_order&context=`. +7. Receives `200 {"valid": true, "constraints_match": true, ...}`. +8. Accepts the order. diff --git a/amdp-spec/examples/procurement-cross-vendor.json b/amdp-spec/examples/procurement-cross-vendor.json new file mode 100644 index 0000000..05c7c00 --- /dev/null +++ b/amdp-spec/examples/procurement-cross-vendor.json @@ -0,0 +1,60 @@ +{ + "amdp_version": "0.1.0", + "mandate_id": "01904c2d-9b4a-7f6e-a3df-2e9b5c6d7e8f", + "principal": { + "id": "did:web:acme-industries.example", + "verifier_url": "https://acme-industries.example/.well-known/amdp/jwks", + "display_name": "ACME Industries AG" + }, + "agent": { + "id": "did:web:acme-industries.example/agents/procurement-assistant", + "name": "Procurement Assistant", + "model": "anthropic/claude-sonnet-4-7" + }, + "scope": { + "vertical": "procurement", + "actions": [ + "approve_order", + "select_vendor", + "negotiate_terms" + ], + "constraints": { + "max_amount": { + "value": 50000, + "currency": "USD", + "mode": "per_action" + }, + "time_window": { + "from": "2026-05-01T00:00:00Z", + "to": "2027-05-01T00:00:00Z" + }, + "vendor_whitelist": [ + "office-supplies-co.example", + "*.trusted-saas-vendor.example", + "industrial-parts.example", + "logistics-partner.example" + ], + "categories": [ + "office-supplies", + "software-subscriptions", + "industrial-parts", + "logistics-services" + ], + "geo_regions": [ + "US", + "CA", + "EU", + "UK" + ] + } + }, + "issued_at": "2026-04-28T09:30:00Z", + "expires_at": "2027-05-01T00:00:00Z", + "revocation_url": "https://acme-industries.example/.well-known/amdp/verify", + "audit_trail_endpoint": "https://acme-industries.example/.well-known/amdp/audit", + "signature": { + "algorithm": "hybrid-ed25519-mldsa65", + "value": "base64url:placeholder-ed25519-sig-followed-by-mldsa65-sig", + "key_id": "acme-2026-procurement" + } +} diff --git a/amdp-spec/examples/procurement-cross-vendor.md b/amdp-spec/examples/procurement-cross-vendor.md new file mode 100644 index 0000000..797e212 --- /dev/null +++ b/amdp-spec/examples/procurement-cross-vendor.md @@ -0,0 +1,45 @@ +# Example — Procurement Cross-Vendor + +**File:** [procurement-cross-vendor.json](procurement-cross-vendor.json) +**Vertical:** `procurement` +**License:** MIT + +## Scenario + +ACME Industries AG runs a Procurement Assistant (Claude Sonnet 4.7) that approves orders for routine operating supplies from a pre-approved vendor list. The agent may: + +- Approve orders up to USD 50'000 each (`approve_order`). +- Select among whitelisted vendors for quote requests (`select_vendor`). +- Initiate terms negotiation (`negotiate_terms`) — but final approval still requires principal action because that's the convention in section 4.2. + +## Constraints + +- **`max_amount: 50'000 USD per_action`** — Each individual order capped at USD 50'000. Larger purchases require a separate manual approval workflow outside the mandate. +- **`time_window: 2026-05-01 to 2027-05-01`** — 12-month delegation matching ACME's fiscal year. +- **`vendor_whitelist`** — Only orders to specifically listed vendors (or wildcard-matched subdomains of trusted vendors) are authorized. Notice the wildcard pattern `*.trusted-saas-vendor.example` which permits any subdomain. +- **`categories`** — Restricted to four standard procurement categories. An order for, say, legal services (category `legal`) would be rejected. +- **`geo_regions: [US, CA, EU, UK]`** — Tax/customs simplicity: only jurisdictions where ACME's procurement department has standing onboarding agreements. + +## Why this shape + +- **Long `expires_at` (12 months)** is acceptable here because the procurement scope is operationally bounded (vendor whitelist + categories + per-action cap). The blast-radius of compromise is limited. +- **`vendor_whitelist` is the primary security mechanism.** Even if the per-action cap is raised in a future renewal, the whitelist limits where money can go. +- **Wildcard support in vendor patterns** (`*.trusted-saas-vendor.example`) is helpful for vendors with multiple environments (e.g., `prod.trusted-saas-vendor.example`, `staging.trusted-saas-vendor.example`). +- **`per_action` mode** is appropriate; a `cumulative` cap would prevent legitimate ongoing operations. + +## Anti-pattern + +This mandate does NOT include `vendor_blacklist`. Section 5.3 explicitly forbids combining `vendor_whitelist` and `vendor_blacklist` in the same mandate. A resolver presented with both would return `AMDP_CONSTRAINT_VIOLATION`. + +## Verification flow + +A vendor's order-acceptance API (verifier) receives an order request from the Procurement Assistant for USD 35'000 of `software-subscriptions` from `prod.trusted-saas-vendor.example`. The verifier: + +1. Validates schema. +2. Fetches JWKS. +3. Verifies hybrid signature. +4. Calls `/verify?mandate_id=01904c2d-...&action=approve_order&context=`. +5. Resolver evaluates: amount OK (under cap), category in list, vendor matches wildcard `*.trusted-saas-vendor.example`, geo in list. Returns `200 {"valid": true, "constraints_match": true, ...}`. +6. Verifier accepts the order. + +If the same agent presented an order for `unknown-vendor.example`, the resolver would return `constraints_match: false` with reason `"vendor not in whitelist"`. diff --git a/amdp-spec/examples/public-services-citizen-request.json b/amdp-spec/examples/public-services-citizen-request.json new file mode 100644 index 0000000..55dc630 --- /dev/null +++ b/amdp-spec/examples/public-services-citizen-request.json @@ -0,0 +1,48 @@ +{ + "amdp_version": "0.1.0", + "mandate_id": "01904e4f-bd6c-7d8a-85fa-4f0d7e8f9a0b", + "principal": { + "id": "did:web:citizen-alice.example", + "verifier_url": "https://citizen-alice.example/.well-known/amdp/jwks", + "display_name": "Alice Citizen" + }, + "agent": { + "id": "did:web:tax-helper-app.example/agents/alice-tax-bot", + "name": "Alice's Tax Helper Bot", + "model": "anthropic/claude-sonnet-4-7" + }, + "scope": { + "vertical": "public-services", + "actions": [ + "submit_request", + "approve_form", + "query_records" + ], + "constraints": { + "time_window": { + "from": "2026-05-17T00:00:00Z", + "to": "2026-08-31T23:59:59Z" + }, + "vendor_whitelist": [ + "tax.gov.zh.ch", + "civil-registry.gov.zh.ch" + ], + "geo_regions": [ + "CH" + ], + "data_classes": [ + "public", + "personal-self-only" + ] + } + }, + "issued_at": "2026-05-17T14:00:00Z", + "expires_at": "2026-08-31T23:59:59Z", + "revocation_url": "https://citizen-alice.example/.well-known/amdp/verify", + "audit_trail_endpoint": "https://citizen-alice.example/.well-known/amdp/audit", + "signature": { + "algorithm": "hybrid-ed25519-mldsa65", + "value": "base64url:placeholder-ed25519-sig-followed-by-mldsa65-sig", + "key_id": "alice-2026" + } +} diff --git a/amdp-spec/examples/public-services-citizen-request.md b/amdp-spec/examples/public-services-citizen-request.md new file mode 100644 index 0000000..14d734b --- /dev/null +++ b/amdp-spec/examples/public-services-citizen-request.md @@ -0,0 +1,52 @@ +# Example — Public-Services Citizen Request + +**File:** [public-services-citizen-request.json](public-services-citizen-request.json) +**Vertical:** `public-services` +**License:** MIT + +## Scenario + +Alice is a Zurich resident filing her 2025 tax return. She uses a tax-helper agent (Claude Sonnet 4.7) to handle the form submission and follow-up correspondence with the cantonal tax authority. She authorizes the agent to: + +- Submit tax forms and amendments on her behalf (`submit_request`). +- Acknowledge or approve forms returned by the authority (`approve_form`). +- Query her own tax records to fill in forms accurately (`query_records`). + +## Constraints + +- **No `max_amount` constraint** — Public-services interactions typically don't involve monetary amounts in the AMDP sense. (Tax owed is computed by the authority; the agent only submits forms.) Omitting `max_amount` is fine. +- **`time_window: 2026-05-17 to 2026-08-31`** — Three-and-a-half months — covering the typical Swiss cantonal tax-filing window. +- **`vendor_whitelist`** — Only the Zurich cantonal tax portal and civil registry. The agent cannot make requests to (say) federal social security or to a tax portal in a different canton. +- **`geo_regions: [CH]`** — Switzerland only. +- **`data_classes: [public, personal-self-only]`** — The agent may read public records and Alice's own personal records. It cannot read records about other family members, third parties, or sensitive classes (PII of others, PHI, financial records of others). This is the critical privacy boundary. + +## Why this shape + +- **Citizens are principals too.** AMDP works for natural-person principals just as well as for corporate principals. The `did:web:citizen-alice.example` identifier could in practice be backed by a self-hosted or service-provider-hosted DID method (`did:key`, `did:ion`, etc.). The protocol is identifier-agnostic. +- **`data_classes` is the most important constraint here.** Without it, a misbehaving agent could query records the citizen never intended to expose. The fail-closed semantics of `data_classes` (resolvers MUST treat unknown classes as forbidden) protect citizens from over-broad mandates. +- **`vendor_whitelist` for government endpoints** is appropriate. The citizen may want help with cantonal tax but NOT with federal driving-licence records. Whitelisting endpoint domains achieves that. +- **Three-and-a-half month window** matches the practical workflow (filing deadline + reply time + amendment window). Shorter windows would force renewal mid-process; longer windows expand the blast radius. +- **No `audit_trail_endpoint` host operated by the citizen** is realistic — the citizen MAY use a managed audit service (e.g., a digital-identity service provider) and point the endpoint there. The mandate above uses a self-hosted URL for clarity but in practice would point to a managed service. + +## Privacy considerations + +This is the most privacy-sensitive scenario in the example set. Section 4 of [SECURITY.md](../SECURITY.md) summarizes the relevant principles: + +- The mandate carries Alice's DID, but NOT her tax-number, address, AHV-number, or any other identifier. Those are presented to the cantonal portal during action execution, NOT in the mandate. +- The `audit_trail_endpoint` receives action events about Alice's mandates. If a managed audit service is used, that service sees what actions are happening but NOT the contents of tax forms (the verifier — the cantonal portal — has that data, governed by Swiss tax-secrecy law). +- `data_classes: [personal-self-only]` is enforced at the resolver. Alice's resolver MUST refuse to evaluate constraints for queries targeting records that don't belong to Alice. + +## Verification flow + +The cantonal tax portal (verifier at `tax.gov.zh.ch`) receives a `submit_request` from Alice's Tax Helper Bot, containing a tax-form payload. The verifier: + +1. Validates the mandate schema. +2. Fetches Alice's JWKS at `https://citizen-alice.example/.well-known/amdp/jwks`. +3. Verifies the hybrid signature. +4. Calls `/verify?mandate_id=01904e4f-...&action=submit_request&context=`. +5. Resolver evaluates: time-window OK (current time within 2026-05-17 to 2026-08-31), vendor in list, geo CH, data class allowed. Returns `200 {"valid": true, "constraints_match": true, ...}`. +6. Portal accepts the submission. + +The portal then sends an audit event to Alice's `audit_trail_endpoint` documenting the submission. + +If the same agent later attempts to use the mandate against `tax.gov.be.ch` (Bern instead of Zurich), the resolver would return `constraints_match: false` with reason `"vendor not in whitelist"`.