diff --git a/a2a-trust-header/.gitignore b/a2a-trust-header/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/a2a-trust-header/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/a2a-trust-header/README.md b/a2a-trust-header/README.md index 69520a9..c9f218c 100644 --- a/a2a-trust-header/README.md +++ b/a2a-trust-header/README.md @@ -1,4 +1,18 @@ -# a2a-trust-header — APS Week 2 fixtures +# a2a-trust-header: A2A#1742 fixtures and dual-provider verifier + +Signed, JCS-canonical fixtures for the `x-agent-trust` header being +specified at [a2aproject/A2A#1742](https://github.com/a2aproject/A2A/issues/1742), +plus a consumer verifier that validates both APS and MolTrust emission +shapes against a single canonical schema. + +- **Week 1 (done):** schema lock on the 5-field composite header. +- **Week 2 (done):** 6 APS fixtures + APS-only round-trip verifier. See [Week 2](#week-2--six-aps-fixtures) below. +- **Week 3 (done):** canonical JSON Schema, dual-provider consumer + verifier, 3 MolTrust-shaped placeholder fixtures. See [Week 3](#week-3--canonical-schema--dual-provider-consumer-verifier) below. + +--- + +## Week 2: six APS fixtures Six signed, JCS-canonical fixtures for the `x-agent-trust` header being specified at [a2aproject/A2A#1742](https://github.com/a2aproject/A2A/issues/1742). @@ -152,3 +166,180 @@ advertised public key. The verdict for each fixture must match Questions, format clarifications, or counter-examples: open a PR or comment on [a2aproject/A2A#1742](https://github.com/a2aproject/A2A/issues/1742). + +--- + +## Week 3: canonical schema + dual-provider consumer verifier + +Week 3 turns the Week 1 schema lock into runnable artifacts and adds +MolTrust-shaped placeholder fixtures so the consumer verifier can +exercise both provider shapes end-to-end before MolTrust ships their +half. + +### Canonical composite header schema + +The canonical wire form is a **5-field composite**: + +| Field | Type | Notes | +|---|---|---| +| `trust_level` | integer 0–4 | 0 = unverified / newly issued; 4 = highest-confidence issuer-backed | +| `attestation_count` | integer ≥ 0 | Number of attestations accumulated by the issuer at `last_verified` time | +| `last_verified` | ISO 8601 date-time | Consumers MAY apply a half-life policy (MolTrust default: 45 days) | +| `evidence_bundle` | string, `ipfs://…` or `https?://…` | Pointer to the full attestation bundle | +| `delegation_chain_root` | `sha256:` or `uri:https?://…` | Self-describing authority root | + +Schema file: [`schema/a2a-trust-header.schema.json`](./schema/a2a-trust-header.schema.json). +Draft 2020-12. All 5 fields are required. `additionalProperties: true` +so providers can emit vendor-specific sibling fields without coupling +the canonical contract. + +`delegation_chain_root` uses a `pattern` constraint to enforce the +self-describing form from Week 1 (`sha256:` | `uri:https?://…`). + +### Composite view derivation + +APS and MolTrust fixtures agree on the 5-field contract but differ in +how rich their native shape is. The consumer verifier reduces every +fixture to the canonical composite before schema validation: + +- **MolTrust-shaped fixtures** (placeholder + real) carry the 5 fields + directly on `header_value`. Derivation is `direct`: read the fields + off `header_value` and validate. +- **APS-shaped fixtures** carry the 5 fields across the richer native + structure: `trust_level` lives inside each attestation's payload, + `attestation_count` = `len(attestations)`, `last_verified` = most + recent attestation's `issued_at`, `evidence_bundle` = synthesized + pointer to the APS gateway's public trust endpoint for the subject + agent, `delegation_chain_root` = already on `header_value`. + Derivation is `aps-synthesized`. + +This split is the Week 1 agreement made operational: consumers see the +same 5 fields regardless of producer; producers keep their richer +native shape. + +### Consumer verifier + +[`consumer-verify.ts`](./consumer-verify.ts) discovers every `*.json` +fixture at the top of `a2a-trust-header/` and under +`moltrust-placeholder/`, classifies each by issuer, derives the +composite view, schema-validates via ajv, and verifies every Ed25519 +signature it finds. Signature verification uses `@noble/ed25519` +(not the APS SDK) so MolTrust does not need to pull APS-specific code +to run this verifier. + +```bash +# From repo root +cd a2a-trust-header +npm install +npx tsx consumer-verify.ts +``` + +Per-fixture row shape: + +``` +[PASS] happy-path.json issuer=aps schema=ok sigs=3/3 root=ok verdict=pass + composite_derivation=aps-synthesized +``` + +Aggregate summary: + +``` +Consumer verify: aggregate + APS fixtures: 6 / 6 pass + MolTrust fixtures: 3 / 3 pass (placeholder) + Unknown issuer: 0 + Schema failures: 0 + Signature failures: 0 + Chain-root drift: 0 +``` + +Exit codes: + +| Code | Meaning | +|---|---| +| `0` | all fixtures pass | +| `1` | any signature or chain-root failure | +| `2` | any schema failure | + +### MolTrust placeholder fixtures + +Three MolTrust-shaped fixtures live under +[`moltrust-placeholder/`](./moltrust-placeholder/). They are synthetic +references MolTrust can replace with their real emission when they ship +Week 3 on their side. Every placeholder fixture is marked with: + +```json +{ + "_placeholder": true, + "_replace_with_real_moltrust_emission": true +} +``` + +| # | File | What it exercises | +|---|---|---| +| 1 | [`trust-trajectory-decay.json`](./moltrust-placeholder/trust-trajectory-decay.json) | `trust_level` steps down 4 → 3 → 2 across three progressive emissions; `evidence_bundle` is an `ipfs://…` pointer | +| 2 | [`attestation-accumulation.json`](./moltrust-placeholder/attestation-accumulation.json) | `attestation_count` accumulates 2 → 7 → 15 with `trust_level` held at 3; `evidence_bundle` is an `https://…` URL | +| 3 | [`shared-happy-path-moltrust.json`](./moltrust-placeholder/shared-happy-path-moltrust.json) | Same `delegation_chain` + root as APS [`happy-path.json`](./happy-path.json), re-signed under MolTrust placeholder key with `issuer="moltrust"`: overlap-region proof | + +#### Placeholder signing key + +Placeholder fixtures are signed with a **deterministic test seed**: +32-byte right-aligned, tail `0xAA`. The public half is embedded in each +fixture's `moltrust_signing_key` field and as `signature.pubkey` on +every emission. This is test-only: + +> Replace before production. Do not reuse the placeholder key in any +> issuer emission seen by a real consumer. + +The generator that produced these fixtures, +[`moltrust-placeholder/generate-placeholder.ts`](./moltrust-placeholder/generate-placeholder.ts), +is committed so MolTrust can see exactly how the shape was constructed. + +### MolTrust replacement workflow + +When MolTrust ships Week 3 on their side: + +1. Replace each placeholder file under `moltrust-placeholder/` with + their real emission. Keep the filenames (trajectory / accumulation + / shared-happy-path) so the consumer verifier picks them up + automatically. +2. Drop the `_placeholder` / `_replace_with_real_moltrust_emission` + fields. +3. Replace `moltrust_signing_key` with their production kid + pubkey, + and re-sign every emission under that key. +4. Re-run `npx tsx consumer-verify.ts`. All 9 fixtures (6 APS + 3 + MolTrust real) must still pass schema + signature + chain-root + checks. + +The consumer verifier does not encode MolTrust import weighting +(0.3 weight, 45-day half-life, `POST /identity/resolve` before import). +Those are downstream policy decisions. What the verifier guarantees is +that every 5-field composite a MolTrust-aware consumer would read is +**schema-conformant, cryptographically verifiable, and chain-root +consistent**: the preconditions MolTrust weighting is built on top of. + +### Ed25519 / canonicalization conventions + +Both providers use the same cryptography: + +- Ed25519 (RFC 8032) for all signatures. +- RFC 8785 JCS canonicalization over every signed payload. +- SHA-256 for `delegation_chain_root` in `sha256:` form. + +The consumer verifier uses `canonicalizeJCS` from +`agent-passport-system` and `@noble/ed25519` for verification. It does +not require the APS SDK to verify MolTrust-shaped fixtures beyond the +canonicalization helper, which is a primitive, not APS-specific. + +### Reproducing Week 3 artifacts + +```bash +cd a2a-trust-header +npm install +# Regenerate placeholder fixtures (byte-reproducible under fixed seed) +npx tsx moltrust-placeholder/generate-placeholder.ts +# Round-trip APS fixtures through the APS-native verifier +npx tsx verify.ts +# Dual-provider schema + signature + chain-root verification +npx tsx consumer-verify.ts +``` diff --git a/a2a-trust-header/consumer-verify.ts b/a2a-trust-header/consumer-verify.ts new file mode 100644 index 0000000..2cbc2bb --- /dev/null +++ b/a2a-trust-header/consumer-verify.ts @@ -0,0 +1,519 @@ +/** + * Dual-provider consumer verifier for A2A#1742 x-agent-trust header. + * + * Week 3 deliverable. Validates BOTH APS and MolTrust-shaped fixtures + * against the canonical 5-field composite schema locked in Week 1, then + * verifies Ed25519 signatures and recomputes delegation_chain_root. + * + * Fixture discovery: + * - every *.json at the top level of a2a-trust-header/ + * (excluding _keys.json and anything under schema/ or moltrust-placeholder/ + * auto-discovered separately) + * - every *.json under moltrust-placeholder/ + * + * For each fixture: + * 1. Derive the composite view (5 canonical fields) from header_value. + * APS fixtures carry trust_level / attestation_count / last_verified + * / evidence_bundle inside attestations[]; the consumer reduces the + * rich APS native shape to the canonical 5-field composite a consumer + * would read on the wire. + * MolTrust placeholder fixtures carry the 5 fields directly on + * header_value (matching the canonical shape natively). + * 2. ajv-validate the composite view against the canonical schema. + * 3. Ed25519-verify every signature present (delegation links, + * attestations, agent_card signals, deny receipts, MolTrust + * placeholder emissions + trajectory entries). Uses @noble/ed25519 + * verifyAsync so this verifier does not pull the APS SDK for + * signature checks. + * 4. Recompute delegation_chain_root when a fixture carries an inline + * delegation_chain array. Compare against the declared value. + * + * Per-fixture row + aggregate summary printed to stdout. + * + * Exit codes: + * 0 = all fixtures pass + * 1 = any signature or chain-root failure + * 2 = any schema failure + */ + +import { readFileSync, readdirSync, statSync } from 'node:fs' +import { createHash } from 'node:crypto' +import { fileURLToPath } from 'node:url' +import { dirname, join } from 'node:path' +import Ajv from 'ajv/dist/2020.js' +import addFormats from 'ajv-formats' +import * as ed from '@noble/ed25519' +import { canonicalizeJCS } from 'agent-passport-system' + +const DIR = dirname(fileURLToPath(import.meta.url)) +const SCHEMA_PATH = join(DIR, 'schema', 'a2a-trust-header.schema.json') +const PLACEHOLDER_DIR = join(DIR, 'moltrust-placeholder') + +const sha256 = (s: string) => createHash('sha256').update(s).digest('hex') + +// ── Schema validator ────────────────────────────────────────────────── +const schema = JSON.parse(readFileSync(SCHEMA_PATH, 'utf8')) +const ajv = new Ajv({ allErrors: true, strict: false }) +addFormats(ajv) +const validateComposite = ajv.compile(schema) + +// ── Fixture discovery ───────────────────────────────────────────────── +// Files that live alongside fixtures but are NOT fixtures. These are +// excluded from the fixture discovery loop. +const NON_FIXTURE_FILES = new Set([ + '_keys.json', + 'package.json', + 'package-lock.json', + 'tsconfig.json', +]) + +function discoverFixtures(): Array<{ path: string; label: string }> { + const files: Array<{ path: string; label: string }> = [] + + for (const entry of readdirSync(DIR)) { + if (!entry.endsWith('.json')) continue + if (NON_FIXTURE_FILES.has(entry)) continue + if (entry.startsWith('_')) continue + const full = join(DIR, entry) + if (!statSync(full).isFile()) continue + files.push({ path: full, label: entry }) + } + + try { + for (const entry of readdirSync(PLACEHOLDER_DIR)) { + if (!entry.endsWith('.json')) continue + const full = join(PLACEHOLDER_DIR, entry) + if (!statSync(full).isFile()) continue + files.push({ path: full, label: `moltrust-placeholder/${entry}` }) + } + } catch { + /* no placeholder dir yet; skip */ + } + + files.sort((a, b) => a.label.localeCompare(b.label)) + return files +} + +// ── Issuer classification ───────────────────────────────────────────── +type IssuerKind = 'aps' | 'moltrust' | 'unknown' + +function classifyIssuer(fixture: Record, hv: Record): IssuerKind { + if (fixture._placeholder === true) return 'moltrust' + + const direct = typeof hv.issuer === 'string' ? (hv.issuer as string) : undefined + if (direct === 'moltrust' || direct?.startsWith('moltrust:')) return 'moltrust' + if (direct?.startsWith('did:aps:')) return 'aps' + + const did = typeof hv.did === 'string' ? (hv.did as string) : undefined + if (did?.startsWith('did:aps:')) return 'aps' + if (did?.startsWith('did:moltrust:')) return 'moltrust' + + // Infer from attestation issuer kid prefix + const atts = (hv.attestations ?? []) as Array<{ + payload?: { issuer?: string } + signature?: { kid?: string } + }> + for (const att of atts) { + const kid = att?.signature?.kid ?? '' + if (kid.startsWith('did:aps:')) return 'aps' + if (kid.startsWith('moltrust:') || kid.startsWith('did:moltrust:')) return 'moltrust' + const iss = att?.payload?.issuer ?? '' + if (iss.startsWith('did:aps:')) return 'aps' + if (iss === 'moltrust' || iss.startsWith('moltrust:')) return 'moltrust' + } + + // Shared-card fixture: inspect agent_card.trust.signals + const card = hv.agent_card as + | { trust?: { signals?: Array<{ signature?: { kid?: string } }> } } + | undefined + if (card?.trust?.signals?.length) { + const kid = card.trust.signals[0].signature?.kid ?? '' + if (kid.startsWith('did:aps:')) return 'aps' + } + + return 'unknown' +} + +// ── Composite-view derivation ───────────────────────────────────────── +/** + * Reduce a fixture's header_value to the canonical 5-field composite. + * For MolTrust placeholders, the fields are already present on + * header_value and returned directly. For APS fixtures, they are + * synthesized from the rich native shape. + */ +function deriveComposite( + hv: Record, + issuerKind: IssuerKind, +): { composite: Record; derivation: 'direct' | 'aps-synthesized' } { + const hasDirectCompositeFields = + typeof hv.trust_level === 'number' && + typeof hv.attestation_count === 'number' && + typeof hv.last_verified === 'string' && + typeof hv.evidence_bundle === 'string' && + typeof hv.delegation_chain_root === 'string' + + if (hasDirectCompositeFields) { + return { + composite: { + trust_level: hv.trust_level, + attestation_count: hv.attestation_count, + last_verified: hv.last_verified, + evidence_bundle: hv.evidence_bundle, + delegation_chain_root: hv.delegation_chain_root, + }, + derivation: 'direct', + } + } + + // APS synthesis path: derive from attestations[] + agent_card.trust.signals[] + const attestations = (hv.attestations ?? []) as Array<{ + payload?: { trust_level?: string; issued_at?: string } + }> + const card = hv.agent_card as + | { trust?: { signals?: Array<{ payload?: { trust_level?: string; issued_at?: string } }> } } + | undefined + const signals = card?.trust?.signals ?? [] + const all = [...attestations, ...signals] + + const levelMap: Record = { + unknown: 0, + flagged: 1, + revoked: 0, + developing: 2, + trusted: 4, + } + + const last = all[all.length - 1] + const trustStr = last?.payload?.trust_level ?? 'unknown' + const trustLevel = levelMap[trustStr] ?? 0 + const attestationCount = all.length + const lastVerified = last?.payload?.issued_at ?? '1970-01-01T00:00:00Z' + + const subject = + typeof hv.subject_agent === 'string' ? (hv.subject_agent as string) : 'unknown' + // Synthesize evidence_bundle as a pointer to the APS gateway's public + // trust attestation endpoint for the subject agent. + const evidenceBundle = `https://gateway.aeoess.com/api/v1/public/trust/${encodeURIComponent( + subject, + )}/attestation` + + return { + composite: { + trust_level: trustLevel, + attestation_count: attestationCount, + last_verified: lastVerified, + evidence_bundle: evidenceBundle, + delegation_chain_root: hv.delegation_chain_root ?? '', + }, + derivation: 'aps-synthesized', + } +} + +// ── Signature collection ────────────────────────────────────────────── +interface SignatureRef { + location: string + sig: string + pubkey: string + canonicalPayload: string +} + +/** + * Walk the header_value and collect every signed payload + signature + * pair. Canonicalization is RFC 8785 JCS over payload-minus-signature + * for inline-signed objects, or over payload for wrapped + * { payload, signature } objects. This is the shared convention APS + + * MolTrust both agreed to use. + */ +function collectSignatures(hv: Record, prefix: string): SignatureRef[] { + const out: SignatureRef[] = [] + + // 1. Delegation chain: each link inline-signs { ...link, signature } + const chain = (hv.delegation_chain ?? []) as Array> + for (let i = 0; i < chain.length; i++) { + const link = chain[i] + const sig = link.signature as { sig?: string; pubkey?: string } | undefined + if (sig?.sig && sig.pubkey) { + const { signature: _, ...payload } = link + out.push({ + location: `${prefix}.delegation_chain[${i}]`, + sig: sig.sig, + pubkey: sig.pubkey, + canonicalPayload: canonicalizeJCS(payload), + }) + } + } + + // 2. Attestations: { payload, signature } wrapper + const atts = (hv.attestations ?? []) as Array<{ + payload?: Record + signature?: { sig?: string; pubkey?: string } + }> + for (let i = 0; i < atts.length; i++) { + const a = atts[i] + if (a?.signature?.sig && a.signature.pubkey && a.payload) { + out.push({ + location: `${prefix}.attestations[${i}]`, + sig: a.signature.sig, + pubkey: a.signature.pubkey, + canonicalPayload: canonicalizeJCS(a.payload), + }) + } + } + + // 3. Agent card trust signals (A2A#1628 pattern) + const card = hv.agent_card as + | { + trust?: { + signals?: Array<{ + payload?: Record + signature?: { sig?: string; pubkey?: string } + }> + } + } + | undefined + const signals = card?.trust?.signals ?? [] + for (let i = 0; i < signals.length; i++) { + const s = signals[i] + if (s?.signature?.sig && s.signature.pubkey && s.payload) { + out.push({ + location: `${prefix}.agent_card.trust.signals[${i}]`, + sig: s.signature.sig, + pubkey: s.signature.pubkey, + canonicalPayload: canonicalizeJCS(s.payload), + }) + } + } + + // 4. Deny receipt + const deny = hv.deny_receipt as + | { payload?: Record; signature?: { sig?: string; pubkey?: string } } + | undefined + if (deny?.signature?.sig && deny.signature.pubkey && deny.payload) { + out.push({ + location: `${prefix}.deny_receipt`, + sig: deny.signature.sig, + pubkey: deny.signature.pubkey, + canonicalPayload: canonicalizeJCS(deny.payload), + }) + } + + // 5. MolTrust-shape inline signature on header_value itself. The + // signed payload is header_value minus `signature` and minus fields + // that are not part of the emission (`trajectory`, `delegation_chain`, + // `overlap_ref` are accompanying data, not signed content). + const inlineSig = hv.signature as { sig?: string; pubkey?: string } | undefined + if (inlineSig?.sig && inlineSig.pubkey) { + const { + signature: _sig, + trajectory: _traj, + delegation_chain: _chain, + overlap_ref: _ov, + ...signed + } = hv as Record + out.push({ + location: `${prefix}.header_value.signature`, + sig: inlineSig.sig, + pubkey: inlineSig.pubkey, + canonicalPayload: canonicalizeJCS(signed), + }) + } + + // 6. MolTrust trajectory[] entries (each carries its own inline signature) + const trajectory = (hv.trajectory ?? []) as Array> + for (let i = 0; i < trajectory.length; i++) { + const entry = trajectory[i] + const s = entry.signature as { sig?: string; pubkey?: string } | undefined + if (s?.sig && s.pubkey) { + const { signature: _sig, ...signed } = entry + out.push({ + location: `${prefix}.trajectory[${i}]`, + sig: s.sig, + pubkey: s.pubkey, + canonicalPayload: canonicalizeJCS(signed), + }) + } + } + + return out +} + +// ── Per-fixture verification ────────────────────────────────────────── +interface FixtureRow { + fixture: string + issuer_kind: IssuerKind + schema_valid: boolean + schema_errors: unknown[] + signatures_total: number + signatures_valid: number + signature_failures: string[] + delegation_chain_root_recomputed: string | null + delegation_chain_root_declared: string | null + delegation_chain_root_mismatch: boolean + composite_derivation: 'direct' | 'aps-synthesized' | 'n/a' + verdict: 'pass' | 'fail_schema' | 'fail_signature' | 'fail_chain_root' | 'partial' + notes: string[] +} + +async function verifyFixture(path: string, label: string): Promise { + const raw = readFileSync(path, 'utf8') + const fixture = JSON.parse(raw) as Record + const hv = (fixture.header_value ?? {}) as Record + const issuerKind = classifyIssuer(fixture, hv) + + const row: FixtureRow = { + fixture: label, + issuer_kind: issuerKind, + schema_valid: false, + schema_errors: [], + signatures_total: 0, + signatures_valid: 0, + signature_failures: [], + delegation_chain_root_recomputed: null, + delegation_chain_root_declared: null, + delegation_chain_root_mismatch: false, + composite_derivation: 'n/a', + verdict: 'pass', + notes: [], + } + + // 1. Derive composite + schema-validate + const { composite, derivation } = deriveComposite(hv, issuerKind) + row.composite_derivation = derivation + const schemaOk = validateComposite(composite) + row.schema_valid = !!schemaOk + if (!schemaOk) { + row.schema_errors = (validateComposite.errors ?? []) as unknown[] + } + + // 2. Signature verification + const sigs = collectSignatures(hv, label) + row.signatures_total = sigs.length + for (const s of sigs) { + try { + const ok = await ed.verifyAsync( + s.sig, + new TextEncoder().encode(s.canonicalPayload), + s.pubkey, + ) + if (ok) { + row.signatures_valid++ + } else { + row.signature_failures.push(s.location) + } + } catch (err) { + row.signature_failures.push( + `${s.location} (verify threw: ${(err as Error).message})`, + ) + } + } + + // 3. Recompute delegation_chain_root from inline chain if present + const chain = hv.delegation_chain as unknown[] | undefined + const declaredRoot = + typeof hv.delegation_chain_root === 'string' + ? (hv.delegation_chain_root as string) + : null + row.delegation_chain_root_declared = declaredRoot + if (Array.isArray(chain) && chain.length > 0 && declaredRoot) { + const recomputed = `sha256:${sha256(canonicalizeJCS(chain))}` + row.delegation_chain_root_recomputed = recomputed + if (declaredRoot.startsWith('sha256:') && recomputed !== declaredRoot) { + row.delegation_chain_root_mismatch = true + row.notes.push( + `declared ${declaredRoot} != recomputed ${recomputed}; drift/variant fixture may declare this intentionally`, + ) + } + } + + // 4. Derive verdict + const hasSchemaFailure = !row.schema_valid + const hasSignatureFailure = row.signature_failures.length > 0 + // For fixtures that explicitly flag format_variant, a chain-root + // mismatch under a non-sha256 advertised algorithm is EXPECTED and + // does not count as a verdict failure. + const formatVariant = fixture.format_variant === true + const hasChainRootFailure = + row.delegation_chain_root_mismatch && !formatVariant + + if (hasSchemaFailure) row.verdict = 'fail_schema' + else if (hasSignatureFailure) row.verdict = 'fail_signature' + else if (hasChainRootFailure) row.verdict = 'fail_chain_root' + else if (issuerKind === 'unknown') { + row.verdict = 'partial' + row.notes.push('issuer could not be classified; signatures still verified generically') + } else row.verdict = 'pass' + + return row +} + +// ── Rendering ───────────────────────────────────────────────────────── +function renderRow(r: FixtureRow): void { + const tag = r.verdict === 'pass' ? 'PASS' : r.verdict === 'partial' ? 'PART' : 'FAIL' + console.log( + `[${tag}] ${r.fixture} issuer=${r.issuer_kind} schema=${ + r.schema_valid ? 'ok' : 'FAIL' + } sigs=${r.signatures_valid}/${r.signatures_total} root=${ + r.delegation_chain_root_mismatch ? 'drift' : 'ok' + } verdict=${r.verdict}`, + ) + if (r.composite_derivation !== 'n/a') { + console.log(` composite_derivation=${r.composite_derivation}`) + } + if (!r.schema_valid) { + for (const e of r.schema_errors) { + console.log(` schema: ${JSON.stringify(e)}`) + } + } + for (const f of r.signature_failures) { + console.log(` sig FAIL: ${f}`) + } + for (const n of r.notes) { + console.log(` · ${n}`) + } +} + +// ── Main ────────────────────────────────────────────────────────────── +async function main(): Promise { + const fixtures = discoverFixtures() + console.log( + `Consumer verify: A2A#1742 x-agent-trust header, dual-provider (APS + MolTrust)`, + ) + console.log(`Schema: ${SCHEMA_PATH}`) + console.log(`Discovered ${fixtures.length} fixture(s)\n`) + + const rows: FixtureRow[] = [] + for (const f of fixtures) { + const row = await verifyFixture(f.path, f.label) + rows.push(row) + renderRow(row) + } + + // Aggregate + const aps = rows.filter(r => r.issuer_kind === 'aps') + const molt = rows.filter(r => r.issuer_kind === 'moltrust') + const unknown = rows.filter(r => r.issuer_kind === 'unknown') + const apsPass = aps.filter(r => r.verdict === 'pass').length + const moltPass = molt.filter(r => r.verdict === 'pass').length + const schemaFailures = rows.filter(r => r.verdict === 'fail_schema').length + const sigFailures = rows.filter(r => r.verdict === 'fail_signature').length + const chainRootFailures = rows.filter(r => r.verdict === 'fail_chain_root').length + + console.log('\nConsumer verify: aggregate') + console.log(` APS fixtures: ${apsPass} / ${aps.length} pass`) + console.log( + ` MolTrust fixtures: ${moltPass} / ${molt.length} pass (placeholder)`, + ) + console.log(` Unknown issuer: ${unknown.length}`) + console.log(` Schema failures: ${schemaFailures}`) + console.log(` Signature failures: ${sigFailures}`) + console.log(` Chain-root drift: ${chainRootFailures}`) + + if (schemaFailures > 0) return 2 + if (sigFailures > 0 || chainRootFailures > 0) return 1 + return 0 +} + +main().then(code => process.exit(code)).catch(err => { + console.error(err) + process.exit(1) +}) diff --git a/a2a-trust-header/moltrust-placeholder/attestation-accumulation.json b/a2a-trust-header/moltrust-placeholder/attestation-accumulation.json new file mode 100644 index 0000000..89540eb --- /dev/null +++ b/a2a-trust-header/moltrust-placeholder/attestation-accumulation.json @@ -0,0 +1,93 @@ +{ + "fixture": "attestation-accumulation", + "description": "MolTrust-shaped placeholder: attestation_count accumulation 2 → 7 → 15 across three progressive emissions with trust_level held at 3. Demonstrates the accumulation dimension of the 5-field composite header: an agent with many concurring attestations is meaningfully distinct from one with few, even at the same trust_level. evidence_bundle is an https:// URL. MolTrust replaces this with their production emission shape.", + "expected_verifier_output": "valid", + "format_variant": false, + "spec_refs": [ + "a2aproject/A2A#1742" + ], + "header_name": "x-agent-trust", + "moltrust_signing_key": { + "kid": "moltrust-placeholder-v1", + "pubkey": "71de3b4e933aa718a6f5c45845ee83af8000a450f9572d4cf393e681c8144191", + "seed_tail": "0xAA", + "notes": "Deterministic test seed. Replace before production." + }, + "_placeholder": true, + "_replace_with_real_moltrust_emission": true, + "_placeholder_notes": "Test-only MolTrust shape emitted by APS as a structural reference. Deterministic signing seed (tail 0xAA) is documented in moltrust-placeholder/generate-placeholder.ts. MolTrust replaces this with production emission + real signing key when Week 3 ships on their side.", + "header_value": { + "trust_level": 3, + "attestation_count": 15, + "last_verified": "2026-04-18T12:10:00Z", + "evidence_bundle": "https://moltrust.example/bundles/e0df1ff190f8dfabdde91755f7c2f3e09fbd8efdf8ffbdfa90d34d77d0fa9cfa", + "delegation_chain_root": "sha256:4a812da6a61c8d8219dacb33dfbc1cb17d169d46397a45f5e63bd5b581591a45", + "subject_agent": "did:aps:fd50b8e3b144ea244fbf7737f550bc8d", + "issuer": "moltrust", + "sequence": 2, + "delta_from_previous": 8, + "signature": { + "alg": "EdDSA", + "kid": "moltrust-placeholder-v1", + "pubkey": "71de3b4e933aa718a6f5c45845ee83af8000a450f9572d4cf393e681c8144191", + "canonicalization": "RFC8785-JCS", + "sig": "095543b0688ce7c4c8ab99dee92b9c14be030108499d302cde8eff1bcb88e5dd1d74175810c9262eab330b81a8797a224a9f6f3bf3e0984e292539a654248f0b" + }, + "trajectory": [ + { + "trust_level": 3, + "attestation_count": 2, + "last_verified": "2026-04-18T12:00:00Z", + "evidence_bundle": "https://moltrust.example/bundles/e0df1ff190f8dfabdde91755f7c2f3e09fbd8efdf8ffbdfa90d34d77d0fa9cfa", + "delegation_chain_root": "sha256:4a812da6a61c8d8219dacb33dfbc1cb17d169d46397a45f5e63bd5b581591a45", + "subject_agent": "did:aps:fd50b8e3b144ea244fbf7737f550bc8d", + "issuer": "moltrust", + "sequence": 0, + "delta_from_previous": 2, + "signature": { + "alg": "EdDSA", + "kid": "moltrust-placeholder-v1", + "pubkey": "71de3b4e933aa718a6f5c45845ee83af8000a450f9572d4cf393e681c8144191", + "canonicalization": "RFC8785-JCS", + "sig": "2db858d5354fd3769f97061abd32b38eae46700f47bdbb1cbf7d5936b92207d7a100dec1c4b6bbb3347a9aefac844743f4f9fb33c9cfd8d6f91ae8cdaa02a700" + } + }, + { + "trust_level": 3, + "attestation_count": 7, + "last_verified": "2026-04-18T12:05:00Z", + "evidence_bundle": "https://moltrust.example/bundles/e0df1ff190f8dfabdde91755f7c2f3e09fbd8efdf8ffbdfa90d34d77d0fa9cfa", + "delegation_chain_root": "sha256:4a812da6a61c8d8219dacb33dfbc1cb17d169d46397a45f5e63bd5b581591a45", + "subject_agent": "did:aps:fd50b8e3b144ea244fbf7737f550bc8d", + "issuer": "moltrust", + "sequence": 1, + "delta_from_previous": 5, + "signature": { + "alg": "EdDSA", + "kid": "moltrust-placeholder-v1", + "pubkey": "71de3b4e933aa718a6f5c45845ee83af8000a450f9572d4cf393e681c8144191", + "canonicalization": "RFC8785-JCS", + "sig": "e30588e482b4343d245abd169714e4861aa99fe653879b8ebccdd44a5ea5d2b45597048cf96826947858adfec37dd227c749ce0ff6e9e0c1ddd2760a9dabce0d" + } + }, + { + "trust_level": 3, + "attestation_count": 15, + "last_verified": "2026-04-18T12:10:00Z", + "evidence_bundle": "https://moltrust.example/bundles/e0df1ff190f8dfabdde91755f7c2f3e09fbd8efdf8ffbdfa90d34d77d0fa9cfa", + "delegation_chain_root": "sha256:4a812da6a61c8d8219dacb33dfbc1cb17d169d46397a45f5e63bd5b581591a45", + "subject_agent": "did:aps:fd50b8e3b144ea244fbf7737f550bc8d", + "issuer": "moltrust", + "sequence": 2, + "delta_from_previous": 8, + "signature": { + "alg": "EdDSA", + "kid": "moltrust-placeholder-v1", + "pubkey": "71de3b4e933aa718a6f5c45845ee83af8000a450f9572d4cf393e681c8144191", + "canonicalization": "RFC8785-JCS", + "sig": "095543b0688ce7c4c8ab99dee92b9c14be030108499d302cde8eff1bcb88e5dd1d74175810c9262eab330b81a8797a224a9f6f3bf3e0984e292539a654248f0b" + } + } + ] + } +} diff --git a/a2a-trust-header/moltrust-placeholder/generate-placeholder.ts b/a2a-trust-header/moltrust-placeholder/generate-placeholder.ts new file mode 100644 index 0000000..e3d1a1c --- /dev/null +++ b/a2a-trust-header/moltrust-placeholder/generate-placeholder.ts @@ -0,0 +1,332 @@ +/** + * MolTrust-shaped placeholder generator for Week 3 of A2A#1742. + * + * Emits 3 synthetic fixtures that MolTrust replaces with their real + * emission when they ship Week 3 on their side. Every fixture: + * + * - is marked _placeholder: true and _replace_with_real_moltrust_emission: true + * - carries the canonical 5-field composite header natively on header_value + * (trust_level / attestation_count / last_verified / evidence_bundle / + * delegation_chain_root) so schema validation passes end-to-end + * - is Ed25519-signed with a deterministic test seed (documented below) + * - uses RFC 8785 JCS canonicalization (the same one APS uses) so both + * providers share canonicalization conventions + * + * The placeholder signing key is a TEST SEED. Seed tail `0xAA`. The public + * half is written to every fixture's `moltrust_signing_key.pubkey` field for + * round-trip verification. MolTrust replaces this with their production + * kid + pubkey when they take over the shape. + * + * Run: npx tsx a2a-trust-header/moltrust-placeholder/generate-placeholder.ts + */ + +import { readFileSync, writeFileSync } from 'node:fs' +import { createHash } from 'node:crypto' +import { fileURLToPath } from 'node:url' +import { dirname } from 'node:path' +import { + canonicalizeJCS, + sign, + publicKeyFromPrivate, +} from 'agent-passport-system' + +const DIR = dirname(fileURLToPath(import.meta.url)) + +// Deterministic test seed: tail 0xAA, 32-byte right-aligned. +// MolTrust must replace this with their production signing key when they +// take over the shape. Private seed stays in this generator only; fixtures +// carry only the public half. +const PLACEHOLDER_SEED = '00000000000000000000000000000000000000000000000000000000000000aa' +const PLACEHOLDER_PUB = publicKeyFromPrivate(PLACEHOLDER_SEED) +const PLACEHOLDER_KID = 'moltrust-placeholder-v1' + +// Synthetic subject agent for placeholder fixtures. Matches the APS +// happy-path subject_agent so the shared-happy-path fixture overlaps. +const SUBJECT_AGENT_PUB = publicKeyFromPrivate( + '0000000000000000000000000000000000000000000000000000000000000004', +) +const APS_ISSUER_PUB = publicKeyFromPrivate( + '0000000000000000000000000000000000000000000000000000000000000001', +) +const DELEGATE_1_PUB = publicKeyFromPrivate( + '0000000000000000000000000000000000000000000000000000000000000005', +) + +// Fixed timestamps for byte-reproducibility. +const T0 = '2026-04-18T12:00:00Z' +const T1 = '2026-04-18T12:05:00Z' +const T2 = '2026-04-18T12:10:00Z' +const T3 = '2026-04-18T12:15:00Z' + +const sha256 = (s: string) => createHash('sha256').update(s).digest('hex') +const did = (pubHex: string) => `did:aps:${pubHex.slice(0, 32)}` +const chainRoot = (chain: unknown[]) => + `sha256:${sha256(canonicalizeJCS(chain))}` + +interface EmissionInput { + trust_level: number + attestation_count: number + last_verified: string + evidence_bundle: string + delegation_chain_root: string + subject_agent: string + extra?: Record +} + +// Sign a MolTrust-shaped emission. The signed payload is the composite +// header object itself (5 canonical fields + subject_agent + any extra +// vendor fields); the signature lives as a sibling field. +function signEmission(e: EmissionInput) { + const payload = { + trust_level: e.trust_level, + attestation_count: e.attestation_count, + last_verified: e.last_verified, + evidence_bundle: e.evidence_bundle, + delegation_chain_root: e.delegation_chain_root, + subject_agent: e.subject_agent, + issuer: 'moltrust', + ...(e.extra ?? {}), + } + const canonical = canonicalizeJCS(payload) + const sig = sign(canonical, PLACEHOLDER_SEED) + return { + ...payload, + signature: { + alg: 'EdDSA', + kid: PLACEHOLDER_KID, + pubkey: PLACEHOLDER_PUB, + canonicalization: 'RFC8785-JCS', + sig, + }, + } +} + +function writeFixture(name: string, fixture: unknown) { + const path = `${DIR}/${name}.json` + writeFileSync(path, JSON.stringify(fixture, null, 2) + '\n') + console.log(` wrote ${name}.json`) +} + +// Standard placeholder disclaimer carried on every fixture. +const placeholderMeta = { + _placeholder: true, + _replace_with_real_moltrust_emission: true, + _placeholder_notes: + 'Test-only MolTrust shape emitted by APS as a structural reference. ' + + 'Deterministic signing seed (tail 0xAA) is documented in ' + + 'moltrust-placeholder/generate-placeholder.ts. MolTrust replaces this ' + + 'with production emission + real signing key when Week 3 ships on their side.', +} + +// ═════════════════════════════════════════════════════════════════════ +// 1. trust-trajectory-decay.json +// trust_level steps down 4 → 3 → 2 across three progressive emissions. +// evidence_bundle is ipfs://... placeholder pointer. +// ═════════════════════════════════════════════════════════════════════ + +{ + const chain = [ + { + delegated_by: APS_ISSUER_PUB, + delegated_to: DELEGATE_1_PUB, + scope: ['tool:read'], + issued_at: T0, + expires_at: T3, + }, + ] + const root = chainRoot(chain) + const evidenceBundle = + 'ipfs://bafkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy' + + const emissions = [4, 3, 2].map((trustLevel, i) => + signEmission({ + trust_level: trustLevel, + attestation_count: (i + 1) * 2, + last_verified: [T0, T1, T2][i], + evidence_bundle: evidenceBundle, + delegation_chain_root: root, + subject_agent: did(SUBJECT_AGENT_PUB), + extra: { + sequence: i, + decay_reason: + i === 0 ? null : i === 1 ? 'behavioral_drift_observed' : 'policy_violation_confirmed', + }, + }), + ) + + // The header_value IS the latest emission (what a live consumer would see + // on the wire), plus a trajectory[] so consumers can inspect the series. + const latest = emissions[emissions.length - 1] + writeFixture('trust-trajectory-decay', { + fixture: 'trust-trajectory-decay', + description: + 'MolTrust-shaped placeholder: trust_level decay trajectory 4 → 3 → 2 across ' + + 'three progressive emissions over the same delegation_chain_root. The header_value ' + + 'mirrors the latest (decayed) emission; the trajectory[] field lets consumers ' + + 'inspect the series. evidence_bundle is an ipfs:// placeholder pointer. ' + + 'Every emission is Ed25519-signed with the placeholder test seed. MolTrust ' + + 'replaces this with their production emission shape.', + expected_verifier_output: 'valid', + format_variant: false, + spec_refs: ['a2aproject/A2A#1742'], + header_name: 'x-agent-trust', + moltrust_signing_key: { + kid: PLACEHOLDER_KID, + pubkey: PLACEHOLDER_PUB, + seed_tail: '0xAA', + notes: 'Deterministic test seed. Replace before production.', + }, + ...placeholderMeta, + header_value: { + ...latest, + trajectory: emissions, + }, + }) +} + +// ═════════════════════════════════════════════════════════════════════ +// 2. attestation-accumulation.json +// attestation_count grows 2 → 7 → 15 across three progressive emissions. +// trust_level holds at 3 throughout. +// ═════════════════════════════════════════════════════════════════════ + +{ + const chain = [ + { + delegated_by: APS_ISSUER_PUB, + delegated_to: SUBJECT_AGENT_PUB, + scope: ['tool:read', 'tool:write'], + issued_at: T0, + expires_at: T3, + }, + ] + const root = chainRoot(chain) + const evidenceBundle = + 'https://moltrust.example/bundles/' + + sha256(canonicalizeJCS({ subject: did(SUBJECT_AGENT_PUB), root })) + + const counts = [2, 7, 15] + const times = [T0, T1, T2] + const emissions = counts.map((count, i) => + signEmission({ + trust_level: 3, + attestation_count: count, + last_verified: times[i], + evidence_bundle: evidenceBundle, + delegation_chain_root: root, + subject_agent: did(SUBJECT_AGENT_PUB), + extra: { sequence: i, delta_from_previous: i === 0 ? count : count - counts[i - 1] }, + }), + ) + + const latest = emissions[emissions.length - 1] + writeFixture('attestation-accumulation', { + fixture: 'attestation-accumulation', + description: + 'MolTrust-shaped placeholder: attestation_count accumulation 2 → 7 → 15 across ' + + 'three progressive emissions with trust_level held at 3. Demonstrates the ' + + 'accumulation dimension of the 5-field composite header: an agent with many ' + + 'concurring attestations is meaningfully distinct from one with few, even at ' + + 'the same trust_level. evidence_bundle is an https:// URL. MolTrust replaces ' + + 'this with their production emission shape.', + expected_verifier_output: 'valid', + format_variant: false, + spec_refs: ['a2aproject/A2A#1742'], + header_name: 'x-agent-trust', + moltrust_signing_key: { + kid: PLACEHOLDER_KID, + pubkey: PLACEHOLDER_PUB, + seed_tail: '0xAA', + notes: 'Deterministic test seed. Replace before production.', + }, + ...placeholderMeta, + header_value: { + ...latest, + trajectory: emissions, + }, + }) +} + +// ═════════════════════════════════════════════════════════════════════ +// 3. shared-happy-path-moltrust.json +// Overlap-region fixture: same agent_card + delegation_chain + +// delegation_chain_root as APS happy-path, re-signed under MolTrust +// placeholder key with issuer='moltrust'. Demonstrates both providers +// can emit identical content on the shared happy-path space with only +// signing key + issuer differentiating. +// ═════════════════════════════════════════════════════════════════════ + +{ + // Rebuild the happy-path chain from the APS seeds so the chain root + // matches APS happy-path.json byte-for-byte. Needs APS seeds to re-sign + // the delegation links; we re-emit the links without signatures here + // because the chain root is computed over the LINK CONTENT only after + // APS's canonicalization, and APS signs each link with the principal's + // key. For this placeholder we just import the APS happy-path chain + // as-is from disk and reuse its root (see below). + + // Load the APS happy-path fixture to reuse its chain + root verbatim. + // This gives us byte-identical overlap with the APS fixture. + const apsHappyPath = JSON.parse( + readFileSync(`${DIR}/../happy-path.json`, 'utf8'), + ) as { + header_value: { + subject_agent: string + delegation_chain: unknown[] + delegation_chain_root: string + } + } + + const chain = apsHappyPath.header_value.delegation_chain + const root = apsHappyPath.header_value.delegation_chain_root + const subject = apsHappyPath.header_value.subject_agent + + const evidenceBundle = + 'https://moltrust.example/bundles/shared-happy-path-' + sha256(root) + + const emission = signEmission({ + trust_level: 4, + attestation_count: 1, + last_verified: T0, + evidence_bundle: evidenceBundle, + delegation_chain_root: root, + subject_agent: subject, + extra: { + overlap_with: 'happy-path.json', + overlap_note: + 'Same delegation_chain and delegation_chain_root as the APS happy-path ' + + 'fixture. Only issuer, signing key, and the 4 accumulation fields differ.', + }, + }) + + writeFixture('shared-happy-path-moltrust', { + fixture: 'shared-happy-path-moltrust', + description: + 'MolTrust-shaped placeholder sharing the APS happy-path delegation chain + root ' + + 'byte-for-byte, re-signed under the MolTrust placeholder key with ' + + 'issuer="moltrust". Demonstrates that both providers can emit identical ' + + 'content over the shared happy-path space with only issuer + signing key ' + + 'differentiation. MolTrust replaces this with their production emission shape.', + expected_verifier_output: 'valid', + format_variant: false, + spec_refs: ['a2aproject/A2A#1742', 'a2aproject/A2A#1628'], + header_name: 'x-agent-trust', + moltrust_signing_key: { + kid: PLACEHOLDER_KID, + pubkey: PLACEHOLDER_PUB, + seed_tail: '0xAA', + notes: 'Deterministic test seed. Replace before production.', + }, + ...placeholderMeta, + header_value: { + ...emission, + delegation_chain: chain, + overlap_ref: { + source: 'happy-path.json', + shared_fields: ['delegation_chain', 'delegation_chain_root', 'subject_agent'], + }, + }, + }) +} + +console.log('\n3 MolTrust-shaped placeholder fixtures generated.') diff --git a/a2a-trust-header/moltrust-placeholder/shared-happy-path-moltrust.json b/a2a-trust-header/moltrust-placeholder/shared-happy-path-moltrust.json new file mode 100644 index 0000000..b90e891 --- /dev/null +++ b/a2a-trust-header/moltrust-placeholder/shared-happy-path-moltrust.json @@ -0,0 +1,79 @@ +{ + "fixture": "shared-happy-path-moltrust", + "description": "MolTrust-shaped placeholder sharing the APS happy-path delegation chain + root byte-for-byte, re-signed under the MolTrust placeholder key with issuer=\"moltrust\". Demonstrates that both providers can emit identical content over the shared happy-path space with only issuer + signing key differentiation. MolTrust replaces this with their production emission shape.", + "expected_verifier_output": "valid", + "format_variant": false, + "spec_refs": [ + "a2aproject/A2A#1742", + "a2aproject/A2A#1628" + ], + "header_name": "x-agent-trust", + "moltrust_signing_key": { + "kid": "moltrust-placeholder-v1", + "pubkey": "71de3b4e933aa718a6f5c45845ee83af8000a450f9572d4cf393e681c8144191", + "seed_tail": "0xAA", + "notes": "Deterministic test seed. Replace before production." + }, + "_placeholder": true, + "_replace_with_real_moltrust_emission": true, + "_placeholder_notes": "Test-only MolTrust shape emitted by APS as a structural reference. Deterministic signing seed (tail 0xAA) is documented in moltrust-placeholder/generate-placeholder.ts. MolTrust replaces this with production emission + real signing key when Week 3 ships on their side.", + "header_value": { + "trust_level": 4, + "attestation_count": 1, + "last_verified": "2026-04-18T12:00:00Z", + "evidence_bundle": "https://moltrust.example/bundles/shared-happy-path-2f3ca114fc196b2d1417195bf1f407e496a3310f8faca699e190f377400e6c62", + "delegation_chain_root": "sha256:e1caf9662de1564c347e1351a486cfbe5459a8475310004d751d899dc43fc529", + "subject_agent": "did:aps:fd50b8e3b144ea244fbf7737f550bc8d", + "issuer": "moltrust", + "overlap_with": "happy-path.json", + "overlap_note": "Same delegation_chain and delegation_chain_root as the APS happy-path fixture. Only issuer, signing key, and the 4 accumulation fields differ.", + "signature": { + "alg": "EdDSA", + "kid": "moltrust-placeholder-v1", + "pubkey": "71de3b4e933aa718a6f5c45845ee83af8000a450f9572d4cf393e681c8144191", + "canonicalization": "RFC8785-JCS", + "sig": "cb8a384c7474a38e859f859c9b0e0e02ae9adb1f577428abc6ebe79304f3ea21385df65166f4a559764380828039794248fe139b164460c8756a54b525542204" + }, + "delegation_chain": [ + { + "delegated_by": "4cb5abf6ad79fbf5abbccafcc269d85cd2651ed4b885b5869f241aedf0a5ba29", + "delegated_to": "fde4fba030ad002f7c2f7d4c331f49d13fb0ec747eceebec634f1ff4cbca9def", + "scope": [ + "tool:read", + "tool:write" + ], + "issued_at": "2026-04-18T12:00:00Z", + "expires_at": "2026-04-18T12:15:00Z", + "signature": { + "alg": "EdDSA", + "kid": "did:aps:4cb5abf6ad79fbf5abbccafcc269d85c", + "pubkey": "4cb5abf6ad79fbf5abbccafcc269d85cd2651ed4b885b5869f241aedf0a5ba29", + "sig": "16884563550fa4c95257f662415a5f360055941521cef7cba793afafa47aa1f01c37a1ef289aaa99f0f8a74ae6c96e2f0c9642f3ba78257a39a92c728b818202" + } + }, + { + "delegated_by": "fde4fba030ad002f7c2f7d4c331f49d13fb0ec747eceebec634f1ff4cbca9def", + "delegated_to": "fd50b8e3b144ea244fbf7737f550bc8dd0c2650bbc1aada833ca17ff8dbf329b", + "scope": [ + "tool:read" + ], + "issued_at": "2026-04-18T12:00:00Z", + "expires_at": "2026-04-18T12:15:00Z", + "signature": { + "alg": "EdDSA", + "kid": "did:aps:fde4fba030ad002f7c2f7d4c331f49d1", + "pubkey": "fde4fba030ad002f7c2f7d4c331f49d13fb0ec747eceebec634f1ff4cbca9def", + "sig": "164c68c579afe299e263921237fadbd9fd7d6a765b3dc16818a081fefcaf2f3b42ddcc84baec137c13fb13084f1350c9d0c3181358144886dcd1316c514e6908" + } + } + ], + "overlap_ref": { + "source": "happy-path.json", + "shared_fields": [ + "delegation_chain", + "delegation_chain_root", + "subject_agent" + ] + } + } +} diff --git a/a2a-trust-header/moltrust-placeholder/trust-trajectory-decay.json b/a2a-trust-header/moltrust-placeholder/trust-trajectory-decay.json new file mode 100644 index 0000000..1dd4837 --- /dev/null +++ b/a2a-trust-header/moltrust-placeholder/trust-trajectory-decay.json @@ -0,0 +1,93 @@ +{ + "fixture": "trust-trajectory-decay", + "description": "MolTrust-shaped placeholder: trust_level decay trajectory 4 → 3 → 2 across three progressive emissions over the same delegation_chain_root. The header_value mirrors the latest (decayed) emission; the trajectory[] field lets consumers inspect the series. evidence_bundle is an ipfs:// placeholder pointer. Every emission is Ed25519-signed with the placeholder test seed. MolTrust replaces this with their production emission shape.", + "expected_verifier_output": "valid", + "format_variant": false, + "spec_refs": [ + "a2aproject/A2A#1742" + ], + "header_name": "x-agent-trust", + "moltrust_signing_key": { + "kid": "moltrust-placeholder-v1", + "pubkey": "71de3b4e933aa718a6f5c45845ee83af8000a450f9572d4cf393e681c8144191", + "seed_tail": "0xAA", + "notes": "Deterministic test seed. Replace before production." + }, + "_placeholder": true, + "_replace_with_real_moltrust_emission": true, + "_placeholder_notes": "Test-only MolTrust shape emitted by APS as a structural reference. Deterministic signing seed (tail 0xAA) is documented in moltrust-placeholder/generate-placeholder.ts. MolTrust replaces this with production emission + real signing key when Week 3 ships on their side.", + "header_value": { + "trust_level": 2, + "attestation_count": 6, + "last_verified": "2026-04-18T12:10:00Z", + "evidence_bundle": "ipfs://bafkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy", + "delegation_chain_root": "sha256:680fbc23d0f8283896c9b4fd8e7c31affe693b13b543d0abc0d83ed7ee6bf3ad", + "subject_agent": "did:aps:fd50b8e3b144ea244fbf7737f550bc8d", + "issuer": "moltrust", + "sequence": 2, + "decay_reason": "policy_violation_confirmed", + "signature": { + "alg": "EdDSA", + "kid": "moltrust-placeholder-v1", + "pubkey": "71de3b4e933aa718a6f5c45845ee83af8000a450f9572d4cf393e681c8144191", + "canonicalization": "RFC8785-JCS", + "sig": "aec87bdb476c1543ed46d2ad6e671bee8f32698d828deecbdb476400c74eff36c1fa2cc4feb8f6dc51716966ebc1437b10f8d8e14dc99015508446c941bcb705" + }, + "trajectory": [ + { + "trust_level": 4, + "attestation_count": 2, + "last_verified": "2026-04-18T12:00:00Z", + "evidence_bundle": "ipfs://bafkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy", + "delegation_chain_root": "sha256:680fbc23d0f8283896c9b4fd8e7c31affe693b13b543d0abc0d83ed7ee6bf3ad", + "subject_agent": "did:aps:fd50b8e3b144ea244fbf7737f550bc8d", + "issuer": "moltrust", + "sequence": 0, + "decay_reason": null, + "signature": { + "alg": "EdDSA", + "kid": "moltrust-placeholder-v1", + "pubkey": "71de3b4e933aa718a6f5c45845ee83af8000a450f9572d4cf393e681c8144191", + "canonicalization": "RFC8785-JCS", + "sig": "c9be066a24206d17a78770c5e3d32512b92938b8b70a4f867badb0644d16d0704ab3d97796f3e5ab429a8cacfd0d2a05c664ee281e7eded3dd516f5e6934750a" + } + }, + { + "trust_level": 3, + "attestation_count": 4, + "last_verified": "2026-04-18T12:05:00Z", + "evidence_bundle": "ipfs://bafkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy", + "delegation_chain_root": "sha256:680fbc23d0f8283896c9b4fd8e7c31affe693b13b543d0abc0d83ed7ee6bf3ad", + "subject_agent": "did:aps:fd50b8e3b144ea244fbf7737f550bc8d", + "issuer": "moltrust", + "sequence": 1, + "decay_reason": "behavioral_drift_observed", + "signature": { + "alg": "EdDSA", + "kid": "moltrust-placeholder-v1", + "pubkey": "71de3b4e933aa718a6f5c45845ee83af8000a450f9572d4cf393e681c8144191", + "canonicalization": "RFC8785-JCS", + "sig": "855a5512ef930add812db26fad76c72ea959e8a7c3da8ca62734ee3fe526fc8cb5bdb63e3f87190ca81923c330c82339032463e03dc1ac3334a9001fd37a610e" + } + }, + { + "trust_level": 2, + "attestation_count": 6, + "last_verified": "2026-04-18T12:10:00Z", + "evidence_bundle": "ipfs://bafkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy", + "delegation_chain_root": "sha256:680fbc23d0f8283896c9b4fd8e7c31affe693b13b543d0abc0d83ed7ee6bf3ad", + "subject_agent": "did:aps:fd50b8e3b144ea244fbf7737f550bc8d", + "issuer": "moltrust", + "sequence": 2, + "decay_reason": "policy_violation_confirmed", + "signature": { + "alg": "EdDSA", + "kid": "moltrust-placeholder-v1", + "pubkey": "71de3b4e933aa718a6f5c45845ee83af8000a450f9572d4cf393e681c8144191", + "canonicalization": "RFC8785-JCS", + "sig": "aec87bdb476c1543ed46d2ad6e671bee8f32698d828deecbdb476400c74eff36c1fa2cc4feb8f6dc51716966ebc1437b10f8d8e14dc99015508446c941bcb705" + } + } + ] + } +} diff --git a/a2a-trust-header/package-lock.json b/a2a-trust-header/package-lock.json new file mode 100644 index 0000000..3b8b83d --- /dev/null +++ b/a2a-trust-header/package-lock.json @@ -0,0 +1,782 @@ +{ + "name": "a2a-trust-header-vectors", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "a2a-trust-header-vectors", + "version": "0.1.0", + "dependencies": { + "@noble/ed25519": "^2.0.0", + "agent-passport-system": "^2.0.0", + "ajv": "^8.0.0", + "ajv-formats": "^3.0.0" + }, + "devDependencies": { + "tsx": "^4.0.0", + "typescript": "^5.0.0" + } + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.88.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.88.0.tgz", + "integrity": "sha512-QQOtB5U9ZBJQj6y1ICmDZl14LWa4JCiJRoihI+0yuZ4OjbONrakP0yLwPv4DJFb3VYCtQM31bTOpCBMs2zghPw==", + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "^3.1.1" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@google/generative-ai": { + "version": "0.24.1", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.24.1.tgz", + "integrity": "sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@noble/ed25519": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-2.3.0.tgz", + "integrity": "sha512-M7dvXL2B92/M7dw9+gzuydL8qn/jiqNHaoR3Q+cb1q1GHV7uwE17WCyFMG+Y+TZb5izcaXk5TdJRrDUxHXL78A==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/agent-passport-system": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/agent-passport-system/-/agent-passport-system-2.1.0.tgz", + "integrity": "sha512-xYJeKjf9to8ATX/8gQY4Iaw8wplLYJ6oSaVKxAT1jQa5pfGViPqeFsqXWX03BcAW/Jr65z+zc6qymLvx7F+S3w==", + "license": "Apache-2.0", + "dependencies": { + "@anthropic-ai/sdk": "^0.88.0", + "@google/generative-ai": "^0.24.1", + "libsodium-wrappers": "^0.8.2", + "openai": "^6.34.0", + "uuid": "^9.0.1" + }, + "bin": { + "agent-passport": "dist/src/cli/index.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/libsodium": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.8.4.tgz", + "integrity": "sha512-lMcYaRi0zcs7tarATsQUYC7rstliIXZuoq0c6zXSgNtSNtdvBgkSegjWhpMJAXzKX3SUSwIp7+zEsob+j3LuRw==", + "license": "ISC" + }, + "node_modules/libsodium-wrappers": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.8.4.tgz", + "integrity": "sha512-mu8aAWucZjTB5O/BtGXtW4e1agy7uHxNYG7zPthmmD1jU43LCDmSWZLN4JhflbdPXj3yDO4lxM1O9hLDgIOXDw==", + "license": "ISC", + "dependencies": { + "libsodium": "^0.8.0" + } + }, + "node_modules/openai": { + "version": "6.34.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.34.0.tgz", + "integrity": "sha512-yEr2jdGf4tVFYG6ohmr3pF6VJuveP0EA/sS8TBx+4Eq5NT10alu5zg2dmxMXMgqpihRDQlFGpRt2XwsGj+Fyxw==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "license": "MIT" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + } + } +} diff --git a/a2a-trust-header/package.json b/a2a-trust-header/package.json new file mode 100644 index 0000000..a7f288d --- /dev/null +++ b/a2a-trust-header/package.json @@ -0,0 +1,23 @@ +{ + "name": "a2a-trust-header-vectors", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "Week 2 + Week 3 fixtures, generators, and dual-provider consumer verifier for the A2A x-agent-trust header (A2A#1742).", + "scripts": { + "verify": "tsx verify.ts", + "consumer-verify": "tsx consumer-verify.ts", + "generate": "tsx generate.ts", + "generate-placeholder": "tsx moltrust-placeholder/generate-placeholder.ts" + }, + "dependencies": { + "@noble/ed25519": "^2.0.0", + "agent-passport-system": "^2.0.0", + "ajv": "^8.0.0", + "ajv-formats": "^3.0.0" + }, + "devDependencies": { + "tsx": "^4.0.0", + "typescript": "^5.0.0" + } +} diff --git a/a2a-trust-header/schema/a2a-trust-header.schema.json b/a2a-trust-header/schema/a2a-trust-header.schema.json new file mode 100644 index 0000000..ef239bd --- /dev/null +++ b/a2a-trust-header/schema/a2a-trust-header.schema.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://aeoess.com/schemas/a2a-trust-header/v1/a2a-trust-header.schema.json", + "title": "A2A x-agent-trust composite header", + "description": "Canonical 5-field composite header for A2A x-agent-trust extension. Locked in A2A#1742 between APS + MolTrust. Providers MAY emit additional sibling fields (additionalProperties=true); consumers MUST read these 5.", + "type": "object", + "required": [ + "trust_level", + "attestation_count", + "last_verified", + "evidence_bundle", + "delegation_chain_root" + ], + "properties": { + "trust_level": { + "type": "integer", + "minimum": 0, + "maximum": 4, + "description": "Reputation dimension. 0 = unverified / newly issued; 4 = highest-confidence issuer-backed." + }, + "attestation_count": { + "type": "integer", + "minimum": 0, + "description": "Number of attestations accumulated by the issuer for this agent at last_verified time." + }, + "last_verified": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 UTC timestamp of last issuer-side verification. Consumers MAY apply a half-life policy (MolTrust default: 45 days)." + }, + "evidence_bundle": { + "type": "string", + "description": "Pointer to the full attestation bundle. Typically an ipfs://... URI or an HTTPS URL.", + "pattern": "^(ipfs://|https?://).+" + }, + "delegation_chain_root": { + "type": "string", + "description": "Self-describing authority root. Inline hash form `sha256:` or pointer form `uri:https://...`. Both providers MUST emit sha256: for happy-path fixtures; drift/revocation cases MAY flag variant formats explicitly.", + "pattern": "^(sha256:[0-9a-f]{64}|uri:https?://.+)$" + } + }, + "additionalProperties": true +} diff --git a/a2a-trust-header/verify.ts b/a2a-trust-header/verify.ts index 3cf9583..6da7d2b 100644 --- a/a2a-trust-header/verify.ts +++ b/a2a-trust-header/verify.ts @@ -14,12 +14,14 @@ import { readFileSync } from 'node:fs' import { createHash } from 'node:crypto' +import { fileURLToPath } from 'node:url' +import { dirname } from 'node:path' import { canonicalizeJCS, verify, -} from '/Users/tima/agent-passport-system/src/index.js' +} from 'agent-passport-system' -const DIR = '/Users/tima/agent-governance-testvectors/a2a-trust-header' +const DIR = dirname(fileURLToPath(import.meta.url)) const sha256 = (s: string) => createHash('sha256').update(s).digest('hex')