From ca554229ac2c67f39336f5010e95e9e3c7428be0 Mon Sep 17 00:00:00 2001 From: jithinraj <7850727+jithinraj@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:12:10 +0530 Subject: [PATCH 01/10] fix(schema): remove deprecated ProofMethodSchema export MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Retire the ProofMethodSchema alias and the PROOF_METHODS constant. Transport-binding values (http-message-signature, dpop, mtls, jwk-thumbprint) are now inlined directly on AgentProofSchema.method. Trust-root proof models remain on ProofTypeSchema; transport-binding and trust-root stay separate. Source + tests: - packages/schema/src/agent-identity.ts: delete ProofMethodSchema, PROOF_METHODS, and the ProofMethod type. Inline the 4-value enum on AgentProofSchema.method. Runtime validation surface for AgentProof is unchanged (same accepted values, same rejection surface). - packages/schema/src/index.ts: remove the ProofMethodSchema schema export and the ProofMethod type export from the barrel. - packages/schema/src/actor-binding.ts: JSDoc rewritten to describe the transport-binding enum as inlined on AgentProofSchema.method and to record that transport-binding and trust-root taxonomies remain separate surfaces by design. - packages/schema/__tests__/agent-identity.test.ts: exercise the four values through AgentProofSchema.parse. Import list trimmed of removed symbols. - packages/schema/__tests__/actor-binding.test.ts: no-overlap assertion text references the inline method enum instead of the removed ProofMethodSchema export. - packages/schema/__tests__/proof-method-deprecation.test.ts: deleted; the deprecated surface it covered is gone. - tests/tooling/api-contract.test.ts: flip the deprecation-era assertion to a removal assertion (contract MUST NOT include ProofMethodSchema / PROOF_METHODS). - tests/tooling/__snapshots__/api-contract.test.ts.snap: drop the ProofMethod / PROOF_METHODS / ProofMethodSchema entries. Schema contract snapshot: - contracts/api/schema.json: regenerated via pnpm api-contract:extract. The diff is the three removed entries plus corresponding total-count decrements. - docs/releases/api-surface/schema.txt: exact 3-line removal (ProofMethod, ProofMethodSchema, PROOF_METHODS). Active-doc teaching updates: - docs/STABILITY-CONTRACT.md: ProofMethodSchema row flipped to Removed with the inline-enum migration note; table expanded to 4 columns with per-row status text. - docs/MIGRATION_CURRENT.md: new ProofMethodSchema removal section with per-call-site migration guidance (inline enum, derive type from schema, keep ProofTypeSchema separate). - docs/specs/AGENT-IDENTITY.md: AgentProof.method annotation shows the inlined enum instead of the removed ProofMethod type. - docs/specs/AGENT-IDENTITY-PROFILE.md: §1.1 / §1.2 / §3.4 / §9.1 rewritten to describe the method enum as inlined on AgentProofSchema.method and to state the decision to keep transport-binding and trust-root taxonomies separate. No wire format change. No kernel / crypto / protocol semantic change. The Wire 0.2 receipt shape and JWS envelope are untouched; only the Zod schema surface for AgentProof changes by inlining the same enum values it always accepted. --- contracts/api/schema.json | 15 +---- docs/MIGRATION_CURRENT.md | 21 +++++++ docs/STABILITY-CONTRACT.md | 16 +++--- docs/releases/api-surface/schema.txt | 3 - docs/specs/AGENT-IDENTITY-PROFILE.md | 36 ++++++------ docs/specs/AGENT-IDENTITY.md | 4 +- .../schema/__tests__/actor-binding.test.ts | 7 ++- .../schema/__tests__/agent-identity.test.ts | 33 ++++++----- .../proof-method-deprecation.test.ts | 56 ------------------- packages/schema/src/actor-binding.ts | 18 ++++-- packages/schema/src/agent-identity.ts | 35 ++---------- packages/schema/src/index.ts | 3 - .../__snapshots__/api-contract.test.ts.snap | 3 - tests/tooling/api-contract.test.ts | 9 ++- 14 files changed, 100 insertions(+), 159 deletions(-) delete mode 100644 packages/schema/__tests__/proof-method-deprecation.test.ts diff --git a/contracts/api/schema.json b/contracts/api/schema.json index f88dc6f96..1d926d16c 100644 --- a/contracts/api/schema.json +++ b/contracts/api/schema.json @@ -1,7 +1,7 @@ { "package": "@peac/schema", "version": "0.12.14", - "extracted_at": "2026-04-22", + "extracted_at": "2026-04-24", "node_version": "v24.13.0", "value_exports": [ { @@ -676,10 +676,6 @@ "name": "PRIVACY_EXTENSION_KEY", "typeof": "static" }, - { - "name": "PROOF_METHODS", - "typeof": "static" - }, { "name": "PROOF_TYPES", "typeof": "static" @@ -740,10 +736,6 @@ "name": "ProblemDetailsSchema", "typeof": "static" }, - { - "name": "ProofMethodSchema", - "typeof": "static" - }, { "name": "ProofTypeSchema", "typeof": "static" @@ -1681,7 +1673,6 @@ "PolicyContext", "PolicyDecision", "PrivacyExtension", - "ProofMethod", "ProofType", "ProvenanceExtension", "PurposeDecision", @@ -1795,6 +1786,6 @@ "PurposeExtensionSchema", "SafetyExtensionSchema" ], - "total_value_exports": 386, - "total_type_exports": 190 + "total_value_exports": 384, + "total_type_exports": 189 } diff --git a/docs/MIGRATION_CURRENT.md b/docs/MIGRATION_CURRENT.md index c74016775..2a8d980a1 100644 --- a/docs/MIGRATION_CURRENT.md +++ b/docs/MIGRATION_CURRENT.md @@ -2,6 +2,27 @@ This guide covers migration paths for current PEAC Protocol surfaces. +## ProofMethodSchema removal (v0.13.0) + +`ProofMethodSchema`, `PROOF_METHODS`, and the `ProofMethod` type were deprecated in v0.12.2 (DD-185) and **removed in v0.13.0 PR B**. The deprecated standalone schema export retired because transport-binding methods are semantically distinct from trust-root proof models; the two concerns should not share a public surface. + +`AgentProofSchema.method` still accepts the same four transport-binding values — the enum is now inlined on the field definition: + +- `http-message-signature` +- `dpop` +- `mtls` +- `jwk-thumbprint` + +Migration by use site: + +- **If you imported `ProofMethodSchema` to validate a method string in isolation:** inline the enum yourself (`z.enum(['http-message-signature', 'dpop', 'mtls', 'jwk-thumbprint'])`) or, preferably, validate the whole proof object through `AgentProofSchema.parse(...)`. +- **If you imported the `ProofMethod` type for a function signature:** replace with `"http-message-signature" | "dpop" | "mtls" | "jwk-thumbprint"` or derive from the schema: `type Method = z.infer['method']`. +- **If you want trust-root proof semantics (how an identity proves itself):** use `ProofTypeSchema` and the `PROOF_TYPES` constant. `ProofType` (8 values: `did-web`, `did-key`, `did-plc`, `did-ion`, `x509-chain`, `ed25519-cert-chain`, `hsm-attestation`, `custom`) was always the canonical trust-root surface; it is unchanged. + +**No wire-format change.** Existing valid `AgentProofSchema.method` values continue to validate. No envelope, no `typ`, no signing change. + +See `docs/PACKAGE_STATUS_V0.13.0_PARITY.md` for the per-export parity table used to scope v0.13.0 schema removals. + ## Package-surface cleanup (v0.13.0) v0.13.0 finishes the scheduled package-surface cleanup. Deprecate-then-remove discipline applies throughout: historical npm versions are never unpublished. diff --git a/docs/STABILITY-CONTRACT.md b/docs/STABILITY-CONTRACT.md index 712b79ac2..f39d1793b 100644 --- a/docs/STABILITY-CONTRACT.md +++ b/docs/STABILITY-CONTRACT.md @@ -156,14 +156,14 @@ Path: [`surfaces/plugin-pack/`](../surfaces/plugin-pack/). ## Deprecation schedule -| Surface | Deprecated since | Removal target | -| --------------------------------------------------------- | ---------------- | -------------- | -| `ProofMethodSchema` (compat alias) | v0.12.2 | v0.13.0 | -| A2A v0.3.0 compatibility path | v0.12.3 | v0.13.0 | -| Legacy `POST /verify` endpoint (in favor of `/v1/verify`) | v0.12.x | v0.13.0 | -| `packages/sdk-js/` workspace stub | v0.12.x | v0.13.0 | -| `peac.receipt/0.9` archival format | Legacy frozen | v0.13.0 | -| `@peac/core` archival verify-only path | Legacy frozen | v0.13.0 | +| Surface | Deprecated since | Removal target | Status | +| --------------------------------------------------------- | ---------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ProofMethodSchema` (compat alias) | v0.12.2 | v0.13.0 | **Removed in v0.13.0 PR B.** Transport-binding values (`http-message-signature`, `dpop`, `mtls`, `jwk-thumbprint`) inlined on `AgentProofSchema.method`. | +| A2A v0.3.0 compatibility path | v0.12.3 | v0.13.0 PR B | Scheduled | +| Legacy `POST /verify` endpoint (in favor of `/v1/verify`) | v0.12.x | post-Sunset (2026-11-01) | v0.13.0 PR B removes from active OpenAPI teaching; runtime alias preserved until advertised Sunset date | +| `packages/sdk-js/` workspace stub | v0.12.x | v0.13.0 PR B | Scheduled | +| `peac.receipt/0.9` archival format | Legacy frozen | v0.13.0 (quarantine) | Quarantined to historical contexts; wire stays frozen | +| `@peac/core` archival verify-only path | Legacy frozen | v0.13.0 PR B | Scheduled (coupled with legacy `/verify` handler rewire) | All status transitions are tracked in [`REPO_SURFACE_STATUS.json`](../REPO_SURFACE_STATUS.json) and mirrored in diff --git a/docs/releases/api-surface/schema.txt b/docs/releases/api-surface/schema.txt index 7beaf319c..594d91f14 100644 --- a/docs/releases/api-surface/schema.txt +++ b/docs/releases/api-surface/schema.txt @@ -207,7 +207,6 @@ OrchestrationFrameworkSchema PEACEnvelope PEACParseError POLICY_DECISIONS -PROOF_METHODS PROOF_TYPES ParseFailure ParseReceiptOptions @@ -226,8 +225,6 @@ PolicyContext PolicyContextSchema PolicyDecision ProblemDetailsSchema -ProofMethod -ProofMethodSchema ProofType ProofTypeSchema PurposeReasonSchema diff --git a/docs/specs/AGENT-IDENTITY-PROFILE.md b/docs/specs/AGENT-IDENTITY-PROFILE.md index 44b20d3e9..c9d65f269 100644 --- a/docs/specs/AGENT-IDENTITY-PROFILE.md +++ b/docs/specs/AGENT-IDENTITY-PROFILE.md @@ -22,7 +22,7 @@ This specification extends the base [AGENT-IDENTITY.md](AGENT-IDENTITY.md) speci 2. **Multi-Root Proof Types** (DD-143): An expanded proof type vocabulary supporting 8 cryptographic and identity proof mechanisms 3. **Minimum Viable Identity Set (MVIS)** (DD-144): Five required fields that constitute a complete identity receipt -These additions are additive to Wire 0.1. All identity data flows through `ext[]` extension slots using reverse-DNS keys per PROFILE_RULES.md. The existing `AgentIdentityAttestation` type and `ProofMethod` enum (4 methods) remain unchanged; the new `ProofType` vocabulary (8 types) is separate. +These additions are additive to Wire 0.1. All identity data flows through `ext[]` extension slots using reverse-DNS keys per PROFILE_RULES.md. The existing `AgentIdentityAttestation` type remains unchanged. The four transport-binding values previously exported as `ProofMethodSchema` (`http-message-signature`, `dpop`, `mtls`, `jwk-thumbprint`) are now inlined directly on `AgentProofSchema.method`; the standalone `ProofMethodSchema` / `ProofMethod` / `PROOF_METHODS` exports were removed in v0.13.0 (DD-185). The new `ProofType` vocabulary (8 types) is a separate trust-root taxonomy. ### 1.2 Scope @@ -38,7 +38,7 @@ This specification does NOT cover: - Changes to the base `AgentIdentityAttestation` schema (see [AGENT-IDENTITY.md](AGENT-IDENTITY.md)) - Wire format modifications (Wire 0.1 is frozen) - EAT adapter implementation (deferred to v0.12.0-preview.1; see DD-154) -- Unification of `ProofMethod` and `ProofType` (deferred to v0.12.0) +- Unification of the transport-binding method enum (`AgentProofSchema.method`) and `ProofType` under a single taxonomy (intentionally rejected: transport-binding and trust-root concerns are semantically distinct surfaces and remain separate) ### 1.3 Requirements Notation @@ -267,20 +267,20 @@ Implementations using `custom` SHOULD document: The proof type vocabulary is **extensible via `registries.json`**: new proof types can be added in future versions. Implementations encountering an unknown `proof_type` value SHOULD treat it as equivalent to `custom` (process the receipt, log a warning) rather than rejecting the receipt. -### 3.4 Relationship to ProofMethod +### 3.4 Relationship to the transport-binding method enum -The existing `ProofMethod` enum in [AGENT-IDENTITY.md](AGENT-IDENTITY.md) (4 methods: `http-message-signature`, `dpop`, `mtls`, `jwk-thumbprint`) describes how an agent proves control of a key in real-time HTTP requests. +The `AgentProofSchema.method` field in [AGENT-IDENTITY.md](AGENT-IDENTITY.md) (four transport-binding values: `http-message-signature`, `dpop`, `mtls`, `jwk-thumbprint`) describes how an agent proves control of a key in real-time HTTP requests. In v0.13.0 (DD-185) the four values were inlined directly on the field; the standalone `ProofMethodSchema` / `ProofMethod` / `PROOF_METHODS` exports were removed because transport-binding concerns are semantically distinct from trust-root taxonomy. -The new `ProofType` vocabulary (8 types) describes the trust root mechanism for an actor's identity in receipts. +The `ProofType` vocabulary (8 types) describes the trust-root mechanism for an actor's identity in receipts. These are **separate concerns**: -| Concern | Type | Vocabulary | Schema | -| --------------------------------- | ------------- | ---------- | ------------------------- | -| Request-time proof of key control | `ProofMethod` | 4 methods | `AgentProof.method` | -| Receipt-time identity trust root | `ProofType` | 8 types | `ActorBinding.proof_type` | +| Concern | Surface | Vocabulary | Schema | +| --------------------------------- | ------------------------- | ------------------ | ------------------------- | +| Request-time proof of key control | `AgentProofSchema.method` | 4 transport values | `AgentProof.method` | +| Receipt-time identity trust root | `ProofTypeSchema` | 8 trust-root types | `ActorBinding.proof_type` | -Unification of `ProofMethod` and `ProofType` into a single taxonomy is deferred to v0.12.0 as it would be a breaking change to the existing API. +Unification of the transport-binding method enum with `ProofType` under a single taxonomy was considered and rejected in v0.13.0 (DD-185): the concerns are semantically distinct and unifying them would conflate how a key is proven in transit with how an identity's trust root is established. The two surfaces remain separate by design. --- @@ -626,16 +626,16 @@ All five fields present; MVIS satisfied. Existing systems using `AgentIdentityAttestation` (v0.9.25) can adopt ActorBinding incrementally: 1. **Phase 1**: Add `org.peacprotocol/actor_binding` to receipts alongside existing attestation headers -2. **Phase 2**: Map existing `ProofMethod` to the closest `ProofType`: +2. **Phase 2**: Pick a `proof_type` (trust-root) that matches how the identity is established, independent of how the key is proven in transit. The transport-binding method on `AgentProofSchema.method` and the receipt-time trust root carried by `ProofType` compose rather than conflict: -| ProofMethod (existing) | ProofType (new) | -| ------------------------ | -------------------------------- | -| `http-message-signature` | `ed25519-cert-chain` or `custom` | -| `dpop` | `custom` | -| `mtls` | `x509-pki` | -| `jwk-thumbprint` | `ed25519-cert-chain` | +| Transport-binding method (`AgentProofSchema.method`) | Typical trust root (`ActorBinding.proof_type`) | +| ---------------------------------------------------- | ---------------------------------------------- | +| `http-message-signature` | `ed25519-cert-chain` or `custom` | +| `dpop` | `custom` | +| `mtls` | `x509-pki` | +| `jwk-thumbprint` | `ed25519-cert-chain` | -3. **Phase 3**: At v0.12.0, unified taxonomy replaces both vocabularies +3. **Phase 3 (no longer on the roadmap)**: Unification of the transport-binding method enum with `ProofType` was considered and rejected in v0.13.0. The two surfaces stay separate because they answer different questions (how the key is proven in the moment vs. how the identity's trust root is rooted). Systems should continue to populate both fields when both signals exist. ### 9.2 From No Identity to MVIS diff --git a/docs/specs/AGENT-IDENTITY.md b/docs/specs/AGENT-IDENTITY.md index 1fc4760f6..5248a1cfd 100644 --- a/docs/specs/AGENT-IDENTITY.md +++ b/docs/specs/AGENT-IDENTITY.md @@ -112,7 +112,9 @@ interface AgentIdentityEvidence { ```typescript interface AgentProof { - method: ProofMethod; // REQUIRED: Proof method + // REQUIRED: transport-binding method. Enum inlined on the field + // (previously exported as ProofMethodSchema; removed in v0.13.0). + method: 'http-message-signature' | 'dpop' | 'mtls' | 'jwk-thumbprint'; key_id: string; // REQUIRED: Key identifier (1-256 chars) alg?: string; // OPTIONAL: Algorithm (default: 'EdDSA') signature?: string; // OPTIONAL: Base64url signature diff --git a/packages/schema/__tests__/actor-binding.test.ts b/packages/schema/__tests__/actor-binding.test.ts index 7efa83136..67ebd5457 100644 --- a/packages/schema/__tests__/actor-binding.test.ts +++ b/packages/schema/__tests__/actor-binding.test.ts @@ -51,9 +51,10 @@ describe('ProofTypeSchema', () => { expect(PROOF_TYPES).toContain('custom'); }); - it('should be separate from ProofMethodSchema (no overlap with transport methods)', () => { - // ProofTypeSchema covers trust root models, not transport mechanisms - // ProofMethodSchema covers: http-message-signature, dpop, mtls, jwk-thumbprint + it('should be separate from the transport-binding method enum (no overlap)', () => { + // ProofTypeSchema covers trust-root models; transport-binding methods + // (http-message-signature / dpop / mtls / jwk-thumbprint) are inlined + // on AgentProofSchema.method and are NOT valid ProofType values. expect(() => ProofTypeSchema.parse('http-message-signature')).toThrow(); expect(() => ProofTypeSchema.parse('dpop')).toThrow(); expect(() => ProofTypeSchema.parse('mtls')).toThrow(); diff --git a/packages/schema/__tests__/agent-identity.test.ts b/packages/schema/__tests__/agent-identity.test.ts index ed6e91c83..a01a6a606 100644 --- a/packages/schema/__tests__/agent-identity.test.ts +++ b/packages/schema/__tests__/agent-identity.test.ts @@ -4,7 +4,6 @@ import { describe, it, expect } from 'vitest'; import { ControlTypeSchema, - ProofMethodSchema, BindingDetailsSchema, AgentProofSchema, AgentIdentityEvidenceSchema, @@ -13,7 +12,6 @@ import { AgentIdentityVerifiedSchema, AGENT_IDENTITY_TYPE, CONTROL_TYPES, - PROOF_METHODS, validateAgentIdentityAttestation, isAgentIdentityAttestation, createAgentIdentityAttestation, @@ -43,21 +41,28 @@ describe('ControlTypeSchema', () => { }); }); -describe('ProofMethodSchema', () => { - it('should accept valid proof methods', () => { - expect(ProofMethodSchema.parse('http-message-signature')).toBe('http-message-signature'); - expect(ProofMethodSchema.parse('dpop')).toBe('dpop'); - expect(ProofMethodSchema.parse('mtls')).toBe('mtls'); - expect(ProofMethodSchema.parse('jwk-thumbprint')).toBe('jwk-thumbprint'); - }); +describe('AgentProofSchema.method (transport-binding enum; inlined v0.13.0)', () => { + // ProofMethodSchema was removed in v0.13.0 after the DD-185 deprecation + // horizon. The 4 transport-binding values are now inlined on + // AgentProofSchema.method. Runtime validation must still accept the same + // four values and reject everything else. + const validMethods = ['http-message-signature', 'dpop', 'mtls', 'jwk-thumbprint'] as const; + const baseProof = { + key_id: 'k1', + alg: 'EdDSA', + signature: 'sig', + }; - it('should reject invalid proof methods', () => { - expect(() => ProofMethodSchema.parse('unknown')).toThrow(); - expect(() => ProofMethodSchema.parse('signature')).toThrow(); + it('accepts the four canonical transport-binding methods', () => { + for (const method of validMethods) { + expect(AgentProofSchema.parse({ ...baseProof, method }).method).toBe(method); + } }); - it('should match PROOF_METHODS constant', () => { - expect(PROOF_METHODS).toEqual(['http-message-signature', 'dpop', 'mtls', 'jwk-thumbprint']); + it('rejects values outside the transport-binding enum', () => { + for (const method of ['unknown', 'signature', 'ed25519-cert-chain', '', 'custom']) { + expect(() => AgentProofSchema.parse({ ...baseProof, method })).toThrow(); + } }); }); diff --git a/packages/schema/__tests__/proof-method-deprecation.test.ts b/packages/schema/__tests__/proof-method-deprecation.test.ts deleted file mode 100644 index c7f39b485..000000000 --- a/packages/schema/__tests__/proof-method-deprecation.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * ProofMethodSchema deprecation contract tests - * - * Validates that the deprecated ProofMethodSchema, ProofMethod type, - * and PROOF_METHODS array remain functional and importable from the - * package barrel through v0.12.x. Imports use the barrel to prove - * public surface availability (not internal module paths). - */ - -import { describe, it, expect } from 'vitest'; -import { ProofMethodSchema, PROOF_METHODS } from '../src/index'; - -describe('ProofMethodSchema deprecation contract', () => { - it('is exported from barrel', () => { - expect(ProofMethodSchema).toBeDefined(); - expect(PROOF_METHODS).toBeDefined(); - }); - - it('parses all 4 valid transport-binding methods at runtime', () => { - const expected = ['http-message-signature', 'dpop', 'mtls', 'jwk-thumbprint'] as const; - for (const method of expected) { - expect(ProofMethodSchema.parse(method)).toBe(method); - } - }); - - it('rejects values not in the transport-binding enum', () => { - expect(() => ProofMethodSchema.parse('ed25519-cert-chain')).toThrow(); - expect(() => ProofMethodSchema.parse('custom')).toThrow(); - expect(() => ProofMethodSchema.parse('')).toThrow(); - expect(() => ProofMethodSchema.parse(123)).toThrow(); - expect(() => ProofMethodSchema.parse(null)).toThrow(); - expect(() => ProofMethodSchema.parse(undefined)).toThrow(); - }); - - it('PROOF_METHODS array matches schema enum values exactly', () => { - expect(PROOF_METHODS).toEqual(['http-message-signature', 'dpop', 'mtls', 'jwk-thumbprint']); - expect(PROOF_METHODS).toHaveLength(4); - }); - - it('PROOF_METHODS values all round-trip through schema', () => { - for (const method of PROOF_METHODS) { - expect(ProofMethodSchema.parse(method)).toBe(method); - } - }); - - it('safeParse returns success for valid values and failure for invalid', () => { - const valid = ProofMethodSchema.safeParse('dpop'); - expect(valid.success).toBe(true); - if (valid.success) { - expect(valid.data).toBe('dpop'); - } - - const invalid = ProofMethodSchema.safeParse('nonexistent'); - expect(invalid.success).toBe(false); - }); -}); diff --git a/packages/schema/src/actor-binding.ts b/packages/schema/src/actor-binding.ts index 32218a2af..ebe5da9d4 100644 --- a/packages/schema/src/actor-binding.ts +++ b/packages/schema/src/actor-binding.ts @@ -5,8 +5,13 @@ * and (MVIS) for the Agent Identity Profile. * * ActorBinding lives in ext["org.peacprotocol/actor_binding"] in Wire 0.1. - * ProofTypeSchema is SEPARATE from ProofMethodSchema (agent-identity.ts) - * to avoid breaking the v0.9.25+ API. Unification deferred to v0.12.0. + * ProofTypeSchema (trust-root taxonomy) is SEPARATE from the transport-binding + * method enum on AgentProofSchema.method (agent-identity.ts). Unifying them + * into a single taxonomy was considered and rejected in v0.13.0 (DD-185): + * transport-binding and trust-root concerns are semantically distinct and + * remain separate surfaces. The ProofMethodSchema / PROOF_METHODS / ProofMethod + * standalone exports were removed in v0.13.0; the four transport-binding + * values are now inlined directly on AgentProofSchema.method. * * @see docs/specs/AGENT-IDENTITY-PROFILE.md for normative specification */ @@ -22,9 +27,12 @@ import { z } from 'zod'; * 8 methods covering attestation chains, RATS, keyless signing, * decentralized identity, workload identity, PKI, and vendor-defined. * - * SEPARATE from ProofMethodSchema (4 transport-level methods in agent-identity.ts). - * ProofMethodSchema covers how proof is transported (HTTP sig, DPoP, mTLS, JWK thumbprint). - * ProofTypeSchema covers the trust root model used to establish identity. + * SEPARATE from the transport-binding method enum inlined on + * AgentProofSchema.method (agent-identity.ts). That enum covers how proof + * is transported (HTTP signatures, DPoP, mTLS, JWK thumbprint). + * ProofTypeSchema covers the trust-root model used to establish identity. + * The two concerns compose rather than conflict; see + * docs/specs/AGENT-IDENTITY-PROFILE.md §3.4 for the mapping table. * * The 'custom' type: implementers MUST document their proof semantics externally. * proof_ref SHOULD use a reverse-DNS namespace (e.g., 'com.example.vendor/proof-type-v1'). diff --git a/packages/schema/src/agent-identity.ts b/packages/schema/src/agent-identity.ts index e5f748650..a1574015b 100644 --- a/packages/schema/src/agent-identity.ts +++ b/packages/schema/src/agent-identity.ts @@ -28,33 +28,6 @@ export type ControlType = z.infer; */ export const CONTROL_TYPES = ['operator', 'user-delegated'] as const; -// ============================================================================= -// PROOF METHOD (v0.9.25+) -// ============================================================================= - -/** - * @deprecated ProofMethodSchema is deprecated as of v0.12.2. - * Transport-level binding methods (HTTP signatures, DPoP, mTLS, JWK thumbprint) - * are semantically distinct from trust-root models (ProofTypeSchema). - * This alias remains functional through v0.12.x. No consumer action required now. - * In v0.13.0, AgentProofSchema.method will migrate to either an inline enum - * or a dedicated TransportBindingMethodSchema. Remove-not-before: v0.13.0. - * - * @see ProofTypeSchema for the canonical trust-root model schema - */ -export const ProofMethodSchema = z.enum([ - 'http-message-signature', - 'dpop', - 'mtls', - 'jwk-thumbprint', -]); -export type ProofMethod = z.infer; - -/** - * @deprecated See ProofMethodSchema deprecation note. - */ -export const PROOF_METHODS = ['http-message-signature', 'dpop', 'mtls', 'jwk-thumbprint'] as const; - // ============================================================================= // BINDING DETAILS (v0.9.25+) // ============================================================================= @@ -94,10 +67,12 @@ export type BindingDetails = z.infer; export const AgentProofSchema = z .object({ /** - * Proof method used. - * @see ProofMethodSchema - deprecated in v0.12.2; will migrate in v0.13.0 + * Transport-binding proof method. The four values below are the + * canonical transport-layer bindings recognized by PEAC; they are + * semantically distinct from trust-root models (see `ProofTypeSchema`). + * Inlined in v0.13.0 after the DD-185 deprecation horizon closed. */ - method: ProofMethodSchema, + method: z.enum(['http-message-signature', 'dpop', 'mtls', 'jwk-thumbprint']), /** Key ID (matches kid in JWS header or JWKS) */ key_id: z.string().min(1).max(256), diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts index aea04d7ef..6492a878b 100644 --- a/packages/schema/src/index.ts +++ b/packages/schema/src/index.ts @@ -94,7 +94,6 @@ export type { EvidenceValidationResult, ReceiptClaimsType } from './validators'; // Agent Identity validators (v0.9.25+) export { ControlTypeSchema, - ProofMethodSchema, BindingDetailsSchema, AgentProofSchema, AgentIdentityEvidenceSchema, @@ -104,7 +103,6 @@ export { // Constants AGENT_IDENTITY_TYPE, CONTROL_TYPES, - PROOF_METHODS, // Helpers validateAgentIdentityAttestation, isAgentIdentityAttestation, @@ -115,7 +113,6 @@ export { } from './agent-identity'; export type { ControlType, - ProofMethod, BindingDetails, AgentProof, AgentIdentityEvidence, diff --git a/tests/tooling/__snapshots__/api-contract.test.ts.snap b/tests/tooling/__snapshots__/api-contract.test.ts.snap index 75efea87f..26ebbfeef 100644 --- a/tests/tooling/__snapshots__/api-contract.test.ts.snap +++ b/tests/tooling/__snapshots__/api-contract.test.ts.snap @@ -326,7 +326,6 @@ exports[`API contract: @peac/schema > barrel type exports match snapshot (static "PolicyContext", "PolicyDecision", "PrivacyExtension", - "ProofMethod", "ProofType", "ProvenanceExtension", "PurposeExtension", @@ -540,7 +539,6 @@ exports[`API contract: @peac/schema > value exports match snapshot 1`] = ` "PEAC_WIRE_TYP", "POLICY_DECISIONS", "PRIVACY_EXTENSION_KEY", - "PROOF_METHODS", "PROOF_TYPES", "PROVENANCE_EXTENSION_KEY", "PURPOSE_EXTENSION_KEY", @@ -556,7 +554,6 @@ exports[`API contract: @peac/schema > value exports match snapshot 1`] = ` "PolicyContextSchema", "PrivacyExtensionSchema", "ProblemDetailsSchema", - "ProofMethodSchema", "ProofTypeSchema", "ProvenanceExtensionSchema", "PurposeExtensionSchema", diff --git a/tests/tooling/api-contract.test.ts b/tests/tooling/api-contract.test.ts index f574f73d9..503531bdc 100644 --- a/tests/tooling/api-contract.test.ts +++ b/tests/tooling/api-contract.test.ts @@ -145,9 +145,12 @@ describe('API contract: @peac/schema', () => { expect(schemas).toHaveLength(13); }); - it('ProofMethodSchema remains exported (deprecated, compat through v0.12.x)', () => { - expect(exports).toContain('ProofMethodSchema'); - expect(exports).toContain('PROOF_METHODS'); + it('ProofMethodSchema is removed (DD-185; removed in v0.13.0 PR B)', () => { + // Transport-binding method values (http-message-signature, dpop, mtls, + // jwk-thumbprint) are now inlined on AgentProofSchema.method. See + // docs/MIGRATION_CURRENT.md and docs/STABILITY-CONTRACT.md. + expect(exports).not.toContain('ProofMethodSchema'); + expect(exports).not.toContain('PROOF_METHODS'); }); it('ProofTypeSchema remains exported (canonical trust-root model)', () => { From 697b122463536fb0f26a05509b27e86a4b128411 Mon Sep 17 00:00:00 2001 From: jithinraj <7850727+jithinraj@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:12:43 +0530 Subject: [PATCH 02/10] chore(release): refresh API surface snapshots Snapshot-cleanup follow-on to the ProofMethodSchema removal. These diffs are not caused by the schema change; they are a snapshot refresh surfaced by the full `bash scripts/release/api-surface-lock.sh --update` regeneration pass plus the archival of `@peac/sdk`. Contents: - contracts/api/{crypto,kernel,protocol}.json: benign extraction timestamp bump (2026-04-22 -> 2026-04-24). Value and type export lists unchanged for these three packages. - docs/releases/api-surface/schema.txt: regenerated to current @peac/schema .d.ts state. Picks up legitimate v0.12.x schema surface additions that had drifted since the prior snapshot: AttributionExtension / AttributionExtensionSchema, AmountMinorStringSchema, COMPLIANCE_EXTENSION_KEY, COMPLIANCE_STATUSES, ComplianceExtension / ComplianceStatus, CONSENT_EXTENSION_KEY, CONSENT_STATUSES, ConsentExtension / ConsentStatus, CONTENT_SIGNAL_SOURCES, getComplianceExtension, getConsentExtension, getPrivacyExtension, getProvenanceExtension, getPurposeExtension, getSafetyExtension, isValidAmountMinor, and related exports. All already exported from @peac/schema source; the snapshot was stale. The prior commit (ProofMethodSchema removal) carried only the 3-line semantic removal; this commit carries the snapshot refresh so the semantic commit stays clean for review. - docs/releases/api-surface/kernel.txt: pick up legitimate v0.12.x kernel surface additions that had drifted since the prior snapshot: EXTENSION_BUDGET, EXTENSION_GROUPS, ExtensionGroupEntry, PILLAR_VALUES, PROOF_TYPES, ProofTypeEntry, RECEIPT_TYPES, ReceiptTypeEntry, TYPE_TO_EXTENSION_MAP, findExtensionGroup, findProofType, findReceiptType, and related exports. All already exported from @peac/kernel source; the snapshot was stale. - docs/releases/api-surface/protocol.txt: similar snapshot refresh for JWK / JWKS and related protocol exports that had drifted since the prior snapshot. - docs/releases/api-surface/sdk.txt: deleted. @peac/sdk source was archived in prior releases; tracking the absent package produced a "package not found" sentinel on regeneration. Historical snapshot preserved in git history. - scripts/release/api-surface-lock.sh: TRACKED_PACKAGES updated to drop @peac/sdk with an inline comment pointing to archive/sdk-js/. No source change. No behavior change. No wire format change. --- contracts/api/crypto.json | 2 +- contracts/api/kernel.json | 2 +- contracts/api/protocol.json | 2 +- docs/releases/api-surface/kernel.txt | 12 +++++ docs/releases/api-surface/protocol.txt | 16 ++++++ docs/releases/api-surface/schema.txt | 67 ++++++++++++++++++++++++++ docs/releases/api-surface/sdk.txt | 13 ----- scripts/release/api-surface-lock.sh | 3 +- 8 files changed, 100 insertions(+), 17 deletions(-) delete mode 100644 docs/releases/api-surface/sdk.txt diff --git a/contracts/api/crypto.json b/contracts/api/crypto.json index 3511fd9c0..d03fcf23c 100644 --- a/contracts/api/crypto.json +++ b/contracts/api/crypto.json @@ -1,7 +1,7 @@ { "package": "@peac/crypto", "version": "0.12.14", - "extracted_at": "2026-04-22", + "extracted_at": "2026-04-24", "node_version": "v24.13.0", "value_exports": [ { diff --git a/contracts/api/kernel.json b/contracts/api/kernel.json index ab904da58..e146483c1 100644 --- a/contracts/api/kernel.json +++ b/contracts/api/kernel.json @@ -1,7 +1,7 @@ { "package": "@peac/kernel", "version": "0.12.14", - "extracted_at": "2026-04-22", + "extracted_at": "2026-04-24", "node_version": "v24.13.0", "value_exports": [ { diff --git a/contracts/api/protocol.json b/contracts/api/protocol.json index 44ea937b5..84c0a8b29 100644 --- a/contracts/api/protocol.json +++ b/contracts/api/protocol.json @@ -1,7 +1,7 @@ { "package": "@peac/protocol", "version": "0.12.14", - "extracted_at": "2026-04-22", + "extracted_at": "2026-04-24", "node_version": "v24.13.0", "value_exports": [ { diff --git a/docs/releases/api-surface/kernel.txt b/docs/releases/api-surface/kernel.txt index f1ee1802c..15582aade 100644 --- a/docs/releases/api-surface/kernel.txt +++ b/docs/releases/api-surface/kernel.txt @@ -15,9 +15,12 @@ DISPUTE_ERRORS ERRORS ERROR_CATEGORIES ERROR_CODES +EXTENSION_BUDGET +EXTENSION_GROUPS ErrorCategory ErrorDefinition EvidencePillar +ExtensionGroupEntry HASH HEADERS ISSUER_CONFIG @@ -34,18 +37,24 @@ PAYMENT_RAILS PEAC_ALG PEAC_RECEIPT_HEADER PEAC_RECEIPT_URL_HEADER +PILLAR_VALUES POLICY POLICY_BLOCK PRIVATE_IP_RANGES +PROOF_TYPES PaymentRailEntry PeacEvidenceCarrier PolicyBlock +ProofTypeEntry RECEIPT +RECEIPT_TYPES REGISTRIES ReceiptRef +ReceiptTypeEntry RepresentationFields TRANSPORT_METHODS TYPE_GRAMMAR +TYPE_TO_EXTENSION_MAP TransportMethodEntry VARY_HEADERS VERIFICATION_MODES @@ -67,7 +76,10 @@ WireVersion applyPurposeVary findAgentProtocol findControlEngine +findExtensionGroup findPaymentRail +findProofType +findReceiptType findTransportMethod formatHash getError diff --git a/docs/releases/api-surface/protocol.txt b/docs/releases/api-surface/protocol.txt index 62b67c981..9903ae199 100644 --- a/docs/releases/api-surface/protocol.txt +++ b/docs/releases/api-surface/protocol.txt @@ -1,10 +1,26 @@ +JWK +JWKS +JWKSResolveError +JWKSResolveResult +JWKSResolveSuccess +ResolveJWKSOptions +RevokedKeyInfo base64urlDecode base64urlEncode +checkDocumentBinding checkPolicyBinding +clearJWKSCache +clearKidThumbprints +computeDocumentDigest +computeJsonDocumentDigestJcs computeJwkThumbprint computePolicyDigestJcs +computeTextDocumentDigestUtf8 generateKeypair +getJWKSCacheSize +getKidThumbprintSize jwkToPublicKeyBytes +resolveJWKS sha256Bytes sha256Hex verify diff --git a/docs/releases/api-surface/schema.txt b/docs/releases/api-surface/schema.txt index 594d91f14..9f72e3496 100644 --- a/docs/releases/api-surface/schema.txt +++ b/docs/releases/api-surface/schema.txt @@ -4,6 +4,7 @@ AGENT_IDENTITY_TYPE AIPREFSnapshotSchema ATTESTATION_LIMITS ATTESTATION_RECEIPT_TYPE +ATTRIBUTION_EXTENSION_KEY ATTRIBUTION_LIMITS ATTRIBUTION_TYPE ATTRIBUTION_USAGES @@ -19,6 +20,7 @@ AgentIdentityVerified AgentIdentityVerifiedSchema AgentProof AgentProofSchema +AmountMinorStringSchema AttestationExtensions AttestationExtensionsSchema AttestationReceiptClaims @@ -29,6 +31,8 @@ AttributionAttestation AttributionAttestationSchema AttributionEvidence AttributionEvidenceSchema +AttributionExtension +AttributionExtensionSchema AttributionSource AttributionSourceSchema AttributionUsage @@ -42,6 +46,11 @@ CHALLENGE_EXTENSION_KEY CHALLENGE_TYPES COMMERCE_EXTENSION_KEY COMMITMENT_CLASSES +COMPLIANCE_EXTENSION_KEY +COMPLIANCE_STATUSES +CONSENT_EXTENSION_KEY +CONSENT_STATUSES +CONTENT_SIGNAL_SOURCES CONTRIBUTION_TYPES CONTROL_ACTIONS CONTROL_ACTION_EXTENSION_KEY @@ -69,12 +78,22 @@ CommerceExtensionSchema CommitmentClass CommitmentClassSchema CompactJwsSchema +ComplianceExtension +ComplianceExtensionSchema +ComplianceStatus +ComplianceStatusSchema +ConsentExtension +ConsentExtensionSchema +ConsentStatus +ConsentStatusSchema ConstraintValidationResult ConstraintViolation ContactMethod ContactMethodSchema ContentHash ContentHashSchema +ContentSignalSource +ContentSignalSourceSchema ContextMetadata ContributionObligation ContributionObligationSchema @@ -110,6 +129,8 @@ CreditMethod CreditMethodSchema CreditObligation CreditObligationSchema +CustodyEntry +CustodyEntrySchema DERIVATION_TYPES DIGEST_SIZE_CONSTANTS DIGEST_VALUE_PATTERN @@ -151,6 +172,7 @@ DisputeType DisputeTypeSchema DocumentRef DocumentRefSchema +EXTENSION_BUDGET EXTENSION_KEY_PATTERN EXTENSION_LIMITS EnforcementContext @@ -166,6 +188,7 @@ HashAlgorithm HashAlgorithmSchema HashEncoding HashEncodingSchema +HttpsUriHintSchema IDENTITY_EXTENSION_KEY INTERACTION_EXTENSION_KEY INTERACTION_LIMITS @@ -176,6 +199,10 @@ IdentityExtensionSchema InteractionEvidenceV01 InteractionEvidenceV01Schema InteractionValidationResult +Iso8601DateSchema +Iso8601DateStringSchema +Iso8601DurationSchema +Iso8601OffsetDateTimeSchema JSON_EVIDENCE_LIMITS JWSHeader JsonArraySchema @@ -207,7 +234,10 @@ OrchestrationFrameworkSchema PEACEnvelope PEACParseError POLICY_DECISIONS +PRIVACY_EXTENSION_KEY PROOF_TYPES +PROVENANCE_EXTENSION_KEY +PURPOSE_EXTENSION_KEY ParseFailure ParseReceiptOptions ParseReceiptResult @@ -224,11 +254,18 @@ PolicyBlockSchema PolicyContext PolicyContextSchema PolicyDecision +PrivacyExtension +PrivacyExtensionSchema ProblemDetailsSchema ProofType ProofTypeSchema +ProvenanceExtension +ProvenanceExtensionSchema +PurposeExtension +PurposeExtensionSchema PurposeReasonSchema PurposeTokenSchema +RECIPIENT_SCOPES REDACTION_MODES REGISTERED_EXTENSION_GROUP_KEYS REGISTERED_RECEIPT_TYPES @@ -236,7 +273,10 @@ REMEDIATION_TYPES REPRESENTATION_LIMITS RESERVED_KIND_PREFIXES RESULT_STATUSES +RETENTION_MODES +REVIEW_STATUSES REVOCATION_REASONS +RISK_LEVELS ReceiptClaims ReceiptClaimsSchema ReceiptClaimsType @@ -246,6 +286,8 @@ ReceiptTypeSchema ReceiptUrlSchema ReceiptVariant ReceiptView +RecipientScope +RecipientScopeSchema Refs RefsSchema Remediation @@ -258,13 +300,28 @@ ResourceTargetSchema Result ResultSchema ResultStatus +RetentionMode +RetentionModeSchema +ReviewStatus +ReviewStatusSchema RevocationReason RevokedKeyEntryInput RevokedKeyEntryOutput RevokedKeyEntrySchema RevokedKeysArraySchema +Rfc3339DateTimeSchema +Rfc3339TimestampSchema +RiskLevel +RiskLevelSchema +SAFETY_EXTENSION_KEY STEP_ID_PATTERN +SafetyExtension +SafetyExtensionSchema +Sha256DigestSchema SimpleValidationResult +SlsaLevel +SlsaLevelSchema +SpdxExpressionSchema StepId StepIdSchema SubjectProfileSchema @@ -285,6 +342,8 @@ UNSAFE_JsonEvidenceLimits ValidationError ValidationWarning VerifyRequestSchema +WARNING_EXTENSION_GROUP_MISMATCH +WARNING_EXTENSION_GROUP_MISSING WARNING_OCCURRED_AT_SKEW WARNING_TYPE_UNREGISTERED WARNING_TYP_MISSING @@ -338,11 +397,18 @@ extractObligationsExtension findRevokedKey fingerprintRefToString getAccessExtension +getAttributionExtension getChallengeExtension getCommerceExtension +getComplianceExtension +getConsentExtension getCorrelationExtension getIdentityExtension getInteraction +getPrivacyExtension +getProvenanceExtension +getPurposeExtension +getSafetyExtension getValidTransitions hasInteraction hasValidDagSemantics @@ -367,6 +433,7 @@ isPaymentReceipt isReservedKindPrefix isTerminalState isTerminalWorkflowStatus +isValidAmountMinor isValidDisputeAttestation isValidExtensionKey isValidInteractionEvidence diff --git a/docs/releases/api-surface/sdk.txt b/docs/releases/api-surface/sdk.txt deleted file mode 100644 index 858ce3e59..000000000 --- a/docs/releases/api-surface/sdk.txt +++ /dev/null @@ -1,13 +0,0 @@ -ClientConfig -ClientError -DiscoverOptions -DiscoveryResult -PeacClient -PublicKeyMap -VerificationResult -VerifyLocalOptions -VerifyRemoteOptions -discover -verify -verifyLocal -verifyRemote diff --git a/scripts/release/api-surface-lock.sh b/scripts/release/api-surface-lock.sh index 4536cf78a..214febead 100755 --- a/scripts/release/api-surface-lock.sh +++ b/scripts/release/api-surface-lock.sh @@ -38,7 +38,8 @@ TRACKED_PACKAGES=( "@peac/middleware-core" "@peac/adapter-eat" "@peac/mcp-server" - "@peac/sdk" + # @peac/sdk archived (source in archive/sdk-js/); not a tracked public + # surface in v0.13.0+. Historical snapshot preserved in git history. ) FAILED=0 From c5f6eab5d9f7f08d7531cbe7acb6ce31271706c3 Mon Sep 17 00:00:00 2001 From: jithinraj <7850727+jithinraj@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:27:14 +0530 Subject: [PATCH 03/10] fix(mappings-a2a): remove deprecated A2A v0.3.0 compatibility A2A v0.3.0 compatibility was deprecated in v0.12.3 (upstream stabilization to A2A v1.0.0) with the removal target publicly stated as v0.13.0 in docs/STABILITY-CONTRACT.md. @peac/mappings-a2a now validates A2A v1.0.0 shapes only. Surfaces removed: - Agent Card top-level `url` field. The A2AAgentCard interface no longer declares `url`; cards without a valid `supportedInterfaces[0].url` are rejected by normalizeAgentCard (returns null) and by discoverAgentCard (skipped during validation). - `TASK_STATE_V03_TO_V1` map and the `normalizeTaskState` function that mapped kebab-case v0.3.0 states (`working`, `completed`, `input-required`, ...) to v1.0.0 prefixed SCREAMING_SNAKE_CASE (`TASK_STATE_WORKING`, `TASK_STATE_COMPLETED`, `TASK_STATE_INPUT_REQUIRED`, ...). Callers must supply v1.0.0 TaskState values directly. - `/.well-known/agent.json` legacy discovery fallback. Only the v1.0.0 canonical path `/.well-known/agent-card.json` is consulted. - `NormalizedAgentCard.version` discriminant (always v1.0.0 now; no longer useful). - Deprecation-warning plumbing (`warnV03Deprecated` internal state and `_resetDeprecationWarning` test hook). v0.3.0 inputs are rejected outright rather than normalized with a warning. Source + tests: - packages/mappings/a2a/src/types.ts: top-of-file docstring rewritten to v1.0.0 only with an explicit removal note; section heading "A2A Agent Card types (v0.3.0 + v1.0.0)" becomes "A2A Agent Card types (v1.0.0)"; `A2AAgentCard` loses its `url?` field and gains a doc note that `supportedInterfaces[0].url` is required; `TASK_STATE_V03_TO_V1` map deleted with an inline removal note. `A2A_MAX_CARRIER_SIZE = 65_536`, `A2A_V1_TASK_STATE`, `A2A_V1_MESSAGE_ROLE`, `A2APeacPayload`, and the carrier-payload and message-shape types are preserved. - packages/mappings/a2a/src/normalizers.ts: full rewrite. Keeps `isV1AgentCard`, `normalizeAgentCard`, `selectBestInterface`, and the `NormalizedAgentCard` type (minus the `version` field). normalizeAgentCard now returns null for any non-v1.0.0 shape instead of synthesizing a v0.3.0 fallback. - packages/mappings/a2a/src/discovery.ts: fetch path array reduced to `['/.well-known/agent-card.json']`; inline comment updated to "v1.0.0 validation; v0.3.0 rejected". - packages/mappings/a2a/src/attach.ts: docstring reframed to name the behavior as the A2A extension-URI metadata convention (inherited from v0.3.0 and carried forward in v1.0.0). Behavior is unchanged; only the citation text is updated. - packages/mappings/a2a/src/index.ts: barrel drops `TASK_STATE_V03_TO_V1`, `normalizeTaskState`, and `_resetDeprecationWarning` exports. Top-of-file docstring rewritten to v1.0.0 only. - packages/mappings/a2a/tests/normalizers.test.ts: rewritten. isV1AgentCard tests now cover the v1.0.0 positive path and three negative shapes (empty supportedInterfaces, missing supportedInterfaces, empty URL). normalizeAgentCard tests cover v1.0.0 positive cases, explicit v0.3.0 rejection, hybrid card precedence (v1.0.0 over legacy url), multiple-interface handling, and capability preservation. selectBestInterface tests unchanged. The deprecated normalizeTaskState describe block is gone; its coverage moved to explicit rejection assertions in normalizeAgentCard and to the source-level grep gate. - packages/mappings/a2a/tests/discovery.test.ts: VALID_AGENT_CARD, AGENT_CARD_NO_PEAC, and AGENT_CARD_WITH_PARAMS fixtures rewritten to the v1.0.0 supportedInterfaces shape. The "Falls back to /.well-known/agent.json" positive test is flipped to a "does NOT fall back" negative assertion proving the v0.3.0 legacy path is no longer consulted. Active-doc teaching updates: - docs/MIGRATION_CURRENT.md: new A2A v0.3.0 removal section. - docs/STABILITY-CONTRACT.md: A2A row flipped to Removed with stability and removed-in context. - docs/REFERENCE_ARCHITECTURES.md, docs/specs/A2A-RECEIPT-PROFILE.md, docs/specs/DISCOVERY-PROFILE.md, docs/specs/EVIDENCE-CARRIER-CONTRACT.md, docs/specs/INTEROP.md, integrator-kits/a2a/README.md, packages/mappings/a2a/README.md, AGENTS.md: tightened to v1.0.0-only references. No wire format change. No kernel / crypto / protocol semantic change. --- AGENTS.md | 12 +- docs/MIGRATION_CURRENT.md | 31 +++++ docs/REFERENCE_ARCHITECTURES.md | 2 +- docs/STABILITY-CONTRACT.md | 16 +-- docs/specs/A2A-RECEIPT-PROFILE.md | 6 +- docs/specs/DISCOVERY-PROFILE.md | 11 +- docs/specs/EVIDENCE-CARRIER-CONTRACT.md | 2 +- docs/specs/INTEROP.md | 2 +- integrator-kits/a2a/README.md | 2 +- packages/mappings/a2a/README.md | 25 +--- packages/mappings/a2a/src/attach.ts | 7 +- packages/mappings/a2a/src/discovery.ts | 14 +- packages/mappings/a2a/src/index.ts | 14 +- packages/mappings/a2a/src/normalizers.ts | 120 +++++------------- packages/mappings/a2a/src/types.ts | 37 +++--- packages/mappings/a2a/tests/discovery.test.ts | 33 ++++- .../mappings/a2a/tests/normalizers.test.ts | 87 +++++-------- 17 files changed, 192 insertions(+), 229 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 604d1bb89..7a21c1f4e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -58,12 +58,18 @@ The `PEAC-Receipt` header carries a compact JWS (never a bare `receipt_ref`). For A2A (Agent-to-Agent Protocol, Linux Foundation) discovery via `/.well-known/agent-card.json`: -**Agent Card (`capabilities.extensions[]` array, A2A v0.3.0 and v1.0.0):** +**Agent Card (`capabilities.extensions[]` array, A2A v1.0.0):** ```json { "name": "Example Agent", - "url": "https://agent.example", + "supportedInterfaces": [ + { + "url": "https://agent.example", + "protocolBinding": "http+json", + "protocolVersion": "1.0.0" + } + ], "capabilities": { "extensions": [ { @@ -93,7 +99,7 @@ For A2A (Agent-to-Agent Protocol, Linux Foundation) discovery via `/.well-known/ } ``` -The extension URI key maps to a nested object containing the carrier array. This follows the A2A metadata convention. Both v0.3.0 and v1.0.0 Agent Card shapes are accepted. +The extension URI key maps to a nested object containing the carrier array. This follows the A2A metadata convention. A2A v1.0.0 Agent Card shape is required; v0.3.0 compatibility was removed in v0.13.0 (DD-186). Cards without a valid `supportedInterfaces[0].url` are rejected. ## Discovery diff --git a/docs/MIGRATION_CURRENT.md b/docs/MIGRATION_CURRENT.md index 2a8d980a1..648e9f33c 100644 --- a/docs/MIGRATION_CURRENT.md +++ b/docs/MIGRATION_CURRENT.md @@ -2,6 +2,37 @@ This guide covers migration paths for current PEAC Protocol surfaces. +## A2A v0.3.0 compatibility removal (v0.13.0) + +A2A v0.3.0 compatibility was deprecated in v0.12.3 (DD-186) after the A2A v1.0.0 upstream stabilization and **removed in v0.13.0 PR B**. `@peac/mappings-a2a` now validates A2A v1.0.0 shapes only. + +Surfaces removed: + +- **Agent Card top-level `url`.** v0.3.0 cards carried the endpoint URL as `AgentCard.url`. v1.0.0 replaced this with `supportedInterfaces[]`. Cards without a valid `supportedInterfaces[0].url` are no longer accepted; `normalizeAgentCard(card)` returns `null` for them and `discoverAgentCard(...)` skips them. +- **Kebab-case TaskState strings.** v0.3.0 used `"working"`, `"completed"`, `"input-required"`, etc. v1.0.0 uses SCREAMING_SNAKE_CASE with a type prefix (`"TASK_STATE_WORKING"`, `"TASK_STATE_COMPLETED"`, `"TASK_STATE_INPUT_REQUIRED"`). The `normalizeTaskState` function and the `TASK_STATE_V03_TO_V1` map are removed. Callers MUST supply v1.0.0 TaskState values directly. +- **`/.well-known/agent.json` legacy discovery path.** `discoverAgentCard(baseUrl)` now fetches only the v1.0.0 canonical path `/.well-known/agent-card.json`. Deployers still serving the legacy path should publish the canonical path or upgrade to an A2A v1.0.0 implementation. +- **Deprecation-warning plumbing.** `_resetDeprecationWarning` is gone; no v0.3.0 `DeprecationWarning` is emitted because v0.3.0 inputs are now rejected outright rather than normalized with a warning. + +**Migration:** + +```ts +// Before (v0.3.0 shape; no longer accepted at v0.13.0) +const card = { name: 'agent', url: 'https://agent.example' }; + +// After (v1.0.0 shape) +const card = { + name: 'agent', + supportedInterfaces: [ + { url: 'https://agent.example', protocolBinding: 'http+json', protocolVersion: '1.0.0' }, + ], +}; + +// TaskState values must already be v1.0.0 +const state = 'TASK_STATE_WORKING'; // previously 'working' +``` + +`A2A_MAX_CARRIER_SIZE` (`65_536` bytes) is unchanged. `PEAC_EXTENSION_URI`, the `capabilities.extensions[]` registration pattern, and the `metadata[extensionURI].carriers[]` placement convention are all unchanged. The v0.3.0 removal is scoped strictly to the dual-version acceptance surface; v1.0.0 behavior is byte-stable. See `docs/specs/A2A-RECEIPT-PROFILE.md` for the normative v1.0.0 profile. + ## ProofMethodSchema removal (v0.13.0) `ProofMethodSchema`, `PROOF_METHODS`, and the `ProofMethod` type were deprecated in v0.12.2 (DD-185) and **removed in v0.13.0 PR B**. The deprecated standalone schema export retired because transport-binding methods are semantically distinct from trust-root proof models; the two concerns should not share a public surface. diff --git a/docs/REFERENCE_ARCHITECTURES.md b/docs/REFERENCE_ARCHITECTURES.md index dba15a27c..bf58f52c0 100644 --- a/docs/REFERENCE_ARCHITECTURES.md +++ b/docs/REFERENCE_ARCHITECTURES.md @@ -122,7 +122,7 @@ Agent A (Requester) A2A Gateway Agent B (Provider) - Receipt is carried in A2A task `metadata[extensionURI].carriers[]` (max 64 KB) - Agent B is the issuer; Agent A is the verifier - The A2A gateway routes but does not modify the evidence payload -- Supports A2A v1.0 (Linux Foundation); v0.3.0 backward compatibility via normalizer +- Supports A2A v1.0 (Linux Foundation); v0.3.0 compatibility was deprecated in v0.12.3 and removed in v0.13.0 (DD-186) - OAuth PKCE (S256) for agent authentication; Device Code flow for non-browser agents ### Packages involved diff --git a/docs/STABILITY-CONTRACT.md b/docs/STABILITY-CONTRACT.md index f39d1793b..f1db2266d 100644 --- a/docs/STABILITY-CONTRACT.md +++ b/docs/STABILITY-CONTRACT.md @@ -156,14 +156,14 @@ Path: [`surfaces/plugin-pack/`](../surfaces/plugin-pack/). ## Deprecation schedule -| Surface | Deprecated since | Removal target | Status | -| --------------------------------------------------------- | ---------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ProofMethodSchema` (compat alias) | v0.12.2 | v0.13.0 | **Removed in v0.13.0 PR B.** Transport-binding values (`http-message-signature`, `dpop`, `mtls`, `jwk-thumbprint`) inlined on `AgentProofSchema.method`. | -| A2A v0.3.0 compatibility path | v0.12.3 | v0.13.0 PR B | Scheduled | -| Legacy `POST /verify` endpoint (in favor of `/v1/verify`) | v0.12.x | post-Sunset (2026-11-01) | v0.13.0 PR B removes from active OpenAPI teaching; runtime alias preserved until advertised Sunset date | -| `packages/sdk-js/` workspace stub | v0.12.x | v0.13.0 PR B | Scheduled | -| `peac.receipt/0.9` archival format | Legacy frozen | v0.13.0 (quarantine) | Quarantined to historical contexts; wire stays frozen | -| `@peac/core` archival verify-only path | Legacy frozen | v0.13.0 PR B | Scheduled (coupled with legacy `/verify` handler rewire) | +| Surface | Deprecated since | Removal target | Status | +| --------------------------------------------------------- | ---------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ProofMethodSchema` (compat alias) | v0.12.2 | v0.13.0 | **Removed in v0.13.0 PR B.** Transport-binding values (`http-message-signature`, `dpop`, `mtls`, `jwk-thumbprint`) inlined on `AgentProofSchema.method`. | +| A2A v0.3.0 compatibility path | v0.12.3 | v0.13.0 PR B | **Removed in v0.13.0 PR B.** Agent Cards carrying only a top-level `url`, kebab-case TaskState strings, and the `/.well-known/agent.json` legacy discovery path are no longer accepted. A2A v1.0.0 `supportedInterfaces[]` is required. | +| Legacy `POST /verify` endpoint (in favor of `/v1/verify`) | v0.12.x | post-Sunset (2026-11-01) | v0.13.0 PR B removes from active OpenAPI teaching; runtime alias preserved until advertised Sunset date | +| `packages/sdk-js/` workspace stub | v0.12.x | v0.13.0 PR B | Scheduled | +| `peac.receipt/0.9` archival format | Legacy frozen | v0.13.0 (quarantine) | Quarantined to historical contexts; wire stays frozen | +| `@peac/core` archival verify-only path | Legacy frozen | v0.13.0 PR B | Scheduled (coupled with legacy `/verify` handler rewire) | All status transitions are tracked in [`REPO_SURFACE_STATUS.json`](../REPO_SURFACE_STATUS.json) and mirrored in diff --git a/docs/specs/A2A-RECEIPT-PROFILE.md b/docs/specs/A2A-RECEIPT-PROFILE.md index ec2c8b447..14c5296ff 100644 --- a/docs/specs/A2A-RECEIPT-PROFILE.md +++ b/docs/specs/A2A-RECEIPT-PROFILE.md @@ -3,11 +3,11 @@ **Version:** 0.2 **Status:** Normative **Package:** `@peac/mappings-a2a` -**A2A Spec Version:** v1.0.0 (stable); v0.3.0 (deprecated, supported via normalizer) +**A2A Spec Version:** v1.0.0 (stable). v0.3.0 compatibility was deprecated in v0.12.3 and removed in v0.13.0 (DD-186). **Extension URI:** `https://www.peacprotocol.org/ext/traceability/v1` -**Depends on:** Evidence Carrier Contract (DD-124), A2A v1.0 transition normalizer (DD-186) +**Depends on:** Evidence Carrier Contract (DD-124) -This document specifies how PEAC evidence carriers are placed within A2A (Agent-to-Agent Protocol) messages and metadata. It covers Agent Card declaration, metadata layout, header conventions, and security considerations. Both A2A v1.0.0 and v0.3.0 (deprecated) wire formats are supported via the dual-version normalizer (DD-186). +This document specifies how PEAC evidence carriers are placed within A2A (Agent-to-Agent Protocol) messages and metadata. It covers Agent Card declaration, metadata layout, header conventions, and security considerations. A2A v1.0.0 is the only accepted wire format; the v0.3.0 compatibility path (top-level `url`, kebab-case TaskState normalization, `/.well-known/agent.json` legacy discovery) was removed in v0.13.0. The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (RFC 2119, RFC 8174) when, and only when, they appear in all capitals, as shown here. diff --git a/docs/specs/DISCOVERY-PROFILE.md b/docs/specs/DISCOVERY-PROFILE.md index 1a41a724c..8f69f1068 100644 --- a/docs/specs/DISCOVERY-PROFILE.md +++ b/docs/specs/DISCOVERY-PROFILE.md @@ -21,11 +21,12 @@ The algorithm tries three steps in sequence, returning the first successful resu Check if the endpoint publishes an A2A Agent Card with a PEAC extension declared. -1. Fetch `{baseUrl}/.well-known/agent-card.json` (A2A v0.3.0 canonical path) -2. If 404, try `{baseUrl}/.well-known/agent.json` (legacy fallback) -3. Parse the Agent Card JSON -4. Look for PEAC extension in `capabilities.extensions[]` with URI `https://www.peacprotocol.org/ext/traceability/v1` -5. If found, return capabilities from the extension's `params` field +1. Fetch `{baseUrl}/.well-known/agent-card.json` (A2A v1.0.0 canonical path) +2. Parse the Agent Card JSON +3. Look for PEAC extension in `capabilities.extensions[]` with URI `https://www.peacprotocol.org/ext/traceability/v1` +4. If found, return capabilities from the extension's `params` field + +The v0.3.0 legacy fallback `{baseUrl}/.well-known/agent.json` was removed in v0.13.0 alongside the rest of the A2A v0.3.0 compatibility surface (DD-186); deployers still serving only the legacy path should publish the canonical path. **Result source:** `agent_card` diff --git a/docs/specs/EVIDENCE-CARRIER-CONTRACT.md b/docs/specs/EVIDENCE-CARRIER-CONTRACT.md index f52290d16..cb8d33bfd 100644 --- a/docs/specs/EVIDENCE-CARRIER-CONTRACT.md +++ b/docs/specs/EVIDENCE-CARRIER-CONTRACT.md @@ -290,7 +290,7 @@ Carriers are placed in the `metadata` map of A2A messages using the PEAC extensi } ``` -The extension URI (`https://www.peacprotocol.org/ext/traceability/v1`) is registered in the A2A Agent Card's `capabilities.extensions[]` array per A2A spec v0.3.0. +The extension URI (`https://www.peacprotocol.org/ext/traceability/v1`) is registered in the A2A Agent Card's `capabilities.extensions[]` array per the A2A v1.0.0 extension registration convention (originally defined in v0.3.0 and preserved on the v1.0.0 wire). ### 7.3 ACP (Agentic Commerce Protocol) diff --git a/docs/specs/INTEROP.md b/docs/specs/INTEROP.md index da882e585..9f7eaeb69 100644 --- a/docs/specs/INTEROP.md +++ b/docs/specs/INTEROP.md @@ -148,7 +148,7 @@ const result = rslToControlPurposes(['ai-all', 'search']); ### 5.3 Package -`@peac/mappings-a2a` provides A2A v1.0.0 artifact embedding, Agent Card discovery, and carrier extraction. A2A v0.3.0 compat shim retained through v0.12.x; removal in v0.13.0. +`@peac/mappings-a2a` provides A2A v1.0.0 artifact embedding, Agent Card discovery, and carrier extraction. The A2A v0.3.0 compatibility shim was deprecated in v0.12.3 and removed in v0.13.0 (DD-186). --- diff --git a/integrator-kits/a2a/README.md b/integrator-kits/a2a/README.md index ab6b853b5..97c7c1055 100644 --- a/integrator-kits/a2a/README.md +++ b/integrator-kits/a2a/README.md @@ -6,7 +6,7 @@ Carry signed receipts across A2A agent flows: declare PEAC support in your Agent PEAC integrates with A2A at the metadata layer. Receipts travel as Evidence Carriers inside A2A TaskStatus metadata, using the reverse-DNS extension URI `org.peacprotocol`. No A2A protocol changes are required; PEAC uses the standard metadata extension mechanism. -**Compatibility:** `@peac/mappings-a2a` targets A2A v1.0.0 (shipped in v0.12.3). A2A v0.3.0 compat shim is retained through v0.12.x; removal scheduled for v0.13.0. +**Compatibility:** `@peac/mappings-a2a` targets A2A v1.0.0 only (shipped in v0.12.3; deprecated v0.3.0 compat shim removed in v0.13.0 per DD-186). ## Prerequisites diff --git a/packages/mappings/a2a/README.md b/packages/mappings/a2a/README.md index 98c24c3af..558d9ce28 100644 --- a/packages/mappings/a2a/README.md +++ b/packages/mappings/a2a/README.md @@ -10,13 +10,13 @@ pnpm add @peac/mappings-a2a ## What It Does -`@peac/mappings-a2a` bridges PEAC signed interaction receipts and the Agent-to-Agent Protocol (A2A). It normalizes A2A v0.3.0 and v1.0.0 structures into a consistent shape, attaches and extracts PEAC evidence carriers from A2A metadata, and discovers PEAC capabilities advertised by remote agents. All carrier operations enforce transport constraints (64 KB embed limit for A2A). +`@peac/mappings-a2a` bridges PEAC signed interaction receipts and the Agent-to-Agent Protocol (A2A). It validates A2A v1.0.0 Agent Card structures, attaches and extracts PEAC evidence carriers from A2A metadata, and discovers PEAC capabilities advertised by remote agents. All carrier operations enforce transport constraints (64 KB embed limit for A2A). -## How Do I Use It? +A2A v0.3.0 compatibility (dual-version Agent Cards, top-level `url`, kebab-case TaskState normalization, `/.well-known/agent.json` legacy discovery path) was deprecated in v0.12.3 and removed in v0.13.0 (DD-186). This package accepts A2A v1.0.0 shapes only; see [`docs/MIGRATION_CURRENT.md`](../../../docs/MIGRATION_CURRENT.md) for the migration guide. -### Normalize an A2A Agent Card +## How Do I Use It? -Accept either v0.3.0 or v1.0.0 Agent Cards and get a consistent interface: +### Validate an A2A v1.0.0 Agent Card ```typescript import { normalizeAgentCard } from '@peac/mappings-a2a'; @@ -30,21 +30,10 @@ const card = { const normalized = normalizeAgentCard(card); if (normalized) { - console.log(normalized.version); // '1.0.0' console.log(normalized.url); // 'https://billing.example.com' } -``` - -### Normalize a task state across versions - -```typescript -import { normalizeTaskState } from '@peac/mappings-a2a'; - -// v0.3.0 value normalized to v1.0.0 canonical form -console.log(normalizeTaskState('working')); // 'TASK_STATE_WORKING' - -// v1.0.0 values pass through unchanged -console.log(normalizeTaskState('TASK_STATE_COMPLETED')); // 'TASK_STATE_COMPLETED' +// Cards without a valid supportedInterfaces[0].url (including legacy +// v0.3.0 cards that used top-level `url`) return null. ``` ### Attach a receipt to A2A TaskStatus metadata @@ -114,7 +103,7 @@ if (result) { If you are building an AI agent that communicates via A2A and needs signed interaction receipts: -- Use `normalizeAgentCard()` and `normalizeTaskState()` to handle both A2A v0.3.0 and v1.0.0 inputs +- Use `normalizeAgentCard()` to validate incoming A2A v1.0.0 Agent Cards (cards without a valid `supportedInterfaces[0].url` return `null`) - Use `attachReceiptToTaskStatus()` to embed evidence in outgoing A2A messages - Use `extractReceiptFromMetadata()` to retrieve evidence from incoming A2A messages - Use `A2ACarrierAdapter` for a transport-agnostic carrier interface diff --git a/packages/mappings/a2a/src/attach.ts b/packages/mappings/a2a/src/attach.ts index a565d4d03..7a7cc8823 100644 --- a/packages/mappings/a2a/src/attach.ts +++ b/packages/mappings/a2a/src/attach.ts @@ -1,9 +1,10 @@ /** * Attach PEAC evidence carriers to A2A metadata. * - * Places carrier data under metadata[PEAC_EXTENSION_URI] per A2A v0.3.0 - * metadata convention. Uses computeReceiptRef() from @peac/schema - * for receipt reference computation (correction item 4). + * Places carrier data under metadata[PEAC_EXTENSION_URI] per the A2A + * extension-URI metadata convention (inherited from v0.3.0 and carried + * forward in v1.0.0). Uses computeReceiptRef() from @peac/schema for + * receipt reference computation. */ import type { PeacEvidenceCarrier, CarrierMeta } from '@peac/kernel'; diff --git a/packages/mappings/a2a/src/discovery.ts b/packages/mappings/a2a/src/discovery.ts index 1f48a2ab4..68f01beb4 100644 --- a/packages/mappings/a2a/src/discovery.ts +++ b/packages/mappings/a2a/src/discovery.ts @@ -129,8 +129,11 @@ async function validateUrlForDiscovery(url: string, options: DiscoveryOptions): /** * Discover A2A Agent Card from a base URL. * - * Tries `/.well-known/agent-card.json` first, then `/.well-known/agent.json` - * as a legacy fallback per A2A v0.3.0. + * Fetches the A2A v1.0.0 canonical path `/.well-known/agent-card.json`. + * The v0.3.0 legacy fallback `/.well-known/agent.json` was removed in + * v0.13.0 (DD-186) alongside the rest of the v0.3.0 compatibility + * surface; deployers still serving the legacy path should publish the + * canonical path or upgrade to an A2A v1.0.0 implementation. * * SSRF protection per Polish C: * 1. Private IP blocking (literal check; DNS resolution when resolveHostname provided) @@ -146,7 +149,7 @@ export async function discoverAgentCard( options: DiscoveryOptions = {} ): Promise { const fetchFn = options.fetch ?? globalThis.fetch; - const paths = ['/.well-known/agent-card.json', '/.well-known/agent.json']; + const paths = ['/.well-known/agent-card.json']; for (const path of paths) { const url = new URL(path, baseUrl).toString(); @@ -186,7 +189,10 @@ export async function discoverAgentCard( const card = JSON.parse(text) as A2AAgentCard; - // Dual-version validation (DD-186): accept v0.3.0 (url) or v1.0.0 (supportedInterfaces) + // v1.0.0 validation (DD-186; v0.3.0 rejected). Cards MUST expose a + // valid `supportedInterfaces[0].url`. Cards carrying only a legacy + // top-level `url` (the v0.3.0 shape) return null from + // normalizeAgentCard and are skipped here. if (typeof card.name !== 'string') { continue; } diff --git a/packages/mappings/a2a/src/index.ts b/packages/mappings/a2a/src/index.ts index b06a32a4f..3b8014cf0 100644 --- a/packages/mappings/a2a/src/index.ts +++ b/packages/mappings/a2a/src/index.ts @@ -3,7 +3,8 @@ * * Agent-to-Agent Protocol (A2A) integration for PEAC. * Maps PEAC evidence carriers to A2A metadata (TaskStatus, Message, Artifact). - * Supports both A2A v0.3.0 and v1.0.0 (DD-186 dual-version transition). + * A2A v1.0.0 only. The v0.3.0 compatibility path (DD-186) was deprecated + * in v0.12.3 and removed in v0.13.0; see `docs/MIGRATION_CURRENT.md`. */ // Types @@ -24,19 +25,12 @@ export { A2A_MAX_CARRIER_SIZE, A2A_V1_TASK_STATE, A2A_V1_MESSAGE_ROLE, - TASK_STATE_V03_TO_V1, } from './types'; -// Normalizers (DD-186: v0.3.0 + v1.0.0 dual-version) +// Normalizers (A2A v1.0.0 only) export type { NormalizedAgentCard } from './normalizers'; -export { - isV1AgentCard, - normalizeAgentCard, - selectBestInterface, - normalizeTaskState, - _resetDeprecationWarning, -} from './normalizers'; +export { isV1AgentCard, normalizeAgentCard, selectBestInterface } from './normalizers'; // Attach export { diff --git a/packages/mappings/a2a/src/normalizers.ts b/packages/mappings/a2a/src/normalizers.ts index 332a7d56a..3d2b45112 100644 --- a/packages/mappings/a2a/src/normalizers.ts +++ b/packages/mappings/a2a/src/normalizers.ts @@ -1,100 +1,64 @@ /** - * A2A v1.0.0 transition normalizers (DD-186). + * A2A v1.0.0 Agent Card normalizers. * - * Accept both v0.3.0 and v1.0.0 Agent Card, TaskState, and Part shapes. - * v0.3.0 inputs emit a deprecation warning. v0.3.0 removal at v0.13.0. + * The v0.3.0 compatibility path (DD-186; `url` top-level field plus + * kebab-case TaskState mapping) was deprecated in v0.12.3 and removed + * in v0.13.0. Every accepted Agent Card must expose a non-empty + * `supportedInterfaces[0].url`; cards without it are rejected. The + * v0.3.0-to-v1.0.0 TaskState mapping function was removed because its + * only job was translating kebab-case inputs, which are no longer + * accepted. TaskState values are now v1.0.0 SCREAMING_SNAKE_CASE + * strings carried by the caller. */ import type { A2AAgentCard, A2ASupportedInterface } from './types'; -import { TASK_STATE_V03_TO_V1 } from './types'; - -// --------------------------------------------------------------------------- -// Deprecation warning (fires once per process) -// --------------------------------------------------------------------------- - -let v03DeprecationWarned = false; - -function warnV03Deprecated(context: string): void { - if (!v03DeprecationWarned) { - v03DeprecationWarned = true; - process.emitWarning( - `A2A v0.3.0 ${context} detected. Migrate to v1.0.0. ` + - 'v0.3.0 support will be removed in @peac/mappings-a2a v0.13.0.', - 'DeprecationWarning' - ); - } -} - -/** Reset deprecation warning state (for testing only) */ -export function _resetDeprecationWarning(): void { - v03DeprecationWarned = false; -} // --------------------------------------------------------------------------- // Agent Card normalizer // --------------------------------------------------------------------------- -/** Normalized Agent Card with a resolved URL regardless of version */ +/** Normalized Agent Card with a resolved v1.0.0 URL. */ export interface NormalizedAgentCard { name: string; url: string; - version: '0.3.0' | '1.0.0'; supportedInterfaces: A2ASupportedInterface[]; original: A2AAgentCard; } /** - * Detect whether an Agent Card uses v1.0.0 structure. - * - * v1.0.0 cards have `supportedInterfaces[]` instead of top-level `url`. + * Returns true if the card satisfies the v1.0.0 Agent Card contract — + * that is, `supportedInterfaces[0].url` exists and is a non-empty + * string. Cards that fail this check are rejected by + * `normalizeAgentCard` (they are not v0.3.0 fallbacks, which are no + * longer accepted). */ export function isV1AgentCard(card: A2AAgentCard): boolean { return ( Array.isArray(card.supportedInterfaces) && card.supportedInterfaces.length > 0 && - typeof card.supportedInterfaces[0]?.url === 'string' + typeof card.supportedInterfaces[0]?.url === 'string' && + card.supportedInterfaces[0].url.length > 0 ); } /** - * Normalize an A2A Agent Card from either v0.3.0 or v1.0.0 format. + * Normalize an A2A v1.0.0 Agent Card. * - * Returns a consistent shape with a resolved URL. v0.3.0 cards emit - * a deprecation warning on first encounter. - * - * Returns null if the card has neither a valid `url` nor valid - * `supportedInterfaces[0].url`. + * Returns a consistent shape with the resolved primary URL. Cards that + * do not satisfy `isV1AgentCard(...)` return `null` — including the + * legacy v0.3.0 shape with only a top-level `url`, which is no longer + * supported. Callers receiving `null` should treat the card as invalid + * and surface a structured error rather than falling back to v0.3.0. */ export function normalizeAgentCard(card: A2AAgentCard): NormalizedAgentCard | null { - if (isV1AgentCard(card)) { - const iface = card.supportedInterfaces![0]!; - return { - name: card.name, - url: iface.url, - version: '1.0.0', - supportedInterfaces: card.supportedInterfaces!, - original: card, - }; - } - - if (typeof card.url === 'string') { - warnV03Deprecated('Agent Card (top-level url)'); - return { - name: card.name, - url: card.url, - version: '0.3.0', - supportedInterfaces: [ - { - url: card.url, - protocolBinding: 'http+json', - protocolVersion: '0.3.0', - }, - ], - original: card, - }; - } - - return null; + if (!isV1AgentCard(card)) return null; + const iface = card.supportedInterfaces![0]!; + return { + name: card.name, + url: iface.url, + supportedInterfaces: card.supportedInterfaces!, + original: card, + }; } /** @@ -111,25 +75,3 @@ export function selectBestInterface( b.protocolVersion.localeCompare(a.protocolVersion, undefined, { numeric: true }) )[0]!; } - -// --------------------------------------------------------------------------- -// TaskState normalizer -// --------------------------------------------------------------------------- - -/** - * Normalize a task state string from v0.3.0 or v1.0.0 format. - * - * v0.3.0 uses kebab-case (e.g., "working"), v1.0.0 uses prefixed - * SCREAMING_SNAKE_CASE (e.g., "TASK_STATE_WORKING"). - * - * Returns the v1.0.0 canonical form. Unrecognized values pass through - * unchanged. - */ -export function normalizeTaskState(state: string): string { - const v1 = TASK_STATE_V03_TO_V1[state]; - if (v1) { - warnV03Deprecated('TaskState value'); - return v1; - } - return state; -} diff --git a/packages/mappings/a2a/src/types.ts b/packages/mappings/a2a/src/types.ts index c8cd051e3..cf01f12ce 100644 --- a/packages/mappings/a2a/src/types.ts +++ b/packages/mappings/a2a/src/types.ts @@ -1,12 +1,16 @@ /** - * A2A types supporting both v0.3.0 and v1.0.0. + * A2A v1.0.0 types. * * These types are defined locally rather than importing @a2a-js/sdk, * which brings protobuf + gRPC + express peer dependencies that are * not needed for evidence placement. * - * v1.0.0 transition (DD-186): dual-version acceptance with v0.3.0 - * deprecated via process.emitWarning(). v0.3.0 removal at v0.13.0. + * A2A v0.3.0 compatibility (DD-186) was deprecated in v0.12.3 and + * removed in v0.13.0 (DD-186 removal target honored). Agent Cards + * carrying only a top-level `url` (the v0.3.0 shape) are no longer + * accepted; every card must expose `supportedInterfaces[0].url`. + * TaskState values must use the v1.0.0 prefixed SCREAMING_SNAKE_CASE + * form; v0.3.0 kebab-case values are no longer mapped. */ import type { CarrierFormat, PeacEvidenceCarrier, CarrierMeta } from '@peac/kernel'; @@ -22,7 +26,7 @@ export const PEAC_EXTENSION_URI = 'https://www.peacprotocol.org/ext/traceability export const A2A_MAX_CARRIER_SIZE = 65_536; // --------------------------------------------------------------------------- -// A2A Agent Card types (v0.3.0 + v1.0.0) +// A2A Agent Card types (v1.0.0) // --------------------------------------------------------------------------- /** A2A extension entry in capabilities.extensions[] */ @@ -53,16 +57,15 @@ export interface A2ASupportedInterface { } /** - * Minimal A2A Agent Card shape (v0.3.0 + v1.0.0). + * Minimal A2A Agent Card shape (v1.0.0). * - * v0.3.0 cards have top-level `url`. v1.0.0 cards replace it with - * `supportedInterfaces[]`. Both shapes are accepted; v0.3.0 emits - * a deprecation warning. v0.3.0 support removal at v0.13.0. + * v1.0.0 cards expose endpoint bindings via `supportedInterfaces[]`. + * Every accepted card MUST carry a non-empty `supportedInterfaces[0].url`. + * The legacy v0.3.0 top-level `url` field was accepted through v0.12.x + * with a deprecation warning and removed in v0.13.0 (DD-186). */ export interface A2AAgentCard { name: string; - /** @deprecated v0.3.0 field. Use supportedInterfaces[0].url in v1.0.0. */ - url?: string; supportedInterfaces?: A2ASupportedInterface[]; capabilities?: { extensions?: AgentCardExtension[]; @@ -124,17 +127,9 @@ export const A2A_V1_MESSAGE_ROLE = { AGENT: 'ROLE_AGENT', } as const; -/** Map from v0.3.0 kebab-case task states to v1.0.0 prefixed form */ -export const TASK_STATE_V03_TO_V1: Record = { - submitted: A2A_V1_TASK_STATE.SUBMITTED, - working: A2A_V1_TASK_STATE.WORKING, - completed: A2A_V1_TASK_STATE.COMPLETED, - failed: A2A_V1_TASK_STATE.FAILED, - canceled: A2A_V1_TASK_STATE.CANCELED, - rejected: A2A_V1_TASK_STATE.REJECTED, - 'input-required': A2A_V1_TASK_STATE.INPUT_REQUIRED, - 'auth-required': A2A_V1_TASK_STATE.AUTH_REQUIRED, -}; +// The v0.3.0 → v1.0.0 TaskState mapping (previously TASK_STATE_V03_TO_V1) +// was removed in v0.13.0 alongside the v0.3.0 compat path (DD-186). +// Callers MUST supply v1.0.0 TaskState values directly. // --------------------------------------------------------------------------- // Carrier payload type diff --git a/packages/mappings/a2a/tests/discovery.test.ts b/packages/mappings/a2a/tests/discovery.test.ts index d5741840e..09b0853ed 100644 --- a/packages/mappings/a2a/tests/discovery.test.ts +++ b/packages/mappings/a2a/tests/discovery.test.ts @@ -40,7 +40,13 @@ function createMockFetch( const VALID_AGENT_CARD: A2AAgentCard = { name: 'Test Agent', - url: 'https://agent.example.com', + supportedInterfaces: [ + { + url: 'https://agent.example.com', + protocolBinding: 'http+json', + protocolVersion: '1.0.0', + }, + ], capabilities: { extensions: [ { @@ -54,7 +60,13 @@ const VALID_AGENT_CARD: A2AAgentCard = { const AGENT_CARD_NO_PEAC: A2AAgentCard = { name: 'Other Agent', - url: 'https://other.example.com', + supportedInterfaces: [ + { + url: 'https://other.example.com', + protocolBinding: 'http+json', + protocolVersion: '1.0.0', + }, + ], capabilities: { extensions: [ { @@ -87,7 +99,11 @@ describe('discoverAgentCard', () => { expect(card!.name).toBe('Test Agent'); }); - it('falls back to /.well-known/agent.json', async () => { + it('does NOT fall back to the legacy v0.3.0 /.well-known/agent.json path', async () => { + // v0.3.0 discovery fallback was removed in v0.13.0 (DD-186). A deployer + // serving the card only at /.well-known/agent.json (the v0.3.0 path) is + // no longer discoverable; the canonical v1.0.0 path + // /.well-known/agent-card.json is the only path consulted. const mockFetch = createMockFetch( new Map([ [ @@ -100,8 +116,7 @@ describe('discoverAgentCard', () => { const card = await discoverAgentCard('https://agent.example.com', { fetch: mockFetch, }); - expect(card).not.toBeNull(); - expect(card!.name).toBe('Test Agent'); + expect(card).toBeNull(); }); it('returns null when no agent card found', async () => { @@ -289,7 +304,13 @@ describe('getPeacExtension', () => { const AGENT_CARD_WITH_PARAMS: A2AAgentCard = { name: 'Full Agent', - url: 'https://agent.example.com', + supportedInterfaces: [ + { + url: 'https://agent.example.com', + protocolBinding: 'http+json', + protocolVersion: '1.0.0', + }, + ], capabilities: { extensions: [ { diff --git a/packages/mappings/a2a/tests/normalizers.test.ts b/packages/mappings/a2a/tests/normalizers.test.ts index eb2824f3a..c6ceb58cb 100644 --- a/packages/mappings/a2a/tests/normalizers.test.ts +++ b/packages/mappings/a2a/tests/normalizers.test.ts @@ -1,17 +1,14 @@ -import { describe, it, expect, beforeEach } from 'vitest'; +import { describe, it, expect } from 'vitest'; import { isV1AgentCard, normalizeAgentCard, selectBestInterface, - normalizeTaskState, - _resetDeprecationWarning, - A2A_V1_TASK_STATE, type A2AAgentCard, type A2ASupportedInterface, } from '../src/index'; describe('isV1AgentCard', () => { - it('returns true for v1.0.0 card with supportedInterfaces', () => { + it('returns true for v1.0.0 card with supportedInterfaces[0].url', () => { const card: A2AAgentCard = { name: 'Test Agent', supportedInterfaces: [ @@ -21,28 +18,35 @@ describe('isV1AgentCard', () => { expect(isV1AgentCard(card)).toBe(true); }); - it('returns false for v0.3.0 card with top-level url', () => { + it('returns false for a card with empty supportedInterfaces', () => { const card: A2AAgentCard = { name: 'Test Agent', - url: 'https://agent.example.com', + supportedInterfaces: [], }; expect(isV1AgentCard(card)).toBe(false); }); - it('returns false for card with empty supportedInterfaces', () => { + it('returns false for a card without supportedInterfaces (legacy v0.3.0 shape, no longer accepted)', () => { + // A card with only a top-level `url` (the v0.3.0 shape) is not a + // valid v1.0.0 Agent Card. v0.3.0 compatibility was removed in + // v0.13.0 (DD-186); isV1AgentCard MUST NOT claim this shape is v1. + const card = { + name: 'Legacy Agent', + url: 'https://legacy.example.com', + } as unknown as A2AAgentCard; + expect(isV1AgentCard(card)).toBe(false); + }); + + it('returns false when supportedInterfaces[0].url is empty', () => { const card: A2AAgentCard = { - name: 'Test Agent', - supportedInterfaces: [], + name: 'Broken Agent', + supportedInterfaces: [{ url: '', protocolBinding: 'http+json', protocolVersion: '1.0' }], }; expect(isV1AgentCard(card)).toBe(false); }); }); describe('normalizeAgentCard', () => { - beforeEach(() => { - _resetDeprecationWarning(); - }); - it('normalizes v1.0.0 card with supportedInterfaces', () => { const card: A2AAgentCard = { name: 'V1 Agent', @@ -52,23 +56,20 @@ describe('normalizeAgentCard', () => { }; const result = normalizeAgentCard(card); expect(result).not.toBeNull(); - expect(result!.version).toBe('1.0.0'); expect(result!.url).toBe('https://v1.example.com'); expect(result!.name).toBe('V1 Agent'); expect(result!.original).toBe(card); }); - it('normalizes v0.3.0 card with top-level url', () => { - const card: A2AAgentCard = { + it('rejects v0.3.0 card with only top-level url (DD-186 removal at v0.13.0)', () => { + // v0.3.0 cards carried `url` at the top level. v0.13.0 removes this + // compatibility path: normalizeAgentCard returns null instead of + // synthesizing a supportedInterfaces entry from the legacy url. + const card = { name: 'Legacy Agent', url: 'https://legacy.example.com', - }; - const result = normalizeAgentCard(card); - expect(result).not.toBeNull(); - expect(result!.version).toBe('0.3.0'); - expect(result!.url).toBe('https://legacy.example.com'); - expect(result!.supportedInterfaces).toHaveLength(1); - expect(result!.supportedInterfaces[0]!.protocolVersion).toBe('0.3.0'); + } as unknown as A2AAgentCard; + expect(normalizeAgentCard(card)).toBeNull(); }); it('returns null for card with neither url nor supportedInterfaces', () => { @@ -76,16 +77,18 @@ describe('normalizeAgentCard', () => { expect(normalizeAgentCard(card)).toBeNull(); }); - it('prefers supportedInterfaces over url when both present', () => { - const card: A2AAgentCard = { - name: 'Both Agent', + it('ignores a legacy top-level url when supportedInterfaces is present', () => { + // A hybrid card (v0.3.0 url plus v1.0.0 supportedInterfaces) is + // treated as v1.0.0 by the normalizer. The top-level url is no + // longer consulted. + const card = { + name: 'Hybrid Agent', url: 'https://old.example.com', supportedInterfaces: [ { url: 'https://new.example.com', protocolBinding: 'http+json', protocolVersion: '1.0' }, ], - }; + } as A2AAgentCard; const result = normalizeAgentCard(card); - expect(result!.version).toBe('1.0.0'); expect(result!.url).toBe('https://new.example.com'); }); @@ -139,29 +142,3 @@ describe('selectBestInterface', () => { expect(selectBestInterface(interfaces)!.url).toBe('https://only.example.com'); }); }); - -describe('normalizeTaskState', () => { - beforeEach(() => { - _resetDeprecationWarning(); - }); - - it('converts v0.3.0 kebab-case to v1.0.0 prefixed form', () => { - expect(normalizeTaskState('submitted')).toBe(A2A_V1_TASK_STATE.SUBMITTED); - expect(normalizeTaskState('working')).toBe(A2A_V1_TASK_STATE.WORKING); - expect(normalizeTaskState('completed')).toBe(A2A_V1_TASK_STATE.COMPLETED); - expect(normalizeTaskState('failed')).toBe(A2A_V1_TASK_STATE.FAILED); - expect(normalizeTaskState('canceled')).toBe(A2A_V1_TASK_STATE.CANCELED); - expect(normalizeTaskState('rejected')).toBe(A2A_V1_TASK_STATE.REJECTED); - expect(normalizeTaskState('input-required')).toBe(A2A_V1_TASK_STATE.INPUT_REQUIRED); - expect(normalizeTaskState('auth-required')).toBe(A2A_V1_TASK_STATE.AUTH_REQUIRED); - }); - - it('passes through v1.0.0 values unchanged', () => { - expect(normalizeTaskState('TASK_STATE_WORKING')).toBe('TASK_STATE_WORKING'); - expect(normalizeTaskState('TASK_STATE_COMPLETED')).toBe('TASK_STATE_COMPLETED'); - }); - - it('passes through unknown values unchanged', () => { - expect(normalizeTaskState('custom-state')).toBe('custom-state'); - }); -}); From 0598cb6e072dd50b0b6b6a2d26e8e3eb4419acad Mon Sep 17 00:00:00 2001 From: jithinraj <7850727+jithinraj@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:35:45 +0530 Subject: [PATCH 04/10] chore(release): track @peac/mappings-a2a API surface Two small consistency fixes on top of the A2A v0.3.0 removal: 1. Add @peac/mappings-a2a to the API-surface tracking gate. The A2A v0.3.0 removal deleted three public exports (TASK_STATE_V03_TO_V1, normalizeTaskState, and _resetDeprecationWarning) from a published package. Grep confirmed zero in-repo callers, but because @peac/mappings-a2a is on npm `latest`, external consumers deserve an explicit snapshot-tracked contract. - scripts/release/api-surface-lock.sh: add @peac/mappings-a2a to TRACKED_PACKAGES with an inline note explaining why the package was added alongside the v0.3.0 removal. - docs/releases/api-surface/mappings-a2a.txt: new snapshot (68 public exports). Confirmed absent: TASK_STATE_V03_TO_V1, normalizeTaskState, _resetDeprecationWarning. Confirmed present: normalizeAgentCard, isV1AgentCard, selectBestInterface, A2A_MAX_CARRIER_SIZE. - `bash scripts/release/api-surface-lock.sh` now PASSes nine tracked packages (was eight). 2. Tighten runtime-vs-type wording on the Agent Card shape. A2AAgentCard still declares a `[key: string]: unknown` index signature, so TypeScript structurally accepts literals with a stray `url: string` property. The v0.3.0 removal is enforced at runtime by normalizeAgentCard (returns null) and discoverAgentCard (skips invalid cards), not at the type level. The prior commit message and JSDoc were ambiguous on this point. - packages/mappings/a2a/src/types.ts: JSDoc rewritten to name the rejection layer (runtime, not types) and the reason the index signature stays (tolerate unknown incoming JSON fields). - docs/MIGRATION_CURRENT.md: A2A v0.3.0 removal section rewritten to say "rejected at runtime" instead of just "no longer accepted," with an explicit note that the index signature remains and type-level rejection is not claimed. No source behavior change. No wire format change. Runtime rejection behavior for v0.3.0 shapes is byte-stable with the prior commit; the change is in how precisely the repo describes that rejection. --- docs/MIGRATION_CURRENT.md | 2 +- docs/releases/api-surface/mappings-a2a.txt | 68 ++++++++++++++++++++++ packages/mappings/a2a/src/types.ts | 11 +++- scripts/release/api-surface-lock.sh | 4 ++ 4 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 docs/releases/api-surface/mappings-a2a.txt diff --git a/docs/MIGRATION_CURRENT.md b/docs/MIGRATION_CURRENT.md index 648e9f33c..d0f4434a1 100644 --- a/docs/MIGRATION_CURRENT.md +++ b/docs/MIGRATION_CURRENT.md @@ -8,7 +8,7 @@ A2A v0.3.0 compatibility was deprecated in v0.12.3 (DD-186) after the A2A v1.0.0 Surfaces removed: -- **Agent Card top-level `url`.** v0.3.0 cards carried the endpoint URL as `AgentCard.url`. v1.0.0 replaced this with `supportedInterfaces[]`. Cards without a valid `supportedInterfaces[0].url` are no longer accepted; `normalizeAgentCard(card)` returns `null` for them and `discoverAgentCard(...)` skips them. +- **Agent Card top-level `url`.** v0.3.0 cards carried the endpoint URL as `AgentCard.url`. v1.0.0 replaced this with `supportedInterfaces[]`. Cards without a valid `supportedInterfaces[0].url` are rejected at **runtime**: `normalizeAgentCard(card)` returns `null` and `discoverAgentCard(...)` skips them. (The `A2AAgentCard` interface intentionally keeps a `[key: string]: unknown` index signature so incoming JSON with unknown extra fields still typechecks; type-level rejection is NOT claimed. The v0.3.0 removal is enforced by the normalization layer, not the type system.) - **Kebab-case TaskState strings.** v0.3.0 used `"working"`, `"completed"`, `"input-required"`, etc. v1.0.0 uses SCREAMING_SNAKE_CASE with a type prefix (`"TASK_STATE_WORKING"`, `"TASK_STATE_COMPLETED"`, `"TASK_STATE_INPUT_REQUIRED"`). The `normalizeTaskState` function and the `TASK_STATE_V03_TO_V1` map are removed. Callers MUST supply v1.0.0 TaskState values directly. - **`/.well-known/agent.json` legacy discovery path.** `discoverAgentCard(baseUrl)` now fetches only the v1.0.0 canonical path `/.well-known/agent-card.json`. Deployers still serving the legacy path should publish the canonical path or upgrade to an A2A v1.0.0 implementation. - **Deprecation-warning plumbing.** `_resetDeprecationWarning` is gone; no v0.3.0 `DeprecationWarning` is emitted because v0.3.0 inputs are now rejected outright rather than normalized with a warning. diff --git a/docs/releases/api-surface/mappings-a2a.txt b/docs/releases/api-surface/mappings-a2a.txt new file mode 100644 index 000000000..1c798c4c5 --- /dev/null +++ b/docs/releases/api-surface/mappings-a2a.txt @@ -0,0 +1,68 @@ +A2AAgentCard +A2AArtifactLike +A2AAuthErrorCode +A2AAuthEvent +A2AAuthEvidenceResult +A2AAuthMethod +A2ACarrierAdapter +A2ADeviceCodeFlowConfig +A2AExtractAsyncResult +A2AExtractResult +A2AMessageLike +A2AMetadata +A2AOAuthConfig +A2APeacPayload +A2ASupportedInterface +A2ATaskStatusLike +A2A_MAX_CARRIER_SIZE +A2A_V1_MESSAGE_ROLE +A2A_V1_TASK_STATE +AgentCardExtension +AgentCardPeacExtension +AuthorizationRequest +DeviceCodeErrorResponse +DeviceCodePollingError +DeviceCodeRequest +DeviceCodeResponse +DeviceCodeTokenResponse +DiscoveryOptions +FetchFn +GrpcErrorInfo +GrpcStatus +GrpcStatusCode +GrpcStatusCodeValue +NormalizedAgentCard +PEAC_EXTENSION_URI +PKCEChallenge +PeacDiscoveryResult +PeacDiscoverySource +TokenResponse +attachReceiptToArtifact +attachReceiptToMessage +attachReceiptToMetadata +attachReceiptToTaskStatus +buildA2AExtensionsHeader +buildAuthorizationRequest +computeS256Challenge +createA2AAuthStatus +createA2ACarrierMeta +discoverAgentCard +discoverPeacCapabilities +exchangeAuthorizationCode +extractReceiptFromArtifact +extractReceiptFromArtifactAsync +extractReceiptFromMessage +extractReceiptFromMessageAsync +extractReceiptFromMetadata +extractReceiptFromMetadataAsync +extractReceiptFromTaskStatus +extractReceiptFromTaskStatusAsync +fromA2AAuthEvent +generatePKCEChallenge +getPeacExtension +hasPeacExtension +isV1AgentCard +normalizeAgentCard +parseA2AExtensionsHeader +selectBestInterface +validatePKCEVerifier diff --git a/packages/mappings/a2a/src/types.ts b/packages/mappings/a2a/src/types.ts index cf01f12ce..9186b6eb4 100644 --- a/packages/mappings/a2a/src/types.ts +++ b/packages/mappings/a2a/src/types.ts @@ -61,8 +61,15 @@ export interface A2ASupportedInterface { * * v1.0.0 cards expose endpoint bindings via `supportedInterfaces[]`. * Every accepted card MUST carry a non-empty `supportedInterfaces[0].url`. - * The legacy v0.3.0 top-level `url` field was accepted through v0.12.x - * with a deprecation warning and removed in v0.13.0 (DD-186). + * + * The legacy v0.3.0 top-level `url` field is no longer a declared field + * on this interface. Rejection is **runtime** (`normalizeAgentCard` returns + * null and `discoverAgentCard` skips the card) — not type-level. The + * interface intentionally keeps a `[key: string]: unknown` index + * signature so consumers may pass incoming JSON with unknown extra + * fields without TypeScript errors, so a literal with a stray `url` + * string property still typechecks. The v0.3.0 removal (DD-186) is + * enforced by the normalization layer, not by the type system. */ export interface A2AAgentCard { name: string; diff --git a/scripts/release/api-surface-lock.sh b/scripts/release/api-surface-lock.sh index 214febead..e800fd6a3 100755 --- a/scripts/release/api-surface-lock.sh +++ b/scripts/release/api-surface-lock.sh @@ -38,6 +38,10 @@ TRACKED_PACKAGES=( "@peac/middleware-core" "@peac/adapter-eat" "@peac/mcp-server" + # @peac/mappings-a2a tracked from v0.13.0 PR B (DD-186 removal + # of TASK_STATE_V03_TO_V1 / normalizeTaskState / _resetDeprecationWarning + # warranted an explicit public-surface lock on this published package). + "@peac/mappings-a2a" # @peac/sdk archived (source in archive/sdk-js/); not a tracked public # surface in v0.13.0+. Historical snapshot preserved in git history. ) From 04de08748f8a7f0cef8530cc9205ca15f0eb5417 Mon Sep 17 00:00:00 2001 From: jithinraj <7850727+jithinraj@users.noreply.github.com> Date: Fri, 24 Apr 2026 18:37:34 +0530 Subject: [PATCH 05/10] refactor(api): archive legacy verify implementation and @peac/core Rewire the legacy `POST /verify` route to delegate in-process to the canonical `POST /v1/verify` handler. Every alias response carries Deprecation (RFC 9745), Sunset: Sat, 01 Nov 2026 00:00:00 GMT (RFC 8594), and Link rel="deprecation" (RFC 8288) headers. The alias stays runtime-reachable through the advertised Sunset date. apps/api no longer depends on @peac/core. Delete the v0.9.x VerifierV13 implementation, its Hono and Express wrappers, and the ambient type shim. Drop the external marker for @peac/core from the tsup config. Archive packages/core/ to archive/0.9.0-0.9.14/packages-core/. @peac/core is not published at v0.13.0 or later; historical npm versions <=0.9.14 remain installable for verify-only use of historical peac.receipt/0.9 records. Activate the corresponding line in the release maintainer's post-release deprecate script. Drop @peac/core from tsconfig.base.json path mappings, from the publish manifest, and from the now-unused bench-verify and assert-core-exports helpers. Remove the legacy `/verify` stanza from the public OpenAPI contract; HOSTED_VERIFY_CONTRACT.md now documents the alias-only runtime behavior. Update MIGRATION, PACKAGE_STATUS, PACKAGE_STATUS_V0.13.0_PARITY, STABILITY-CONTRACT, COMPATIBILITY_MATRIX, DEPRECATION_POLICY, and SECURITY to describe present-state compatibility coverage. Add two regression tests: - apps/api/tests/legacy-verify-alias-headers.test.ts asserts all three headers are present on every alias response and absent on the canonical /v1/verify response. - apps/api/tests/legacy-verify-alias-pre-sunset.test.ts asserts status parity and response-shape parity between the alias and canonical paths and pins the Sunset header value. Migrate the nightly crypto smoke from the archived @peac/core primitives to the @peac/crypto public sign/verify round-trip. Refresh facts.json metrics to match the post-archive state (build_targets=99, test_files=310, tests=7672). --- .github/workflows/nightly.yml | 25 +- REPO_SURFACE_STATUS.json | 14 +- SECURITY.md | 2 +- apps/api/package.json | 1 - apps/api/src/index.ts | 34 +- apps/api/src/peac-core.d.ts | 31 - apps/api/src/routes.ts | 85 --- apps/api/src/verifier.ts | 596 ------------------ .../tests/legacy-verify-alias-headers.test.ts | 97 +++ .../legacy-verify-alias-pre-sunset.test.ts | 110 ++++ apps/api/tsup.config.ts | 11 +- .../0.9.0-0.9.14/packages-core}/README.md | 28 +- .../packages-core}/jest.config.js | 0 .../0.9.0-0.9.14/packages-core}/package.json | 3 +- .../packages-core}/src/constants.ts | 0 .../packages-core}/src/crypto-sanity.test.js | 0 .../0.9.0-0.9.14/packages-core}/src/crypto.ts | 0 .../packages-core}/src/enforce.ts | 0 .../packages-core}/src/hash.test.js | 0 .../0.9.0-0.9.14/packages-core}/src/hash.ts | 0 .../packages-core}/src/ids/uuidv7.ts | 0 .../0.9.0-0.9.14/packages-core}/src/index.ts | 0 .../0.9.0-0.9.14/packages-core}/src/sign.ts | 0 .../0.9.0-0.9.14/packages-core}/src/types.ts | 0 .../packages-core}/src/verify.test.js | 0 .../0.9.0-0.9.14/packages-core}/src/verify.ts | 0 .../packages-core}/tsconfig.dts.json | 0 .../0.9.0-0.9.14/packages-core}/tsconfig.json | 0 .../packages-core}/tsconfig.types.json | 0 .../packages-core}/tsup.config.ts | 0 docs/COMPATIBILITY_MATRIX.md | 16 +- docs/DEPRECATION_POLICY.md | 6 +- docs/HOSTED_VERIFY_CONTRACT.md | 10 + docs/MIGRATION_CURRENT.md | 12 +- docs/PACKAGE_STATUS.md | 22 +- docs/PACKAGE_STATUS_V0.13.0_PARITY.md | 22 +- docs/STABILITY-CONTRACT.md | 46 +- docs/SURFACE_STATUS.md | 15 +- docs/releases/facts.json | 4 +- packages/cli/tsconfig.types.json | 3 +- packages/mappings/a2a/src/normalizers.ts | 13 +- packages/mappings/a2a/src/types.ts | 6 +- packages/schema/openapi/verify.yaml | 68 +- pnpm-lock.yaml | 38 +- scripts/assert-core-exports.mjs | 104 --- scripts/bench-verify.ts | 169 ----- scripts/guard.sh | 4 +- scripts/publish-manifest.json | 1 - scripts/release/npm-deprecate-v0.13.0.sh | 16 +- tsconfig.base.json | 1 - 50 files changed, 392 insertions(+), 1221 deletions(-) delete mode 100644 apps/api/src/peac-core.d.ts delete mode 100644 apps/api/src/routes.ts delete mode 100644 apps/api/src/verifier.ts create mode 100644 apps/api/tests/legacy-verify-alias-headers.test.ts create mode 100644 apps/api/tests/legacy-verify-alias-pre-sunset.test.ts rename {packages/core => archive/0.9.0-0.9.14/packages-core}/README.md (64%) rename {packages/core => archive/0.9.0-0.9.14/packages-core}/jest.config.js (100%) rename {packages/core => archive/0.9.0-0.9.14/packages-core}/package.json (91%) rename {packages/core => archive/0.9.0-0.9.14/packages-core}/src/constants.ts (100%) rename {packages/core => archive/0.9.0-0.9.14/packages-core}/src/crypto-sanity.test.js (100%) rename {packages/core => archive/0.9.0-0.9.14/packages-core}/src/crypto.ts (100%) rename {packages/core => archive/0.9.0-0.9.14/packages-core}/src/enforce.ts (100%) rename {packages/core => archive/0.9.0-0.9.14/packages-core}/src/hash.test.js (100%) rename {packages/core => archive/0.9.0-0.9.14/packages-core}/src/hash.ts (100%) rename {packages/core => archive/0.9.0-0.9.14/packages-core}/src/ids/uuidv7.ts (100%) rename {packages/core => archive/0.9.0-0.9.14/packages-core}/src/index.ts (100%) rename {packages/core => archive/0.9.0-0.9.14/packages-core}/src/sign.ts (100%) rename {packages/core => archive/0.9.0-0.9.14/packages-core}/src/types.ts (100%) rename {packages/core => archive/0.9.0-0.9.14/packages-core}/src/verify.test.js (100%) rename {packages/core => archive/0.9.0-0.9.14/packages-core}/src/verify.ts (100%) rename {packages/core => archive/0.9.0-0.9.14/packages-core}/tsconfig.dts.json (100%) rename {packages/core => archive/0.9.0-0.9.14/packages-core}/tsconfig.json (100%) rename {packages/core => archive/0.9.0-0.9.14/packages-core}/tsconfig.types.json (100%) rename {packages/core => archive/0.9.0-0.9.14/packages-core}/tsup.config.ts (100%) delete mode 100755 scripts/assert-core-exports.mjs delete mode 100644 scripts/bench-verify.ts diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index cf6c04c40..0fe2bac1d 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -135,24 +135,27 @@ jobs: - name: Test core crypto functions run: | - echo "Testing @peac/core crypto functions with Node.js..." + echo "Testing @peac/crypto sign/verify round-trip with Node.js..." node --input-type=module -e " - const m = await import('./packages/core/dist/index.js'); - const { signDetached, verifyDetached, generateEdDSAKeyPair, validateKidFormat } = m; + const m = await import('./packages/crypto/dist/index.js'); + const { sign, verify, generateKeypair } = m; console.log('Testing with Node.js...'); - const { privateKey, publicKey, kid } = await generateEdDSAKeyPair(); + const { privateKey, publicKey } = await generateKeypair(); console.log('[OK] Key generation works'); - if (!validateKidFormat(kid)) throw new Error('Generated kid invalid format'); - console.log('[OK] Kid validation works'); - - const payload = 'test payload'; - const jws = await signDetached(payload, privateKey, kid); + const kid = 'nightly-test-key'; + const payload = { msg: 'test payload' }; + const jws = await sign(payload, privateKey, kid); + if (typeof jws !== 'string' || jws.split('.').length !== 3) { + throw new Error('sign() did not produce a compact JWS'); + } console.log('[OK] Signing works'); - const verified = await verifyDetached(payload, jws, publicKey); - if (!verified) throw new Error('Verification failed'); + const result = await verify(jws, publicKey); + if (!result || !result.payload || result.payload.msg !== payload.msg) { + throw new Error('Verification did not return the original payload'); + } console.log('[OK] Verification works'); console.log('[OK] Node.js runtime validation complete'); " diff --git a/REPO_SURFACE_STATUS.json b/REPO_SURFACE_STATUS.json index 8e1548293..4fffb48c3 100644 --- a/REPO_SURFACE_STATUS.json +++ b/REPO_SURFACE_STATUS.json @@ -425,15 +425,6 @@ "layer": 5, "published": false }, - "packages/core": { - "npm": "@peac/core", - "state": "deprecated", - "wire": "0.9", - "layer": "legacy", - "published": true, - "removal": "v0.13.0", - "note": "DEPRECATED. Archival of packages/core/ is coupled with the legacy /verify handler rewire that eliminates the last active consumer in apps/api/src/verifier.ts. Historical 0.9-series receipt verify-only path; use @peac/protocol + @peac/schema + @peac/crypto + @peac/kernel for current wire." - }, "packages/sdk-js": { "npm": "@peac/sdk", "state": "archived", @@ -574,6 +565,11 @@ "layer": 6, "published": false, "note": "ARCHIVED in v0.13.0. Near-empty scaffold (version constant + doc comment) never published." + }, + "archive/0.9.0-0.9.14/packages-core": { + "state": "archived", + "wire": "0.1", + "note": "ARCHIVED in v0.13.0. Source moved from packages/core/ to archive/0.9.0-0.9.14/packages-core/. Historical 0.9-series peac.receipt/0.9 verify-only path. Not published at v0.13.0 or later. Historical npm versions <=0.9.14 remain installable (deprecate-then-remove discipline)." } } } diff --git a/SECURITY.md b/SECURITY.md index db0b1680a..856271762 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -40,7 +40,7 @@ kept in the loop and the disclosure calendar is coordinated. | `v0.12.x` | Active | Wire 0.2 (`interaction-record+jwt`) | Through the `v0.13.x` line | | `v0.11.x` | Maintenance (security fixes only) | Wire 0.1 (`peac-receipt/0.1`) | 6 months after `v0.13.0` ships | | `v0.10.x` and earlier | End of life | Wire 0.1 and earlier | No further updates | -| `peac.receipt/0.9` archival path | Verify-only through `@peac/core` | `peac.receipt/0.9` | Frozen; removal at `v0.13.0` | +| `peac.receipt/0.9` archival path | Historical verify-only | `peac.receipt/0.9` | Archived at `v0.13.0` | See [Compatibility matrix](docs/COMPATIBILITY_MATRIX.md) for full runtime and wire-format compatibility, and [Security operations](docs/SECURITY-OPERATIONS.md) diff --git a/apps/api/package.json b/apps/api/package.json index d8ded81d9..6399192db 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -30,7 +30,6 @@ }, "dependencies": { "@hono/node-server": "^1.19.13", - "@peac/core": "workspace:*", "@peac/crypto": "workspace:*", "@peac/disc": "workspace:*", "@peac/jwks-cache": "workspace:*", diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 58b691fb6..11c2da3f0 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -5,7 +5,6 @@ import { Hono } from 'hono'; import { serve } from '@hono/node-server'; -import { createV13HonoHandler } from './routes.js'; import { createVerifyV1Handler } from './verify-v1.js'; import { createIssueV1Handler } from './hosted-issue.js'; import { createIssuerHealthHandler } from './issuer-health.js'; @@ -34,11 +33,6 @@ export type { HttpStatus, } from './types.js'; -// Legacy enhanced verifier (deprecated -- kept for backwards compat, will be removed) -export { VerifierV13 } from './verifier.js'; -export { createV13ExpressHandler, createV13HonoHandler } from './routes.js'; -export type { V13VerifyRequest, V13VerifyResponse, VerifierOptions } from './verifier.js'; - // v1 verify endpoint export { createVerifyV1Handler, resetVerifyV1RateLimit } from './verify-v1.js'; @@ -68,6 +62,19 @@ export const PROBLEM_TYPES = { MISCONFIGURED_VERIFIER: 'https://www.peacprotocol.org/problems/misconfigured-verifier', } as const; +/** + * Deprecation headers for the legacy `POST /verify` alias and any other + * route that predates `POST /v1/verify`. The alias keeps serving valid + * `/v1/verify`-shaped responses, but every response carries an RFC 9745 + * `Deprecation` marker, an RFC 8594 `Sunset` date, and an RFC 8288 + * `Link` relation pointing at the migration guide. + */ +export const LEGACY_VERIFY_DEPRECATION_HEADERS = { + Deprecation: 'true', + Sunset: 'Sat, 01 Nov 2026 00:00:00 GMT', + Link: '; rel="deprecation"', +} as const; + // HTTP Server (when run as application) if (import.meta.url === `file://${process.argv[1]}`) { const app = new Hono(); @@ -96,18 +103,21 @@ if (import.meta.url === `file://${process.argv[1]}`) { // Health check endpoint app.get('/health', (c) => c.json({ ok: true })); - // Legacy verify endpoint (deprecated: removal target v0.13.0; see Sunset header) - app.post('/verify', createV13HonoHandler()); - // Canonical v1 verify endpoint const verifyV1 = createVerifyV1Handler(); app.post('/v1/verify', verifyV1); + // Legacy verify endpoint. Kept runtime-reachable through the advertised + // Sunset date. The alias delegates in-process to the canonical v1 + // handler and stamps deprecation headers on every response. + app.post('/verify', (c) => { + for (const [k, v] of Object.entries(LEGACY_VERIFY_DEPRECATION_HEADERS)) c.header(k, v); + return verifyV1(c); + }); + // Deprecated alias (Sunset: Nov 1 2026) app.post('/api/v1/verify', (c) => { - c.header('Sunset', 'Sat, 01 Nov 2026 00:00:00 GMT'); - c.header('Deprecation', 'true'); - c.header('Link', '; rel="deprecation"'); + for (const [k, v] of Object.entries(LEGACY_VERIFY_DEPRECATION_HEADERS)) c.header(k, v); return verifyV1(c); }); diff --git a/apps/api/src/peac-core.d.ts b/apps/api/src/peac-core.d.ts deleted file mode 100644 index 32da4141f..000000000 --- a/apps/api/src/peac-core.d.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Ambient type declarations for @peac/core (deprecated compat-only package). - * - * @peac/core may not emit .d.ts files reliably in CI. This file provides - * the minimal type surface needed by the legacy verifier.ts so that - * typecheck:apps can run as a blocking CI gate. - * - * Remove this file when apps/api/src/verifier.ts is migrated away from - * @peac/core (tracked: @peac/core removal target is v0.13.0). - */ -declare module '@peac/core' { - export interface VerifyKeySet { - [kid: string]: Uint8Array; - } - - export interface VerifyResult { - valid: boolean; - claims?: Record; - payload?: Record; - kid?: string; - error?: string; - } - - export function verifyReceipt( - jws: string, - keys: VerifyKeySet, - options?: Record - ): Promise; - - export function canonicalPolicyHash(policy: unknown): Promise; -} diff --git a/apps/api/src/routes.ts b/apps/api/src/routes.ts deleted file mode 100644 index 1b3e994d1..000000000 --- a/apps/api/src/routes.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * API routes for v0.9.13.1 enhanced verifier (deprecated) - */ - -import { VerifierV13 } from './verifier.js'; - -const verifier = new VerifierV13(); - -/** - * Centralized deprecation header values for the legacy /verify endpoint. - * RFC 8594 (Sunset), RFC 8288 (Link rel="deprecation"). - */ -const LEGACY_DEPRECATION_HEADERS = { - Sunset: 'Sat, 01 Nov 2026 00:00:00 GMT', - Deprecation: 'true', - Link: '; rel="deprecation"', -} as const; - -// Express.js route handler -export function createV13ExpressHandler() { - return async (req: any, res: any) => { - // Extract observability headers - const requestId = req.headers['x-request-id']; - const traceParent = req.headers['traceparent']; - - const result = await verifier.verify(req.body, { - allowPrivateNet: process.env.PEAC_ALLOW_PRIVATE_NET === 'true', - timeout: 250, // Total ≤ 250ms per spec - maxInputSize: 256 * 1024, // 256 KiB - maxRedirects: 3, - requestId, - traceId: traceParent, - }); - - res.status(result.status); - - if (result.status !== 200) { - res.set('Content-Type', 'application/problem+json'); - } else { - res.set('Content-Type', 'application/json'); - } - - // Add required security headers - res.set('X-Content-Type-Options', 'nosniff'); - res.set('Cache-Control', 'no-store'); - res.set('Vary', 'PEAC-Receipt'); - res.set('Referrer-Policy', 'no-referrer'); - - // RFC 8594 deprecation headers - for (const [k, v] of Object.entries(LEGACY_DEPRECATION_HEADERS)) res.set(k, v); - - res.json(result.body); - }; -} - -// Hono route handler -export function createV13HonoHandler() { - return async (c: any) => { - // Extract observability headers - const requestId = c.req.header('x-request-id'); - const traceParent = c.req.header('traceparent'); - - const body = await c.req.json(); - const result = await verifier.verify(body, { - allowPrivateNet: process.env.PEAC_ALLOW_PRIVATE_NET === 'true', - timeout: 250, // Total ≤ 250ms per spec - maxInputSize: 256 * 1024, - maxRedirects: 3, - requestId, - traceId: traceParent, - }); - - const headers = { - 'Content-Type': result.status === 200 ? 'application/json' : 'application/problem+json', - 'X-Content-Type-Options': 'nosniff', - 'Cache-Control': 'no-store', - Vary: 'PEAC-Receipt', - 'Referrer-Policy': 'no-referrer', - // RFC 8594 deprecation headers - ...LEGACY_DEPRECATION_HEADERS, - }; - - return c.json(result.body, result.status, headers); - }; -} diff --git a/apps/api/src/verifier.ts b/apps/api/src/verifier.ts deleted file mode 100644 index 6b3d0b358..000000000 --- a/apps/api/src/verifier.ts +++ /dev/null @@ -1,596 +0,0 @@ -/** - * Enhanced verifier implementation for v0.9.13.1 spec - * POST /verify {receipt, resource} → {valid, claims, policyHash, reconstructed, inputs, timing} - */ - -import { promises as dns } from 'node:dns'; -import { verifyReceipt, canonicalPolicyHash } from '@peac/core'; -import type { VerifyKeySet } from '@peac/core'; -import { discover } from '@peac/disc'; -import type { HttpStatus } from './types.js'; -import { PROBLEM_TYPES } from './index.js'; - -export interface V13VerifyRequest { - receipt: string; - resource?: string; -} - -export interface V13VerifyResponse { - valid: boolean; - claims?: any; - policyHash?: string; - reconstructed?: { - hash?: string; - matches?: boolean; - }; - inputs?: Array<{ - type: 'aipref' | 'agent-permissions' | 'peac.txt'; - url: string; - etag?: string | null; - }>; - timing: { - total_ms: number; - fetch_ms: number; - hash_ms: number; - }; - meta?: { - request_id?: string; - trace_id?: string; - }; -} - -export interface VerifierOptions { - timeout?: number; - allowPrivateNet?: boolean; - maxInputSize?: number; - maxRedirects?: number; - requestId?: string; - traceId?: string; -} - -export class VerifierV13 { - private cache = new Map(); - - async verify( - request: V13VerifyRequest, - options: VerifierOptions = {} - ): Promise<{ status: HttpStatus; body: V13VerifyResponse | any }> { - const startTime = Date.now(); - let fetchTime = 0; - let hashTime = 0; - - const buildTiming = () => ({ - total_ms: Date.now() - startTime, - fetch_ms: fetchTime, - hash_ms: hashTime, - }); - - const buildMeta = () => ({ - ...(options.requestId && { request_id: options.requestId }), - ...(options.traceId && { trace_id: options.traceId }), - }); - - try { - // Validate request - if (!request.receipt || typeof request.receipt !== 'string') { - return { - status: 400, - body: { - type: PROBLEM_TYPES.INVALID_REQUEST, - title: 'Invalid Request', - status: 400, - detail: 'receipt field is required and must be a string', - timing: buildTiming(), - meta: buildMeta(), - }, - }; - } - - // Parse verification keys with fail-closed logic - function parseKeyset(env = process.env.PEAC_VERIFY_KEYS): VerifyKeySet { - if (!env) return {}; - try { - const ks = JSON.parse(env); - return ks && typeof ks === 'object' ? (ks as VerifyKeySet) : {}; - } catch { - return {}; // invalid JSON - } - } - - const keys = parseKeyset(); - if (!keys || Object.keys(keys).length === 0) { - return { - status: 422, - body: { - type: PROBLEM_TYPES.MISCONFIGURED_VERIFIER, - title: 'Missing Verification Keys', - status: 422, - detail: 'PEAC_VERIFY_KEYS is not set or invalid.', - timing: buildTiming(), - meta: buildMeta(), - }, - }; - } - - // Verify receipt signature using v0.9.14 core function - let payload; - try { - ({ payload } = await verifyReceipt(request.receipt, keys)); - } catch (error) { - const errorMsg = error instanceof Error ? error.message : 'Unknown error'; - - // Map specific error types to problem types - if (errorMsg.includes('Expired receipt')) { - return { - status: 422, - body: { - type: PROBLEM_TYPES.EXPIRED_RECEIPT, - title: 'Expired Receipt', - status: 422, - detail: errorMsg, - timing: buildTiming(), - meta: buildMeta(), - }, - }; - } - - return { - status: 422, - body: { - type: PROBLEM_TYPES.INVALID_SIGNATURE, - title: 'Invalid Signature', - status: 422, - detail: errorMsg, - timing: buildTiming(), - meta: buildMeta(), - }, - }; - } - - const response: V13VerifyResponse = { - valid: true, // reaching here means verification passed - claims: payload, - timing: buildTiming(), - meta: buildMeta(), - }; - - // If resource is provided, discover policies and recompute hash - if (request.resource) { - try { - const fetchStart = Date.now(); - await this.addPolicyValidation(request.resource, response, options); - fetchTime = Date.now() - fetchStart; - - // Update timing with fetch time - response.timing = buildTiming(); - } catch (error) { - // Policy validation errors don't invalidate the receipt itself - response.reconstructed = { - hash: '', - matches: false, - }; - response.timing = buildTiming(); - } - } - - return { - status: 200, - body: response, - }; - } catch (error) { - return { - status: 500, - body: { - type: PROBLEM_TYPES.PROCESSING_ERROR, - title: 'Processing Error', - status: 500, - detail: error instanceof Error ? error.message : 'Unknown error', - timing: buildTiming(), - meta: buildMeta(), - }, - }; - } - } - - private async addPolicyValidation( - resource: string, - response: V13VerifyResponse, - options: VerifierOptions - ) { - const inputs: V13VerifyResponse['inputs'] = []; - const totalTimeout = Math.min(options.timeout || 250, 250); // Total ≤ 250ms - const startTime = Date.now(); - - // Apply SSRF guards - if (!(await this.isAllowedUrl(resource, options))) { - throw new Error('URL not allowed by security policy'); - } - - // Helper to check remaining time budget - const checkTimeLimit = () => { - if (Date.now() - startTime > totalTimeout) { - throw new Error('Total time budget exceeded'); - } - }; - - // Discover peac.txt with caching. peac.txt is a policy-document surface - // per docs/specs/PEAC-TXT.md; this verifier only records the observation - // (URL + etag) and does NOT read key-discovery fields from the parsed - // result. Key resolution uses /.well-known/peac-issuer.json -> jwks_uri - // -> JWKS elsewhere in the verify path. - try { - checkTimeLimit(); - const peacUrl = new URL('/.well-known/peac.txt', resource).toString(); - const cached = this.getCachedResult(peacUrl); - - if (cached) { - inputs.push({ - type: 'peac.txt', - url: peacUrl, - etag: cached.etag || null, - }); - } else { - const peacResult = await discover(resource); - this.setCachedResult(peacUrl, peacResult, null); - inputs.push({ - type: 'peac.txt', - url: peacUrl, - etag: null, - }); - } - } catch { - inputs.push({ - type: 'peac.txt', - url: new URL('/.well-known/peac.txt', resource).toString(), - etag: null, - }); - } - - // Check AIPREF headers with caching - try { - checkTimeLimit(); - const cached = this.getCachedResult(resource); - - let aiprefResult; - let etag = null; - - if (cached) { - // Use cached result and send If-None-Match if ETag available - const headers = cached.etag ? { 'If-None-Match': cached.etag } : undefined; - try { - aiprefResult = await this.fetchWithLimits(resource, { - method: 'HEAD', - timeout: 150, - headers, - }); - if (aiprefResult.status === 304) { - // Not modified, use cached data - aiprefResult = cached.data; - etag = cached.etag; - } else { - // Updated, store new result - etag = aiprefResult.headers.get('etag'); - this.setCachedResult(resource, aiprefResult, etag); - } - } catch { - // Fallback to cached data on fetch error - aiprefResult = cached.data; - etag = cached.etag; - } - } else { - aiprefResult = await this.fetchWithLimits(resource, { - method: 'HEAD', - timeout: 150, - }); - etag = aiprefResult.headers.get('etag'); - this.setCachedResult(resource, aiprefResult, etag); - } - - inputs.push({ - type: 'aipref', - url: resource, - etag, - }); - } catch { - inputs.push({ - type: 'aipref', - url: resource, - etag: null, - }); - } - - // Check agent-permissions with caching - try { - checkTimeLimit(); - const htmlCacheKey = `${resource}:html`; - const cached = this.getCachedResult(htmlCacheKey); - - let htmlResponse; - let etag = null; - - if (cached) { - // Use cached result and send If-None-Match if ETag available - const headers = cached.etag ? { 'If-None-Match': cached.etag } : undefined; - try { - htmlResponse = await this.fetchWithLimits(resource, { - timeout: 150, - headers, - }); - if (htmlResponse.status === 304) { - // Not modified, use cached data - htmlResponse = cached.data; - etag = cached.etag; - } else { - // Updated, store new result - etag = htmlResponse.headers.get('etag'); - this.setCachedResult(htmlCacheKey, htmlResponse, etag); - } - } catch { - // Fallback to cached data on fetch error - htmlResponse = cached.data; - etag = cached.etag; - } - } else { - htmlResponse = await this.fetchWithLimits(resource, { - timeout: 150, - }); - etag = htmlResponse.headers.get('etag'); - this.setCachedResult(htmlCacheKey, htmlResponse, etag); - } - - const html = await htmlResponse.text(); - const linkMatch = html.match( - /]*rel=["']agent-permissions["'][^>]*href=["']([^"']+)["']/i - ); - - if (linkMatch) { - const href = linkMatch[1]; - const absoluteUrl = new URL(href, resource).toString(); - inputs.push({ - type: 'agent-permissions', - url: absoluteUrl, - etag, - }); - } else { - inputs.push({ - type: 'agent-permissions', - url: resource, - etag: null, - }); - } - } catch { - inputs.push({ - type: 'agent-permissions', - url: resource, - etag: null, - }); - } - - response.inputs = inputs; - - // Recompute policy hash from discovered inputs with timing - const hashStart = Date.now(); - try { - const mockPolicy = { - resource, - inputs: inputs, - discovered_at: new Date().toISOString(), - }; - - const recomputedHash = await canonicalPolicyHash(mockPolicy); - response.policyHash = recomputedHash; - - // Compare with receipt policy_hash if present - if (response.claims?.policy_hash) { - response.reconstructed = { - hash: recomputedHash, - matches: response.claims.policy_hash === recomputedHash, - }; - } - } catch (error) { - response.reconstructed = { - hash: '', - matches: false, - }; - } - - // Update hash timing in the parent context - if (response.timing) { - response.timing.hash_ms = Date.now() - hashStart; - } - } - - private getCachedResult(key: string) { - const cached = this.cache.get(key); - if (!cached) return null; - if (Date.now() >= cached.expires) { - this.cache.delete(key); - return null; - } - return cached; // { data, etag, expires } - } - - private setCachedResult(key: string, data: any, etag: string | null) { - this.cache.set(key, { data, etag: etag || undefined, expires: Date.now() + 5 * 60 * 1000 }); - } - - private async isAllowedUrl(url: string, options: VerifierOptions): Promise { - try { - const parsed = new URL(url); - - // Scheme allowlist: https only; http only on loopback - if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') { - return false; - } - - if (parsed.protocol === 'http:') { - const hostname = parsed.hostname.toLowerCase(); - // Only allow http for loopback addresses - if (hostname !== 'localhost' && hostname !== '127.0.0.1' && hostname !== '::1') { - return false; - } - } - - // Resolve and block private/link-local after DNS - if (!options.allowPrivateNet) { - try { - const addrs = await dns.lookup(parsed.hostname, { all: true }); - for (const addr of addrs) { - if (await this.isIpPrivate(addr.address)) { - return false; - } - } - } catch { - // DNS resolution failed - return false; - } - } - - return true; - } catch { - return false; - } - } - - private async isIpPrivate(addr: string): Promise { - // IPv4 private ranges - const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/; - const ipv4Match = addr.match(ipv4Regex); - - if (ipv4Match) { - const [, a, b, c, d] = ipv4Match.map(Number); - - // Loopback: 127.0.0.0/8 - if (a === 127) return true; - // Private ranges: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 - if (a === 10) return true; - if (a === 172 && b >= 16 && b <= 31) return true; - if (a === 192 && b === 168) return true; - // Link-local: 169.254.0.0/16 - if (a === 169 && b === 254) return true; - - return false; - } - - // IPv6 ranges - if (addr.includes(':')) { - const lower = addr.toLowerCase(); - // Loopback: ::1 - if (lower === '::1' || lower === '0:0:0:0:0:0:0:1') return true; - // ULA: fc00::/7 - if (lower.startsWith('fc') || lower.startsWith('fd')) return true; - // Link-local: fe80::/10 - if ( - lower.startsWith('fe8') || - lower.startsWith('fe9') || - lower.startsWith('fea') || - lower.startsWith('feb') - ) - return true; - } - - return false; - } - - private async fetchWithLimits( - url: string, - options: { - method?: string; - timeout?: number; - maxRedirects?: number; - headers?: Record; - } = {} - ) { - const perFetch = Math.min(options.timeout || 150, 150); - const maxSize = 256 * 1024; // 256 KiB - const maxRedirects = Math.min(options.maxRedirects || 3, 3); - - let current = url; - let redirects = 0; - - for (;;) { - // Validate current URL with DNS resolution - if (!(await this.isAllowedUrl(current, {}))) { - throw new Error('URL not allowed'); - } - - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), perFetch); - - try { - const response = await fetch(current, { - method: options.method || 'GET', - redirect: 'manual', - signal: controller.signal, - headers: { - 'User-Agent': 'PEAC-Verifier/0.9.13.1', - Accept: 'text/html,application/json,text/plain,*/*;q=0.8', - 'Cache-Control': 'no-cache', - ...options.headers, - }, - }); - - clearTimeout(timeoutId); - - // Handle 3xx redirects with same-scheme validation and DNS re-check - if (response.status >= 300 && response.status < 400) { - const location = response.headers.get('location'); - if (!location) throw new Error('Redirect without Location'); - - const next = new URL(location, current).toString(); - const fromScheme = new URL(current).protocol; - const toScheme = new URL(next).protocol; - if (fromScheme !== toScheme) throw new Error('Cross-scheme redirect blocked'); - - if (++redirects > maxRedirects) throw new Error('Too many redirects'); - current = next; - continue; - } - - // Enforce size limit even without Content-Length by streaming - if (response.body) { - const reader = response.body.getReader(); - const chunks: Uint8Array[] = []; - let totalRead = 0; - - try { - for (;;) { - const { value, done } = await reader.read(); - if (done) break; - - if (value) { - totalRead += value.byteLength; - if (totalRead > maxSize) { - throw new Error('Response too large'); - } - chunks.push(value); - } - } - - // Reconstruct the response with the buffered body - const totalLength = chunks.reduce((acc, chunk) => acc + chunk.byteLength, 0); - const body = new Uint8Array(totalLength); - let offset = 0; - for (const chunk of chunks) { - body.set(chunk, offset); - offset += chunk.byteLength; - } - - return new Response(body, { - status: response.status, - statusText: response.statusText, - headers: response.headers, - }); - } finally { - reader.releaseLock(); - } - } - - return response; - } catch (error) { - clearTimeout(timeoutId); - throw error; - } - } - } -} diff --git a/apps/api/tests/legacy-verify-alias-headers.test.ts b/apps/api/tests/legacy-verify-alias-headers.test.ts new file mode 100644 index 000000000..f2ca029e4 --- /dev/null +++ b/apps/api/tests/legacy-verify-alias-headers.test.ts @@ -0,0 +1,97 @@ +/** + * Legacy /verify alias: deprecation-header contract. + * + * The reference verifier keeps `POST /verify` runtime-reachable as a + * deprecated compatibility alias. Every response (success or error) + * MUST carry: + * + * - `Deprecation: true` (RFC 9745) + * - `Sunset: Sat, 01 Nov 2026 00:00:00 GMT` (RFC 8594) + * - `Link: ; rel="deprecation"` (RFC 8288) + * + * The same is true for the `/api/v1/verify` alias. This test exercises + * the alias routes directly in a minimal Hono app that wires the alias + * handler the same way the real server does. + */ + +import { describe, it, expect, beforeAll } from 'vitest'; +import { Hono } from 'hono'; +import { + createVerifyV1Handler, + resetVerifyV1RateLimit, + isProblemError, + PROBLEM_MEDIA_TYPE, + LEGACY_VERIFY_DEPRECATION_HEADERS, +} from '../src/index.js'; + +function buildApp() { + resetVerifyV1RateLimit(); + const app = new Hono(); + app.onError((err, c) => { + if (isProblemError(err)) { + c.header('Content-Type', PROBLEM_MEDIA_TYPE); + return c.body(JSON.stringify(err.toProblemDetails()), err.status); + } + return c.json({ title: 'Internal Server Error', status: 500 }, 500); + }); + const verifyV1 = createVerifyV1Handler(); + app.post('/v1/verify', verifyV1); + app.post('/verify', (c) => { + for (const [k, v] of Object.entries(LEGACY_VERIFY_DEPRECATION_HEADERS)) c.header(k, v); + return verifyV1(c); + }); + app.post('/api/v1/verify', (c) => { + for (const [k, v] of Object.entries(LEGACY_VERIFY_DEPRECATION_HEADERS)) c.header(k, v); + return verifyV1(c); + }); + return app; +} + +function request(path: string, body: unknown) { + return new Request(`http://localhost${path}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }); +} + +describe('legacy /verify alias — deprecation headers', () => { + let app: ReturnType; + + beforeAll(() => { + app = buildApp(); + }); + + it('exposes the full deprecation header set as a named constant', () => { + expect(LEGACY_VERIFY_DEPRECATION_HEADERS.Deprecation).toBe('true'); + expect(LEGACY_VERIFY_DEPRECATION_HEADERS.Sunset).toBe('Sat, 01 Nov 2026 00:00:00 GMT'); + expect(LEGACY_VERIFY_DEPRECATION_HEADERS.Link).toBe( + '; rel="deprecation"' + ); + }); + + it('stamps all three headers on a /verify error response', async () => { + const res = await app.request(request('/verify', { receipt: 'not-a-jws' })); + expect(res.headers.get('Deprecation')).toBe('true'); + expect(res.headers.get('Sunset')).toBe('Sat, 01 Nov 2026 00:00:00 GMT'); + expect(res.headers.get('Link')).toBe( + '; rel="deprecation"' + ); + }); + + it('stamps all three headers on an /api/v1/verify error response', async () => { + const res = await app.request(request('/api/v1/verify', { receipt: 'not-a-jws' })); + expect(res.headers.get('Deprecation')).toBe('true'); + expect(res.headers.get('Sunset')).toBe('Sat, 01 Nov 2026 00:00:00 GMT'); + expect(res.headers.get('Link')).toBe( + '; rel="deprecation"' + ); + }); + + it('canonical /v1/verify response does NOT carry deprecation headers', async () => { + const res = await app.request(request('/v1/verify', { receipt: 'not-a-jws' })); + expect(res.headers.get('Deprecation')).toBeNull(); + expect(res.headers.get('Sunset')).toBeNull(); + expect(res.headers.get('Link')).toBeNull(); + }); +}); diff --git a/apps/api/tests/legacy-verify-alias-pre-sunset.test.ts b/apps/api/tests/legacy-verify-alias-pre-sunset.test.ts new file mode 100644 index 000000000..03238778a --- /dev/null +++ b/apps/api/tests/legacy-verify-alias-pre-sunset.test.ts @@ -0,0 +1,110 @@ +/** + * Legacy /verify alias: pre-Sunset runtime responsiveness. + * + * The alias MUST remain runtime-reachable through the advertised Sunset + * date (Sat, 01 Nov 2026 00:00:00 GMT, RFC 8594). For the same input, + * the alias MUST return the same response shape and status code as the + * canonical `POST /v1/verify`. The alias differs only in the + * deprecation-header set stamped on every response. + * + * This test drives the alias and the canonical route with identical + * inputs and asserts both the status-code parity and the shape of the + * response body, across a success-adjacent path (the request is parsed + * and reaches the verifier layer) and an error path (malformed input). + */ + +import { describe, it, expect, beforeAll } from 'vitest'; +import { Hono } from 'hono'; +import { + createVerifyV1Handler, + resetVerifyV1RateLimit, + isProblemError, + PROBLEM_MEDIA_TYPE, + LEGACY_VERIFY_DEPRECATION_HEADERS, +} from '../src/index.js'; + +function buildApp() { + resetVerifyV1RateLimit(); + const app = new Hono(); + app.onError((err, c) => { + if (isProblemError(err)) { + c.header('Content-Type', PROBLEM_MEDIA_TYPE); + return c.body(JSON.stringify(err.toProblemDetails()), err.status); + } + return c.json({ title: 'Internal Server Error', status: 500 }, 500); + }); + const verifyV1 = createVerifyV1Handler(); + app.post('/v1/verify', verifyV1); + app.post('/verify', (c) => { + for (const [k, v] of Object.entries(LEGACY_VERIFY_DEPRECATION_HEADERS)) c.header(k, v); + return verifyV1(c); + }); + return app; +} + +function request(path: string, body: unknown) { + return new Request(`http://localhost${path}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }); +} + +describe('legacy /verify alias — pre-Sunset responsiveness', () => { + let app: ReturnType; + + beforeAll(() => { + app = buildApp(); + }); + + it('Sunset header value is exactly the advertised date', async () => { + const res = await app.request(request('/verify', { receipt: 'not-a-jws' })); + expect(res.headers.get('Sunset')).toBe('Sat, 01 Nov 2026 00:00:00 GMT'); + }); + + it('alias and canonical return identical status codes on invalid input', async () => { + const aliasRes = await app.request(request('/verify', { receipt: 'not-a-jws' })); + const canonRes = await app.request(request('/v1/verify', { receipt: 'not-a-jws' })); + expect(aliasRes.status).toBe(canonRes.status); + }); + + it('alias and canonical return identical response shape on invalid input', async () => { + const aliasRes = await app.request(request('/verify', { receipt: 'not-a-jws' })); + const canonRes = await app.request(request('/v1/verify', { receipt: 'not-a-jws' })); + + const aliasBody = await aliasRes.json(); + const canonBody = await canonRes.json(); + + // Both paths return RFC 9457 Problem Details. Compare the set of + // keys (values that would drift per-request, e.g. peac_trace_id, are + // allowed to differ). + expect(Object.keys(aliasBody).sort()).toEqual(Object.keys(canonBody).sort()); + expect(aliasBody.type).toBe(canonBody.type); + expect(aliasBody.title).toBe(canonBody.title); + expect(aliasBody.status).toBe(canonBody.status); + }); + + it('alias and canonical agree on RFC 9457 Content-Type for error bodies', async () => { + const aliasRes = await app.request(request('/verify', { receipt: 'not-a-jws' })); + const canonRes = await app.request(request('/v1/verify', { receipt: 'not-a-jws' })); + const aliasCt = aliasRes.headers.get('Content-Type') ?? ''; + const canonCt = canonRes.headers.get('Content-Type') ?? ''; + // Both should start with application/problem+json (may or may not have charset) + expect(aliasCt.split(';')[0]).toBe(canonCt.split(';')[0]); + }); + + it('alias is reachable with malformed JSON (returns 400 Problem Details)', async () => { + const badReq = new Request('http://localhost/verify', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: '{not-json', + }); + const res = await app.request(badReq); + // We don't assert the exact status; only that the alias responded + // (did not hang, did not throw uncaught) and stamped deprecation. + expect(res.status).toBeGreaterThanOrEqual(400); + expect(res.status).toBeLessThan(500); + expect(res.headers.get('Sunset')).toBe('Sat, 01 Nov 2026 00:00:00 GMT'); + expect(res.headers.get('Deprecation')).toBe('true'); + }); +}); diff --git a/apps/api/tsup.config.ts b/apps/api/tsup.config.ts index 59e873d64..3ee8a1512 100644 --- a/apps/api/tsup.config.ts +++ b/apps/api/tsup.config.ts @@ -1,14 +1,7 @@ import { defineConfig } from 'tsup'; export default defineConfig({ - entry: [ - 'src/index.ts', - 'src/handler.ts', - 'src/errors.ts', - 'src/verifier.ts', - 'src/routes.ts', - 'src/verify-v1.ts', - ], + entry: ['src/index.ts', 'src/handler.ts', 'src/errors.ts', 'src/verify-v1.ts'], format: ['cjs', 'esm'], dts: false, // Temporarily disable declaration generation sourcemap: true, @@ -17,5 +10,5 @@ export default defineConfig({ treeshake: true, minify: false, target: 'es2022', - external: ['node:*', '@peac/core', '@peac/disc'], + external: ['node:*', '@peac/disc'], }); diff --git a/packages/core/README.md b/archive/0.9.0-0.9.14/packages-core/README.md similarity index 64% rename from packages/core/README.md rename to archive/0.9.0-0.9.14/packages-core/README.md index abd8d802e..208f381bc 100644 --- a/packages/core/README.md +++ b/archive/0.9.0-0.9.14/packages-core/README.md @@ -1,4 +1,30 @@ -# @peac/core +# @peac/core — ARCHIVED (verify-only history) + +> **ARCHIVED (v0.13.0).** Source moved from `packages/core/` to +> `archive/0.9.0-0.9.14/packages-core/`. `@peac/core` is **not published +> at v0.13.0 or later**. Historical npm versions `<=0.9.14` remain +> installable for verify-only use of historical `peac.receipt/0.9` +> records (deprecate-then-remove discipline; historical tarballs are +> never unpublished). +> +> `@peac/core` was the original monolithic package that bundled signing, +> verification, policy enforcement, and utilities. It has been replaced +> by the kernel-first package architecture. This directory is preserved +> for historical reference only; it is not built, not linted, and not +> included in the workspace. +> +> **Migration:** `@peac/kernel` (constants), `@peac/schema` (types), +> `@peac/crypto` (sign / verify primitives), `@peac/protocol` +> (`issue`, `verifyLocal`, `verify`). See +> [`docs/MIGRATION_CURRENT.md`](../../../docs/MIGRATION_CURRENT.md). +> +> Below this banner is the historical README preserved verbatim for +> archaeology. It describes the (now-archived) `@peac/core` surface and +> is no longer authoritative. + +--- + +## Historical README (pre-archive) > **DEPRECATED. Removal scheduled for v0.13.0.** > Use `@peac/kernel`, `@peac/schema`, `@peac/crypto`, and `@peac/protocol` instead. diff --git a/packages/core/jest.config.js b/archive/0.9.0-0.9.14/packages-core/jest.config.js similarity index 100% rename from packages/core/jest.config.js rename to archive/0.9.0-0.9.14/packages-core/jest.config.js diff --git a/packages/core/package.json b/archive/0.9.0-0.9.14/packages-core/package.json similarity index 91% rename from packages/core/package.json rename to archive/0.9.0-0.9.14/packages-core/package.json index 01ce4b880..d6acb69d4 100644 --- a/packages/core/package.json +++ b/archive/0.9.0-0.9.14/packages-core/package.json @@ -1,7 +1,8 @@ { "name": "@peac/core", "version": "0.12.14", - "description": "DEPRECATED (removal: v0.13.0) - Use @peac/kernel, @peac/schema, @peac/crypto, @peac/protocol instead", + "description": "ARCHIVED in v0.13.0. Historical 0.9-series verify-only path. Use @peac/kernel, @peac/schema, @peac/crypto, @peac/protocol instead.", + "private": true, "type": "module", "homepage": "https://github.com/peacprotocol/peac#readme", "bugs": { diff --git a/packages/core/src/constants.ts b/archive/0.9.0-0.9.14/packages-core/src/constants.ts similarity index 100% rename from packages/core/src/constants.ts rename to archive/0.9.0-0.9.14/packages-core/src/constants.ts diff --git a/packages/core/src/crypto-sanity.test.js b/archive/0.9.0-0.9.14/packages-core/src/crypto-sanity.test.js similarity index 100% rename from packages/core/src/crypto-sanity.test.js rename to archive/0.9.0-0.9.14/packages-core/src/crypto-sanity.test.js diff --git a/packages/core/src/crypto.ts b/archive/0.9.0-0.9.14/packages-core/src/crypto.ts similarity index 100% rename from packages/core/src/crypto.ts rename to archive/0.9.0-0.9.14/packages-core/src/crypto.ts diff --git a/packages/core/src/enforce.ts b/archive/0.9.0-0.9.14/packages-core/src/enforce.ts similarity index 100% rename from packages/core/src/enforce.ts rename to archive/0.9.0-0.9.14/packages-core/src/enforce.ts diff --git a/packages/core/src/hash.test.js b/archive/0.9.0-0.9.14/packages-core/src/hash.test.js similarity index 100% rename from packages/core/src/hash.test.js rename to archive/0.9.0-0.9.14/packages-core/src/hash.test.js diff --git a/packages/core/src/hash.ts b/archive/0.9.0-0.9.14/packages-core/src/hash.ts similarity index 100% rename from packages/core/src/hash.ts rename to archive/0.9.0-0.9.14/packages-core/src/hash.ts diff --git a/packages/core/src/ids/uuidv7.ts b/archive/0.9.0-0.9.14/packages-core/src/ids/uuidv7.ts similarity index 100% rename from packages/core/src/ids/uuidv7.ts rename to archive/0.9.0-0.9.14/packages-core/src/ids/uuidv7.ts diff --git a/packages/core/src/index.ts b/archive/0.9.0-0.9.14/packages-core/src/index.ts similarity index 100% rename from packages/core/src/index.ts rename to archive/0.9.0-0.9.14/packages-core/src/index.ts diff --git a/packages/core/src/sign.ts b/archive/0.9.0-0.9.14/packages-core/src/sign.ts similarity index 100% rename from packages/core/src/sign.ts rename to archive/0.9.0-0.9.14/packages-core/src/sign.ts diff --git a/packages/core/src/types.ts b/archive/0.9.0-0.9.14/packages-core/src/types.ts similarity index 100% rename from packages/core/src/types.ts rename to archive/0.9.0-0.9.14/packages-core/src/types.ts diff --git a/packages/core/src/verify.test.js b/archive/0.9.0-0.9.14/packages-core/src/verify.test.js similarity index 100% rename from packages/core/src/verify.test.js rename to archive/0.9.0-0.9.14/packages-core/src/verify.test.js diff --git a/packages/core/src/verify.ts b/archive/0.9.0-0.9.14/packages-core/src/verify.ts similarity index 100% rename from packages/core/src/verify.ts rename to archive/0.9.0-0.9.14/packages-core/src/verify.ts diff --git a/packages/core/tsconfig.dts.json b/archive/0.9.0-0.9.14/packages-core/tsconfig.dts.json similarity index 100% rename from packages/core/tsconfig.dts.json rename to archive/0.9.0-0.9.14/packages-core/tsconfig.dts.json diff --git a/packages/core/tsconfig.json b/archive/0.9.0-0.9.14/packages-core/tsconfig.json similarity index 100% rename from packages/core/tsconfig.json rename to archive/0.9.0-0.9.14/packages-core/tsconfig.json diff --git a/packages/core/tsconfig.types.json b/archive/0.9.0-0.9.14/packages-core/tsconfig.types.json similarity index 100% rename from packages/core/tsconfig.types.json rename to archive/0.9.0-0.9.14/packages-core/tsconfig.types.json diff --git a/packages/core/tsup.config.ts b/archive/0.9.0-0.9.14/packages-core/tsup.config.ts similarity index 100% rename from packages/core/tsup.config.ts rename to archive/0.9.0-0.9.14/packages-core/tsup.config.ts diff --git a/docs/COMPATIBILITY_MATRIX.md b/docs/COMPATIBILITY_MATRIX.md index c4ae16d37..fba3cf45e 100644 --- a/docs/COMPATIBILITY_MATRIX.md +++ b/docs/COMPATIBILITY_MATRIX.md @@ -14,7 +14,7 @@ Current as of v0.12.13. | `@peac/middleware-express` | Full | - | **default** | | Go SDK (`sdks/go/`) | Full: `Issue()` + `VerifyLocal()` + JCS (22 cross-language vectors) | Legacy verify only | **supported** (core issue/verify); middleware **experimental** | | Python | API-first via reference verifier (httpx examples, `>=3.12`) | - | **examples only** | -| `@peac/core` | - | Full (Wire 0.9 locked) | **deprecated** (removal: v0.13.0) | +| `@peac/core` | - | Full (Wire 0.9 locked) | **archived** (at v0.13.0) | | `@peac/sdk` | - | Full (Wire 0.1) | **archived** (use `@peac/protocol`) | ## Runtime Environments @@ -106,10 +106,10 @@ Informational and regression-oriented. Operator-facing service-level objectives ## Deprecation Schedule -| Surface | Deprecated since | Removal target | Migration | -| ------------------------- | ---------------- | ------------------------ | ---------------------------------------------------------------------------------------------------- | -| `@peac/core` | v0.10.0 | v0.13.0 | Use `@peac/kernel` plus `@peac/schema` plus `@peac/crypto` plus `@peac/protocol`. | -| `@peac/sdk` | v0.12.7 | v0.13.0 | Use `@peac/protocol` directly. | -| API `/verify` endpoint | v0.12.7 | v0.13.0 (or Nov 1, 2026) | Use `/api/v1/verify`. Legacy `/verify` carries RFC 9745 `Deprecation` and RFC 8594 `Sunset` headers. | -| `apps/bridge` | v0.12.7 | v0.13.0 | Use `@peac/protocol` or `/api/v1/verify`. | -| Wire 0.1 default teaching | v0.12.7 | Immediate | All defaults now Wire 0.2. | +| Surface | Deprecated since | Removal target | Migration | +| ------------------------- | ---------------- | ------------------------ | ------------------------------------------------------------------------------------------------ | +| `@peac/core` | v0.10.0 | v0.13.0 (archived) | Use `@peac/kernel` plus `@peac/schema` plus `@peac/crypto` plus `@peac/protocol`. | +| `@peac/sdk` | v0.12.7 | v0.13.0 (archived) | Use `@peac/protocol` directly. | +| API `/verify` endpoint | v0.12.7 | post-Sunset (2026-11-01) | Use `/v1/verify`. Legacy `/verify` delegates in-process; carries RFC 9745 / RFC 8594 / RFC 8288. | +| `apps/bridge` | v0.12.7 | v0.13.0 | Use `@peac/protocol` or `/api/v1/verify`. | +| Wire 0.1 default teaching | v0.12.7 | Immediate | All defaults now Wire 0.2. | diff --git a/docs/DEPRECATION_POLICY.md b/docs/DEPRECATION_POLICY.md index 33e6612d4..13bd7b973 100644 --- a/docs/DEPRECATION_POLICY.md +++ b/docs/DEPRECATION_POLICY.md @@ -51,10 +51,10 @@ No surface may be promoted from `experimental` or `compat-only` to `supported` o | Surface | State | Deprecated since | Removal target | Replacement | | ------------------------- | ---------- | ---------------- | ------------------------ | ------------------------------------------------------------------- | -| `@peac/core` | deprecated | v0.10.0 | v0.13.0 | `@peac/kernel` + `@peac/schema` + `@peac/crypto` + `@peac/protocol` | +| `@peac/core` | archived | v0.10.0 | v0.13.0 | `@peac/kernel` + `@peac/schema` + `@peac/crypto` + `@peac/protocol` | | `@peac/sdk` | archived | v0.12.7 | v0.13.0 | `@peac/protocol` | -| `apps/bridge` | archived | v0.12.7 | v0.13.0 | `@peac/protocol` or `/api/v1/verify` | -| API `/verify` | deprecated | v0.12.7 | v0.13.0 (or Nov 1, 2026) | `/api/v1/verify` | +| `apps/bridge` | archived | v0.12.7 | v0.13.0 | `@peac/protocol` or `/v1/verify` | +| API `/verify` | deprecated | v0.12.7 | post-Sunset (2026-11-01) | `/v1/verify` | | Wire 0.1 default teaching | removed | v0.12.7 | Immediate | All defaults now Wire 0.2 | ## Archive Protocol diff --git a/docs/HOSTED_VERIFY_CONTRACT.md b/docs/HOSTED_VERIFY_CONTRACT.md index 515960b2a..471cdb215 100644 --- a/docs/HOSTED_VERIFY_CONTRACT.md +++ b/docs/HOSTED_VERIFY_CONTRACT.md @@ -45,6 +45,16 @@ Verify a signed interaction record. **Error responses:** RFC 9457 Problem Details. +### Legacy `POST /verify` (deprecated compatibility alias) + +The reference verifier keeps `POST /verify` runtime-reachable for callers that have not yet migrated. The alias is **not** documented in the machine-readable OpenAPI contract (the canonical verify operation is `POST /v1/verify`). At runtime the alias: + +- Delegates in-process to the canonical `POST /v1/verify` handler and returns the same response shape and status codes. +- Stamps the same security and rate-limit headers as `POST /v1/verify`. +- Additionally stamps RFC 9745 `Deprecation: true`, RFC 8594 `Sunset: Sat, 01 Nov 2026 00:00:00 GMT`, and RFC 8288 `Link: ; rel="deprecation"` on every response. + +Runtime removal is scheduled no earlier than the advertised Sunset date (2026-11-01). New integrations MUST target `POST /v1/verify`; the `POST /api/v1/verify` path is covered by the same alias behavior. + ### POST /v1/issue (provisional) Issue a signed interaction record. This endpoint is provisional and uses a BYO-key model (caller provides an Ed25519 private key seed). The contract is defined in [`HOSTED_ISSUE_CONTRACT.md`](HOSTED_ISSUE_CONTRACT.md). The legacy request model shown below is retained for archival purposes only. diff --git a/docs/MIGRATION_CURRENT.md b/docs/MIGRATION_CURRENT.md index d0f4434a1..52a4b96dc 100644 --- a/docs/MIGRATION_CURRENT.md +++ b/docs/MIGRATION_CURRENT.md @@ -103,11 +103,11 @@ No runtime behavior change: `@peac/pref` v0.12.14 was already a facade over `@pe Summary: migrate parse / validate / emit to `@peac/policy-kit` now if your code only touches policy documents. Keep `@peac/disc@0.13.0` installed if your code needs `discover()` or the associated constants; migrate once the remote-fetch surface ports. -### `@peac/core` — archive coupled with legacy `/verify` handler rewire +### `@peac/core` — archived -**State:** `@peac/core` is the v0.9.x `peac.receipt/0.9` verify-only implementation and remains marked deprecated. Archival is coupled with the legacy `POST /verify` HTTP handler rewire (the only remaining active consumer is `apps/api/src/verifier.ts`). When that rewire lands, `packages/core/` moves to `archive/0.9.0-0.9.14/@peac-core/`, `apps/api/src/verifier.ts` is deleted, and the legacy `/verify` route delegates internally to the canonical `/v1/verify` handler while preserving its advertised `Sunset: Sat, 01 Nov 2026 00:00:00 GMT` (RFC 8594). +**State at v0.13.0:** archived. `@peac/core` was the v0.9.x `peac.receipt/0.9` verify-only implementation. Source moved from `packages/core/` to `archive/0.9.0-0.9.14/packages-core/`. `@peac/core` is **not published at v0.13.0 or later**. Historical npm versions `<=0.9.14` remain installable for verify-only use of historical `peac.receipt/0.9` records. The archive is coupled with the legacy `POST /verify` handler rewire: `apps/api/src/verifier.ts` was deleted (it was the only remaining active consumer), and the legacy `/verify` route now delegates in-process to the canonical `/v1/verify` handler while stamping RFC 9745 `Deprecation: true`, RFC 8594 `Sunset: Sat, 01 Nov 2026 00:00:00 GMT`, and RFC 8288 `Link` headers on every response. -**Migration:** use `@peac/protocol` (`issue`, `verifyLocal`, `verify`), `@peac/schema` (types), `@peac/crypto` (sign / verify primitives), and `@peac/kernel` (wire constants). Historical `@peac/core@<=0.9.14` versions on npm remain installable for verify-only use of historical `peac.receipt/0.9` records. +**Migration:** use `@peac/protocol` (`issue`, `verifyLocal`, `verify`), `@peac/schema` (types), `@peac/crypto` (sign / verify primitives), and `@peac/kernel` (wire constants). ### Empty Layer-6 pillar stubs — archived @@ -132,7 +132,7 @@ Kept in workspace as shipping packages: ### `npm deprecate` dispatch -Staged at [`scripts/release/npm-deprecate-v0.13.0.sh`](../scripts/release/npm-deprecate-v0.13.0.sh). Executed manually after promote. Covers `@peac/pref@<=0.12.14`, `@peac/sdk@<=0.10.2`, `@peac/disc@<=0.12.14`, and `@peac/disc@0.13.0` (explicitly marked as a one-release compatibility bridge). `@peac/core` deprecate line is present but commented until the legacy `/verify` handler rewire lands. +Staged at [`scripts/release/npm-deprecate-v0.13.0.sh`](../scripts/release/npm-deprecate-v0.13.0.sh). Executed manually after promote. Covers `@peac/pref@<=0.12.14`, `@peac/sdk@<=0.10.2`, `@peac/disc@<=0.12.14`, `@peac/disc@0.13.0` (marked as a one-release compatibility bridge), and `@peac/core@<=0.9.14`. --- @@ -203,7 +203,7 @@ const result = await verifyLocal(jws, publicKey); ## From `@peac/core` to `@peac/protocol` -`@peac/core` is deprecated (removal: v0.13.0). Migrate to the kernel-first packages. +`@peac/core` is archived at v0.13.0. Migrate to the kernel-first packages. ### Import changes @@ -229,7 +229,7 @@ import { generateKeypair, verify } from '@peac/crypto'; ## From legacy API `/verify` to `/v1/verify` -The legacy `/verify` endpoint is deprecated (Sunset: Nov 1, 2026). Migrate to the canonical `/v1/verify`. The `/api/v1/verify` path remains wired as a deprecated alias and resolves to the same handler; new code should use `/v1/verify`, which is the path documented in [`packages/schema/openapi/verify.yaml`](../packages/schema/openapi/verify.yaml) and [`docs/HOSTED_VERIFY_CONTRACT.md`](HOSTED_VERIFY_CONTRACT.md). +The legacy `/verify` endpoint is deprecated (Sunset: Nov 1, 2026). Migrate to the canonical `/v1/verify`. The `/verify` and `/api/v1/verify` paths remain runtime-reachable as deprecated compatibility aliases; they delegate in-process to the canonical `/v1/verify` handler, return the same response shape, and stamp RFC 9745 `Deprecation: true`, RFC 8594 `Sunset: Sat, 01 Nov 2026 00:00:00 GMT`, and RFC 8288 `Link: ; rel="deprecation"` on every response. New integrations must target `/v1/verify`, which is the only path documented in the public OpenAPI contract ([`packages/schema/openapi/verify.yaml`](../packages/schema/openapi/verify.yaml), [`docs/HOSTED_VERIFY_CONTRACT.md`](HOSTED_VERIFY_CONTRACT.md)). ### Request diff --git a/docs/PACKAGE_STATUS.md b/docs/PACKAGE_STATUS.md index 8eac13d39..90df0f8a0 100644 --- a/docs/PACKAGE_STATUS.md +++ b/docs/PACKAGE_STATUS.md @@ -85,20 +85,20 @@ Do not edit manually. Source: `REPO_SURFACE_STATUS.json`. Rebuild via `node scri | Package | npm | Removal | Note | | -------------------- | ------------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `packages/discovery` | `@peac/disc` | v0.13.1 | Published deprecated compatibility alias. Re-exports tolerant peac.txt parse / emit / validate / discover + constants; barrel emits one-shot PEAC_DISC_DEPRECATED DeprecationWarning. Canonical loader is @peac/policy-kit loadPolicyDocument. Retained at v0.13.0 to preserve publish closure with @peac/cli and apps/api; full retirement deferred until workspace consumers migrate. | -| `packages/core` | `@peac/core` | v0.13.0 | DEPRECATED. Archival of packages/core/ is coupled with the legacy /verify handler rewire that eliminates the last active consumer in apps/api/src/verifier.ts. Historical 0.9-series receipt verify-only path; use @peac/protocol + @peac/schema + @peac/crypto + @peac/kernel for current wire. | ## Archived (non-default, may be removed) -| Surface | Reason | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `packages/sdk-js` | Moved to archive/sdk-js/. Uses deprecated @peac/core. Replaced by @peac/protocol. | -| `apps/bridge` | Moved to archive/bridge/. Unused dev sidecar. Imports deprecated @peac/core. | -| `archive/pref` | ARCHIVED in v0.13.0 (moved from packages/aipref/). Migration: @peac/mappings-content-signals (RFC 9651 Structured Fields, full RFC 8785 JCS + SHA-256 digest). Historical npm versions <=0.12.14 remain installable (deprecate-then-remove discipline). | -| `archive/pillars/access` | ARCHIVED in v0.13.0. Empty pillar stub never published. Pillar taxonomy label retained; concrete implementation targeted for v1.0+ watchlist. | -| `archive/pillars/compliance` | ARCHIVED in v0.13.0. Empty pillar stub never published. | -| `archive/pillars/consent` | ARCHIVED in v0.13.0. Empty pillar stub never published. | -| `archive/pillars/intelligence` | ARCHIVED in v0.13.0. Empty pillar stub never published. | -| `archive/pillars/provenance` | ARCHIVED in v0.13.0. Near-empty scaffold (version constant + doc comment) never published. | +| Surface | Reason | +| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `packages/sdk-js` | Moved to archive/sdk-js/. Uses deprecated @peac/core. Replaced by @peac/protocol. | +| `apps/bridge` | Moved to archive/bridge/. Unused dev sidecar. Imports deprecated @peac/core. | +| `archive/pref` | ARCHIVED in v0.13.0 (moved from packages/aipref/). Migration: @peac/mappings-content-signals (RFC 9651 Structured Fields, full RFC 8785 JCS + SHA-256 digest). Historical npm versions <=0.12.14 remain installable (deprecate-then-remove discipline). | +| `archive/pillars/access` | ARCHIVED in v0.13.0. Empty pillar stub never published. Pillar taxonomy label retained; concrete implementation targeted for v1.0+ watchlist. | +| `archive/pillars/compliance` | ARCHIVED in v0.13.0. Empty pillar stub never published. | +| `archive/pillars/consent` | ARCHIVED in v0.13.0. Empty pillar stub never published. | +| `archive/pillars/intelligence` | ARCHIVED in v0.13.0. Empty pillar stub never published. | +| `archive/pillars/provenance` | ARCHIVED in v0.13.0. Near-empty scaffold (version constant + doc comment) never published. | +| `archive/0.9.0-0.9.14/packages-core` | ARCHIVED in v0.13.0. Source moved from packages/core/ to archive/0.9.0-0.9.14/packages-core/. Historical 0.9-series peac.receipt/0.9 verify-only path. Not published at v0.13.0 or later. Historical npm versions <=0.9.14 remain installable (deprecate-then-remove discipline). | ## Experimental (API may change) diff --git a/docs/PACKAGE_STATUS_V0.13.0_PARITY.md b/docs/PACKAGE_STATUS_V0.13.0_PARITY.md index e61e26d36..2372df286 100644 --- a/docs/PACKAGE_STATUS_V0.13.0_PARITY.md +++ b/docs/PACKAGE_STATUS_V0.13.0_PARITY.md @@ -69,16 +69,14 @@ If step 1 is not completed, `@peac/disc` must remain published as a compatibilit --- -## `@peac/core` → canonical replacement parity +## `@peac/core` → canonical replacement parity (already archived) -| `@peac/core` export | Kind | Replacement | Equivalent? | Action | -| ------------------------------------------------ | --------- | ------------------------------------------------------------ | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `verifyReceipt(jws, keys)` | function | `@peac/protocol.verifyReceipt` (canonical JWKS-based verify) | yes | Rewire `apps/api/src/verifier.ts` to delegate internally to the canonical `/v1/verify` handler; delete `apps/api/src/verifier.ts`; remove `@peac/core` from `apps/api/package.json`; archive `packages/core/`. | -| `canonicalPolicyHash` | function | Canonical `computePolicyDigestJcs` from `@peac/protocol` | yes | Rewire legacy-verify call sites to the canonical digest path. | -| `VerifyKeySet` | type | Canonical key-set type from `@peac/protocol` | yes | Update type imports in the surviving legacy code paths; confirm byte-identical verify output on parity corpus. | -| Legacy `peac.receipt/0.9` signing / verify paths | functions | No current-wire replacement; archive package is verify-only. | N/A | Already legacy-quarantined. No consumer on active surface should need these. | - -Archive execution is coupled with the legacy `/verify` handler rewire so the retirement is one conceptual change, not two. +| `@peac/core` export | Kind | Replacement | Equivalent? | Status | +| ------------------------------------------------ | --------- | ------------------------------------------------------------ | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `verifyReceipt(jws, keys)` | function | `@peac/protocol.verifyReceipt` (canonical JWKS-based verify) | yes | Archived. `apps/api/src/verifier.ts` deleted; the legacy `POST /verify` route now delegates in-process to `/v1/verify` and stamps RFC 9745 / RFC 8594 / RFC 8288 headers. `@peac/core` removed from `apps/api/package.json`. | +| `canonicalPolicyHash` | function | Canonical `computePolicyDigestJcs` from `@peac/protocol` | yes | Archived. No active consumer. | +| `VerifyKeySet` | type | Canonical key-set type from `@peac/protocol` | yes | Archived. No active consumer. | +| Legacy `peac.receipt/0.9` signing / verify paths | functions | No current-wire replacement; archive is verify-only. | N/A | Archived under `archive/0.9.0-0.9.14/packages-core/`. Historical npm versions `<=0.9.14` remain installable for verify-only use; `@peac/core` is not published at v0.13.0 or later. | --- @@ -96,6 +94,6 @@ No further migration work required; all `@peac/pref` exports have canonical equi ## Change log for this audit -| Date | Change | -| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 2026-04-24 | Initial audit drafted. `@peac/disc` is a published deprecated compatibility alias; retirement requires the 5 current-compatibility items above to be satisfied. `@peac/core` archive is coupled with the legacy `/verify` handler rewire. `@peac/pref` migration complete as of v0.12.14. | +| Date | Change | +| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 2026-04-24 | Initial audit drafted. `@peac/disc` is a published deprecated compatibility alias; retirement requires the 5 current-compatibility items above to be satisfied. `@peac/core` archived (source moved to `archive/0.9.0-0.9.14/packages-core/`; legacy `/verify` handler rewired to delegate in-process to `/v1/verify`). `@peac/pref` migration complete as of v0.12.14. | diff --git a/docs/STABILITY-CONTRACT.md b/docs/STABILITY-CONTRACT.md index f1db2266d..d364fc52a 100644 --- a/docs/STABILITY-CONTRACT.md +++ b/docs/STABILITY-CONTRACT.md @@ -20,14 +20,14 @@ maintenance (security and correctness fixes apply to every active line per ## Wire formats -| Surface | Concrete identifier | Classification | Notes | -| -------------------------- | --------------------------------------------------------- | -------------- | -------------------------------------------------------------- | -| Current interaction record | `typ: interaction-record+jwt` (JWS JOSE header; Wire 0.2) | `stable` | Frozen wire identifiers until v1.0 | -| Legacy receipt format | `peac-receipt/0.1` (Wire 0.1) | `stable` | Verify-only path frozen; no new-feature extensions | -| Archival receipt format | `peac.receipt/0.9` | `deprecated` | Verify-only via `@peac/core` archival path; removal at v0.13.0 | -| Cryptographic envelope | JWS Compact Serialization (RFC 7515), Ed25519 (RFC 8032) | `stable` | Algorithm negotiation is not supported | -| Canonical JSON | JCS (RFC 8785) | `stable` | Cross-language parity fixtures in `specs/conformance/` | -| Verifier response bodies | `application/json` (RFC 8259) | `stable` | Error bodies: `application/problem+json` (RFC 9457) | +| Surface | Concrete identifier | Classification | Notes | +| -------------------------- | --------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| Current interaction record | `typ: interaction-record+jwt` (JWS JOSE header; Wire 0.2) | `stable` | Frozen wire identifiers until v1.0 | +| Legacy receipt format | `peac-receipt/0.1` (Wire 0.1) | `stable` | Verify-only path frozen; no new-feature extensions | +| Archival receipt format | `peac.receipt/0.9` | `archived` | Historical verify-only path. Source archived under `archive/0.9.0-0.9.14/packages-core/`; historical npm `@peac/core@<=0.9.14` remains installable. | +| Cryptographic envelope | JWS Compact Serialization (RFC 7515), Ed25519 (RFC 8032) | `stable` | Algorithm negotiation is not supported | +| Canonical JSON | JCS (RFC 8785) | `stable` | Cross-language parity fixtures in `specs/conformance/` | +| Verifier response bodies | `application/json` (RFC 8259) | `stable` | Error bodies: `application/problem+json` (RFC 9457) | ## Public TypeScript APIs @@ -59,8 +59,8 @@ maintenance (security and correctness fixes apply to every active line per | `@peac/contracts` | `stable` | Machine-readable contract exports | | `@peac/pay402`, `@peac/pref`, `@peac/attribution`, `@peac/receipts` | `stable` | Supporting packages on Layer 4 | | `@peac/worker-core` | `stable` | Worker-oriented helpers | -| `@peac/core` | `deprecated` | Archival; Wire 0.9 verify-only path; removal at v0.13.0 | -| `@peac/sdk` | `deprecated` | Workspace stub; removal at v0.13.0 | +| `@peac/core` | `archived` | Archived at v0.13.0; source under `archive/0.9.0-0.9.14/packages-core/`; historical npm `<=0.9.14` remains installable | +| `@peac/sdk` | `archived` | Archived; historical npm `<=0.10.2` remains installable | Consumers: import only from the package's documented public entry points. Subpath imports into internal modules are not a stable surface even when @@ -96,11 +96,11 @@ Package: [`@peac/cli`](../packages/cli). ## Reference verifier (`apps/api`) -| Surface | Path | Classification | -| --------------------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------- | -| `POST /v1/verify` | [`apps/api`](../apps/api) | `stable` | -| `POST /v1/issue` | `apps/api` (provisional; see [Hosted issue contract](HOSTED_VERIFY_CONTRACT.md)) | `experimental` | -| Legacy `POST /verify` | `apps/api` | `deprecated` (Deprecation + Sunset headers; removal at v0.13.0) | +| Surface | Path | Classification | +| --------------------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| `POST /v1/verify` | [`apps/api`](../apps/api) | `stable` | +| `POST /v1/issue` | `apps/api` (provisional; see [Hosted issue contract](HOSTED_VERIFY_CONTRACT.md)) | `experimental` | +| Legacy `POST /verify` | `apps/api` | `deprecated` (delegates in-process to `/v1/verify`; Deprecation + Sunset headers; runtime removal no earlier than 2026-11-01) | Response shapes: @@ -156,14 +156,14 @@ Path: [`surfaces/plugin-pack/`](../surfaces/plugin-pack/). ## Deprecation schedule -| Surface | Deprecated since | Removal target | Status | -| --------------------------------------------------------- | ---------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ProofMethodSchema` (compat alias) | v0.12.2 | v0.13.0 | **Removed in v0.13.0 PR B.** Transport-binding values (`http-message-signature`, `dpop`, `mtls`, `jwk-thumbprint`) inlined on `AgentProofSchema.method`. | -| A2A v0.3.0 compatibility path | v0.12.3 | v0.13.0 PR B | **Removed in v0.13.0 PR B.** Agent Cards carrying only a top-level `url`, kebab-case TaskState strings, and the `/.well-known/agent.json` legacy discovery path are no longer accepted. A2A v1.0.0 `supportedInterfaces[]` is required. | -| Legacy `POST /verify` endpoint (in favor of `/v1/verify`) | v0.12.x | post-Sunset (2026-11-01) | v0.13.0 PR B removes from active OpenAPI teaching; runtime alias preserved until advertised Sunset date | -| `packages/sdk-js/` workspace stub | v0.12.x | v0.13.0 PR B | Scheduled | -| `peac.receipt/0.9` archival format | Legacy frozen | v0.13.0 (quarantine) | Quarantined to historical contexts; wire stays frozen | -| `@peac/core` archival verify-only path | Legacy frozen | v0.13.0 PR B | Scheduled (coupled with legacy `/verify` handler rewire) | +| Surface | Deprecated since | Removal target | Status | +| --------------------------------------------------------- | ---------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ProofMethodSchema` (compat alias) | v0.12.2 | v0.13.0 | **Removed in v0.13.0.** Transport-binding values (`http-message-signature`, `dpop`, `mtls`, `jwk-thumbprint`) inlined on `AgentProofSchema.method`. | +| A2A v0.3.0 compatibility path | v0.12.3 | v0.13.0 | **Removed in v0.13.0.** Agent Cards carrying only a top-level `url`, kebab-case TaskState strings, and the `/.well-known/agent.json` legacy discovery path are no longer accepted. A2A v1.0.0 `supportedInterfaces[]` is required. | +| Legacy `POST /verify` endpoint (in favor of `/v1/verify`) | v0.12.x | post-Sunset (2026-11-01) | Removed from active OpenAPI teaching at v0.13.0. Runtime alias preserved, delegating in-process to `/v1/verify`, until the advertised Sunset date. | +| `packages/sdk-js/` workspace stub | v0.12.x | v0.13.0 | Scheduled | +| `peac.receipt/0.9` archival format | Legacy frozen | v0.13.0 (quarantine) | Quarantined to historical contexts; wire stays frozen | +| `@peac/core` archival verify-only path | Legacy frozen | v0.13.0 | **Archived in v0.13.0** to `archive/0.9.0-0.9.14/packages-core/`; historical npm `<=0.9.14` remains installable. | All status transitions are tracked in [`REPO_SURFACE_STATUS.json`](../REPO_SURFACE_STATUS.json) and mirrored in diff --git a/docs/SURFACE_STATUS.md b/docs/SURFACE_STATUS.md index a884198de..74f436680 100644 --- a/docs/SURFACE_STATUS.md +++ b/docs/SURFACE_STATUS.md @@ -123,11 +123,10 @@ Do not edit manually. Source: `REPO_SURFACE_STATUS.json`. Rebuild via `node scri ## Layer legacy -| Surface | npm | State | Wire | -| ----------------- | ------------ | ---------- | ------ | -| `apps/bridge` | - | archived | 0.9.13 | -| `packages/core` | `@peac/core` | deprecated | 0.9 | -| `packages/sdk-js` | `@peac/sdk` | archived | 0.1 | +| Surface | npm | State | Wire | +| ----------------- | ----------- | -------- | ------ | +| `apps/bridge` | - | archived | 0.9.13 | +| `packages/sdk-js` | `@peac/sdk` | archived | 0.1 | ## Layer sdk @@ -140,3 +139,9 @@ Do not edit manually. Source: `REPO_SURFACE_STATUS.json`. Rebuild via `node scri | Surface | npm | State | Wire | | -------------- | ------------ | -------- | ---- | | `archive/pref` | `@peac/pref` | archived | null | + +## Layer undefined + +| Surface | npm | State | Wire | +| ------------------------------------ | --- | -------- | ---- | +| `archive/0.9.0-0.9.14/packages-core` | - | archived | 0.1 | diff --git a/docs/releases/facts.json b/docs/releases/facts.json index 72e754612..e2716264e 100644 --- a/docs/releases/facts.json +++ b/docs/releases/facts.json @@ -7,9 +7,9 @@ "release_date": "2026-04-22", "metrics": { "tests": 7672, - "test_files": 309, + "test_files": 310, "published_packages": 37, - "build_targets": 100, + "build_targets": 99, "conformance_requirement_ids": 224, "conformance_sections": 25 }, diff --git a/packages/cli/tsconfig.types.json b/packages/cli/tsconfig.types.json index 30d81005b..1ae35fde5 100644 --- a/packages/cli/tsconfig.types.json +++ b/packages/cli/tsconfig.types.json @@ -18,10 +18,9 @@ "noEmit": false, "paths": { - "@peac/core": ["../core/dist/index.d.ts"], "@peac/disc": ["../discovery/dist/index.d.ts"] } }, "include": ["src/**/*"], - "references": [{ "path": "../core" }, { "path": "../discovery" }] + "references": [{ "path": "../discovery" }] } diff --git a/packages/mappings/a2a/src/normalizers.ts b/packages/mappings/a2a/src/normalizers.ts index 3d2b45112..cac9d6cd8 100644 --- a/packages/mappings/a2a/src/normalizers.ts +++ b/packages/mappings/a2a/src/normalizers.ts @@ -26,11 +26,10 @@ export interface NormalizedAgentCard { } /** - * Returns true if the card satisfies the v1.0.0 Agent Card contract — - * that is, `supportedInterfaces[0].url` exists and is a non-empty - * string. Cards that fail this check are rejected by - * `normalizeAgentCard` (they are not v0.3.0 fallbacks, which are no - * longer accepted). + * Returns true if the card satisfies the v1.0.0 Agent Card contract: + * `supportedInterfaces[0].url` exists and is a non-empty string. Cards + * that fail this check are rejected by `normalizeAgentCard` (they are + * not v0.3.0 fallbacks, which are no longer accepted). */ export function isV1AgentCard(card: A2AAgentCard): boolean { return ( @@ -45,9 +44,9 @@ export function isV1AgentCard(card: A2AAgentCard): boolean { * Normalize an A2A v1.0.0 Agent Card. * * Returns a consistent shape with the resolved primary URL. Cards that - * do not satisfy `isV1AgentCard(...)` return `null` — including the + * do not satisfy `isV1AgentCard(...)` return `null` (this includes the * legacy v0.3.0 shape with only a top-level `url`, which is no longer - * supported. Callers receiving `null` should treat the card as invalid + * supported). Callers receiving `null` should treat the card as invalid * and surface a structured error rather than falling back to v0.3.0. */ export function normalizeAgentCard(card: A2AAgentCard): NormalizedAgentCard | null { diff --git a/packages/mappings/a2a/src/types.ts b/packages/mappings/a2a/src/types.ts index 9186b6eb4..1df3d8179 100644 --- a/packages/mappings/a2a/src/types.ts +++ b/packages/mappings/a2a/src/types.ts @@ -64,12 +64,12 @@ export interface A2ASupportedInterface { * * The legacy v0.3.0 top-level `url` field is no longer a declared field * on this interface. Rejection is **runtime** (`normalizeAgentCard` returns - * null and `discoverAgentCard` skips the card) — not type-level. The + * null and `discoverAgentCard` skips the card), not type-level. The * interface intentionally keeps a `[key: string]: unknown` index * signature so consumers may pass incoming JSON with unknown extra * fields without TypeScript errors, so a literal with a stray `url` - * string property still typechecks. The v0.3.0 removal (DD-186) is - * enforced by the normalization layer, not by the type system. + * string property still typechecks. The v0.3.0 removal is enforced by + * the normalization layer, not by the type system. */ export interface A2AAgentCard { name: string; diff --git a/packages/schema/openapi/verify.yaml b/packages/schema/openapi/verify.yaml index bada62427..8bced8117 100644 --- a/packages/schema/openapi/verify.yaml +++ b/packages/schema/openapi/verify.yaml @@ -21,9 +21,13 @@ info: Error responses use RFC 9457 Problem Details (`application/problem+json`) extended with PEAC-canonical fields (`peac_error_code`, `peac_trace_id`). - The legacy `POST /verify` endpoint is retained for compatibility and - carries RFC 9745 `Deprecation` and RFC 8594 `Sunset` response headers; - it is scheduled for removal no earlier than 2026-11-01. + The reference verifier also keeps a runtime-reachable `POST /verify` + alias for callers that have not yet migrated. The alias is omitted + from this contract because the canonical operation is + `POST /v1/verify`. Every response on the alias carries RFC 9745 + `Deprecation`, RFC 8594 `Sunset`, and RFC 8288 `Link` headers; the + alias is scheduled for runtime removal no earlier than 2026-11-01 + and is documented in `docs/HOSTED_VERIFY_CONTRACT.md`. Size caps (enforced by the kernel and verifier, documented here for contract completeness): @@ -43,7 +47,7 @@ servers: tags: - name: Verify - description: Verification endpoints (canonical and legacy). + description: Verification endpoint (canonical). - name: Issue description: Hosted issue endpoints (alpha). - name: Health @@ -196,62 +200,6 @@ paths: schema: $ref: '#/components/schemas/ProblemDetails' - /verify: - post: - operationId: verifyReceiptLegacy - tags: [Verify] - deprecated: true - summary: '[DEPRECATED] Legacy verify endpoint' - description: | - Legacy verification endpoint retained for backward compatibility. - New integrations MUST use `POST /v1/verify`. Every response from - this path carries RFC 9745 `Deprecation` and RFC 8594 `Sunset` - headers plus an RFC 8288 `Link` relation pointing at the migration - guide. Scheduled removal: no earlier than 2026-11-01. - Request body is `application/json`, identical shape to - `POST /v1/verify`. - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/VerifyRequest' - responses: - '200': - description: Verification completed (deprecated response shape). - headers: - Deprecation: - description: RFC 9745 marker indicating the endpoint is deprecated. - schema: - type: string - const: 'true' - Sunset: - description: RFC 8594 HTTP-date at which the endpoint is scheduled for removal. - schema: - type: string - example: 'Sat, 01 Nov 2026 00:00:00 GMT' - Link: - description: RFC 8288 Link header with rel="deprecation" pointing at the migration guide. - schema: - type: string - example: '; rel="deprecation"' - content: - application/json: - schema: - $ref: '#/components/schemas/VerifySuccessResponse' - '400': - description: Invalid request format. - content: - application/problem+json: - schema: - $ref: '#/components/schemas/ProblemDetails' - '422': - description: Verification failure. - content: - application/problem+json: - schema: - $ref: '#/components/schemas/ProblemDetails' - /v1/issue: post: operationId: issueReceipt diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8b4ac1089..d6cc58d2a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -100,9 +100,6 @@ importers: '@hono/node-server': specifier: '>=1.19.13' version: 1.19.13(hono@4.12.12) - '@peac/core': - specifier: workspace:* - version: link:../../packages/core '@peac/crypto': specifier: workspace:* version: link:../../packages/crypto @@ -1260,40 +1257,6 @@ importers: specifier: ^4.0.0 version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@22.19.11)(tsx@4.21.0)(yaml@2.8.3) - packages/core: - dependencies: - '@peac/kernel': - specifier: workspace:* - version: link:../kernel - jose: - specifier: ^5.2.0 - version: 5.10.0 - lru-cache: - specifier: ^10.0.0 - version: 10.4.3 - zod: - specifier: ^4.3.6 - version: 4.3.6 - devDependencies: - '@types/jest': - specifier: ^30.0.0 - version: 30.0.0 - '@types/node': - specifier: ^22.19.11 - version: 22.19.11 - jest: - specifier: ^30.2.0 - version: 30.2.0(@types/node@22.19.11) - ts-jest: - specifier: ^29.0.0 - version: 29.4.5(@babel/core@7.29.0)(esbuild@0.27.0)(jest@30.2.0)(typescript@5.9.3) - tsup: - specifier: ^8.0.0 - version: 8.5.1(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) - typescript: - specifier: ^5.2.0 - version: 5.9.3 - packages/crypto: dependencies: '@noble/ed25519': @@ -8895,6 +8858,7 @@ packages: /lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + dev: true /lru-cache@11.2.5: resolution: {integrity: sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==} diff --git a/scripts/assert-core-exports.mjs b/scripts/assert-core-exports.mjs deleted file mode 100755 index 0f7119deb..000000000 --- a/scripts/assert-core-exports.mjs +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env node -/** - * Assert @peac/core v0.9.14 exports are available - */ - -import { strict as assert } from 'node:assert'; - -async function main() { - console.log('Checking @peac/core exports...'); - - // Import the built module (avoid literal dist path for guard) - const parts = ['..', 'packages', 'core', 'dist', 'index.js']; - const core = await import(new URL(parts.join('/'), import.meta.url).href); - - // Required v0.9.14 exports - const requiredExports = [ - // Core functions - 'signReceipt', - 'createAndSignReceipt', - 'verifyReceipt', - 'enforce', - 'discover', - 'evaluate', - 'settle', - 'prove', - // Utilities - 'canonicalPolicyHash', - 'uuidv7', - 'isUUIDv7', - 'extractTimestamp', - ]; - - const requiredTypes = [ - 'Receipt', - 'Kid', - 'KeySet', - 'VerifyKeySet', - 'VerifyResult', - 'SignOptions', - 'SignReceiptOptions', - 'DiscoveryContext', - 'PolicySource', - 'EvaluationContext', - 'SettlementResult', - 'EnforceResult', - 'EnforceOptions', - 'PolicyInputs', - ]; - - // Check functions - for (const name of requiredExports) { - assert(typeof core[name] === 'function', `Missing export: ${name}`); - console.log(` [OK] ${name}`); - } - - // Check that types are exported (they won't be in the runtime, but build will fail if missing) - console.log('\nType exports (build-time check):'); - for (const type of requiredTypes) { - console.log(` [OK] ${type} (type)`); - } - - // Test basic functionality - console.log('\nTesting basic functionality...'); - - // Test signReceipt and verifyReceipt - try { - const receipt = await core.createAndSignReceipt({ - subject: 'https://example.com/test', - aipref: { status: 'allowed' }, - purpose: 'train-ai', - enforcement: { method: 'none' }, - kid: 'test-key', - privateKey: { - kty: 'OKP', - crv: 'Ed25519', - d: 'nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A', - x: '11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo', - }, - }); - - assert(typeof receipt === 'string', 'signReceipt should return a string'); - console.log(' [OK] createAndSignReceipt works'); - - // Test canonicalPolicyHash - const hash = core.canonicalPolicyHash({ test: 'data' }); - assert(typeof hash === 'string', 'canonicalPolicyHash should return a string'); - console.log(' [OK] canonicalPolicyHash works'); - - // Test uuidv7 - const uuid = core.uuidv7(); - assert(core.isUUIDv7(uuid), 'uuidv7 should generate valid UUIDv7'); - console.log(' [OK] uuidv7 works'); - - console.log('\n[OK] All @peac/core exports verified!'); - } catch (error) { - console.error('\n[FAIL] Functionality test failed:', error); - process.exit(1); - } -} - -main().catch((error) => { - console.error('[FAIL] Failed:', error); - process.exit(1); -}); diff --git a/scripts/bench-verify.ts b/scripts/bench-verify.ts deleted file mode 100644 index eba235a91..000000000 --- a/scripts/bench-verify.ts +++ /dev/null @@ -1,169 +0,0 @@ -/** - * @peac/core v0.9.14 - Verification performance benchmark - * Measures p95 latency for verifyReceipt() with proper @peac/core imports - * - * @deprecated This benchmark uses @peac/core which is deprecated. See v0.9.15+ benchmarks. - */ - -import { performance } from 'node:perf_hooks'; -import { writeFileSync } from 'node:fs'; -// @ts-expect-error jose types not installed in root devDependencies -import { generateKeyPair, exportJWK } from 'jose'; - -// Robust monorepo-safe module loading -async function loadCore() { - // 1) Normal resolution (if root has dep / after publish) - try { - // @ts-expect-error @peac/core is deprecated, use @peac/protocol - return await import('@peac/core'); - } catch { - /* fallback below */ - } - - // 2) Workspace package root (Node resolves via package.json "exports") - try { - // @ts-expect-error import.meta requires ESM module setting - const pkgRoot = new URL('../packages/core/', import.meta.url); - console.log('Loading @peac/core from workspace package root'); - return await import(pkgRoot.href); - } catch { - /* fallback below */ - } - - // 3) TS source (no build needed; tsx compiles it) - console.log('Fallback: Loading @peac/core from TypeScript source'); - // @ts-expect-error import.meta requires ESM module setting - const srcUrl = new URL('../packages/core/src/index.ts', import.meta.url); - return await import(srcUrl.href); -} - -const ITERATIONS = 300; -const WARMUP_ITERATIONS = 30; - -async function benchmark() { - // Load core module first - const core = await loadCore(); - const { verifyReceipt, createAndSignReceipt } = core; - - // 30s watchdog to ensure P95 is always printed - const kill = setTimeout(() => { - console.log('P95: 999'); // Timeout sentinel - process.exit(1); - }, 30_000); - - // Ensure P95 is always printed even on early exit - process.on('uncaughtException', (err) => { - console.error('Error:', err.message); - clearTimeout(kill); - console.log('P95: 999'); // Failure sentinel - process.exit(1); - }); - - console.log('Setting up test data...'); - - // Generate test key pair - const { privateKey, publicKey } = await generateKeyPair('EdDSA'); - const jwkPriv = await exportJWK(privateKey); - const jwkPub = await exportJWK(publicKey); - - jwkPriv.alg = 'EdDSA'; - jwkPriv.kid = 'bench-1'; - jwkPub.alg = 'EdDSA'; - jwkPub.kid = 'bench-1'; - - const keyId = 'bench-1'; - const keyPair = jwkPriv; - - // Create test receipt - const testReceipt = await createAndSignReceipt({ - subject: 'https://example.com/test-resource', - aipref: { status: 'allowed' }, - purpose: 'train-ai', - enforcement: { method: 'none' }, - kid: keyId, - privateKey: keyPair, - }); - - // Prepare verification keys (public only) - const verifyKeys = { - [keyId]: jwkPub, - }; - - console.log('Warming up...'); - - // Warmup - for (let i = 0; i < WARMUP_ITERATIONS; i++) { - await verifyReceipt(testReceipt, verifyKeys); - } - - console.log(`Running ${ITERATIONS} verification operations...`); - - const timings: number[] = []; - - for (let i = 0; i < ITERATIONS; i++) { - const start = performance.now(); - await verifyReceipt(testReceipt, verifyKeys); - const end = performance.now(); - timings.push(end - start); - } - - // Calculate statistics - timings.sort((a, b) => a - b); - - const min = timings[0]; - const max = timings[timings.length - 1]; - const avg = timings.reduce((sum, t) => sum + t, 0) / timings.length; - const p50 = timings[Math.floor(timings.length * 0.5)]; - const p95 = timings[Math.floor(timings.length * 0.95)]; - const p99 = timings[Math.floor(timings.length * 0.99)]; - - const targetP95 = parseFloat(process.env.P95_MAX || '5.0'); - - const results = { - timestamp: new Date().toISOString(), - version: '0.9.14', - operations: ITERATIONS, - timings_ms: { - min: Number(min.toFixed(3)), - max: Number(max.toFixed(3)), - avg: Number(avg.toFixed(3)), - p50: Number(p50.toFixed(3)), - p95: Number(p95.toFixed(3)), - p99: Number(p99.toFixed(3)), - }, - target_p95_ms: targetP95, - passes_target: p95 < targetP95, - }; - - console.log('\nPerformance Results:'); - console.log(` Min: ${min.toFixed(3)}ms`); - console.log(` Max: ${max.toFixed(3)}ms`); - console.log(` Avg: ${avg.toFixed(3)}ms`); - console.log(` P50: ${p50.toFixed(3)}ms`); - console.log(` P95: ${p95.toFixed(3)}ms (target: <${targetP95}ms)`); - console.log(` P99: ${p99.toFixed(3)}ms`); - console.log(`\nTarget: ${results.passes_target ? 'PASS' : 'FAIL'}`); - - // Output exact P95 for CI parsing - console.log(`P95: ${p95.toFixed(2)}`); - - // Clear watchdog - clearTimeout(kill); - - // Write results to file - writeFileSync('perf-results.json', JSON.stringify(results, null, 2)); - console.log('\nResults saved to perf-results.json'); - - return results; -} - -// @ts-expect-error import.meta requires ESM module setting -if (import.meta.url === `file://${process.argv[1]}`) { - benchmark().catch((err) => { - console.error(err); - console.log('P95: 999'); // Failure sentinel - process.exit(1); - }); -} - -export { benchmark }; diff --git a/scripts/guard.sh b/scripts/guard.sh index 312a09390..c5d3937bc 100755 --- a/scripts/guard.sh +++ b/scripts/guard.sh @@ -183,8 +183,8 @@ echo "== forbid legacy wire format IDs (v0.10.0 break) ==" # peac.receipt/0.9 -> peac-receipt/0.1 # peac.dispute-bundle/0.1 -> peac-bundle/0.1 # wire/0.9/ -> wire/0.1/ -# Allow in: CHANGELOG, guard script, migration docs, versioning doctrine (historical), deprecated packages, tests pending migration, Go SDK, security / stability / threat docs that must name the legacy identifier to describe its deprecated state, v0.13.0 package-status doc (archive-scheduled rows must name the legacy wire they retire), archive/ (excluded by path filter below). -LEGACY_WIRE_ALLOW='^(CHANGELOG\.md|SECURITY\.md|scripts/(guard\.sh|lint-schemas\.mjs|verify-trust-artifacts\.mjs)|docs/(migration/|MIGRATION_CURRENT\.md|PACKAGE_STATUS\.md|PACKAGE_STATUS_V0\.13\.0_PARITY\.md|specs/VERSIONING\.md|STABILITY-CONTRACT\.md|THREAT_MODEL\.md)|packages/(core|sdk-js)/|packages/(crypto|protocol|schema)/(tests|openapi|src/(index|types|envelope))|tests/(golden|vectors)/|specs/(kernel/README|conformance/fixtures/bundle/invalid)|sdks/go/|examples/x402-node-server/)' +# Allow in: CHANGELOG, guard script, migration docs, versioning doctrine (historical), deprecated packages, tests pending migration, Go SDK, security / stability / threat docs that must name the legacy identifier to describe its deprecated state, v0.13.0 package-status doc (archive-scheduled rows must name the legacy wire they retire), machine-readable surface status that records the archive note, the post-release npm deprecate script (must name the legacy receipt format in its deprecation message), archive/ (excluded by path filter below). +LEGACY_WIRE_ALLOW='^(CHANGELOG\.md|SECURITY\.md|REPO_SURFACE_STATUS\.json|scripts/(guard\.sh|lint-schemas\.mjs|verify-trust-artifacts\.mjs|release/npm-deprecate-v0\.13\.0\.sh)|docs/(migration/|MIGRATION_CURRENT\.md|PACKAGE_STATUS\.md|PACKAGE_STATUS_V0\.13\.0_PARITY\.md|specs/VERSIONING\.md|STABILITY-CONTRACT\.md|THREAT_MODEL\.md)|packages/(core|sdk-js)/|packages/(crypto|protocol|schema)/(tests|openapi|src/(index|types|envelope))|tests/(golden|vectors)/|specs/(kernel/README|conformance/fixtures/bundle/invalid)|sdks/go/|examples/x402-node-server/)' if git grep -nE 'peac\.receipt/0\.9|peac\.dispute-bundle|wire/0\.9/|PEAC-RECEIPT-SCHEMA-v0\.9' -- ':!node_modules' ':!archive/**' \ | grep -vE "$LEGACY_WIRE_ALLOW" | grep .; then echo "FAIL: Found legacy wire format IDs - use normalized 0.1 versions" diff --git a/scripts/publish-manifest.json b/scripts/publish-manifest.json index 614b74c6a..7e647b2ff 100644 --- a/scripts/publish-manifest.json +++ b/scripts/publish-manifest.json @@ -127,7 +127,6 @@ "@peac/receipts", "@peac/pay402", "@peac/server", - "@peac/core", "@peac/net-node", "@peac/adapter-eat", "@peac/adapter-did", diff --git a/scripts/release/npm-deprecate-v0.13.0.sh b/scripts/release/npm-deprecate-v0.13.0.sh index a781f35e7..b0ee3bcf5 100755 --- a/scripts/release/npm-deprecate-v0.13.0.sh +++ b/scripts/release/npm-deprecate-v0.13.0.sh @@ -65,15 +65,15 @@ $NPM deprecate '@peac/disc@0.13.0' \ 'One-release deprecated compatibility package. Prefer @peac/policy-kit for policy-document parsing and validation. See https://peacprotocol.org/docs/migration.' # -# @peac/core: archive coupled with legacy /verify handler rewire. If that -# change ships in the same v0.13.0 release window, add the deprecate line -# below after it merges but before promote-latest. Left commented out as a -# staging marker. +# @peac/core: archived in v0.13.0. Historical 0.9-series verify-only +# implementation; not published at v0.13.0 or later. Historical npm +# versions <=0.9.14 remain installable for verify-only use of historical +# peac.receipt/0.9 records. # -# echo -# echo "-- @peac/core (archive; coupled with legacy /verify handler rewire) --" -# $NPM deprecate '@peac/core@<=0.9.14' \ -# 'ARCHIVED in v0.13.0. Verify-only for historical 0.9-series receipt records. Use @peac/protocol + @peac/schema + @peac/crypto + @peac/kernel. See https://peacprotocol.org/docs/migration.' +echo +echo "-- @peac/core (archived; historical <=0.9.14 kept; not published 0.13.0+) --" +$NPM deprecate '@peac/core@<=0.9.14' \ + 'ARCHIVED in v0.13.0. Verify-only for historical 0.9-series receipt records. Use @peac/protocol + @peac/schema + @peac/crypto + @peac/kernel. See https://peacprotocol.org/docs/migration.' echo echo "=== complete ===" diff --git a/tsconfig.base.json b/tsconfig.base.json index 775f8d566..c5993d737 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -27,7 +27,6 @@ "@peac/disc": ["./packages/discovery/src"], "@peac/server": ["./packages/server/src"], "@peac/cli": ["./packages/cli/src"], - "@peac/core": ["./packages/core/src"], "@peac/pay402": ["./packages/pay402/src"], "@peac/rails-stripe": ["./packages/rails/stripe/src"], "@peac/rails-x402": ["./packages/rails/x402/src"], From f06b659655df83ad43ed1b64769cfee334a77b9b Mon Sep 17 00:00:00 2001 From: jithinraj <7850727+jithinraj@users.noreply.github.com> Date: Fri, 24 Apr 2026 18:49:18 +0530 Subject: [PATCH 06/10] test(api): align verify alias tests and schema pack smoke The new legacy-verify-alias test files used em dashes in their describe labels, which the repo code-punctuation rule forbids in TypeScript sources. Replace them with ASCII colons. Update scripts/pack-install-smoke.sh to import from the current @peac/schema public surface (ProofTypeSchema, AgentProofSchema with the inlined method enum, CommerceExtensionSchema). The old smoke imported ProofMethodSchema and PROOF_METHODS, which were removed in an earlier commit on this branch. --- .../tests/legacy-verify-alias-headers.test.ts | 2 +- .../legacy-verify-alias-pre-sunset.test.ts | 2 +- scripts/pack-install-smoke.sh | 45 +++++++++---------- 3 files changed, 22 insertions(+), 27 deletions(-) diff --git a/apps/api/tests/legacy-verify-alias-headers.test.ts b/apps/api/tests/legacy-verify-alias-headers.test.ts index f2ca029e4..ec777ba8b 100644 --- a/apps/api/tests/legacy-verify-alias-headers.test.ts +++ b/apps/api/tests/legacy-verify-alias-headers.test.ts @@ -55,7 +55,7 @@ function request(path: string, body: unknown) { }); } -describe('legacy /verify alias — deprecation headers', () => { +describe('legacy /verify alias: deprecation headers', () => { let app: ReturnType; beforeAll(() => { diff --git a/apps/api/tests/legacy-verify-alias-pre-sunset.test.ts b/apps/api/tests/legacy-verify-alias-pre-sunset.test.ts index 03238778a..3b1d97c92 100644 --- a/apps/api/tests/legacy-verify-alias-pre-sunset.test.ts +++ b/apps/api/tests/legacy-verify-alias-pre-sunset.test.ts @@ -50,7 +50,7 @@ function request(path: string, body: unknown) { }); } -describe('legacy /verify alias — pre-Sunset responsiveness', () => { +describe('legacy /verify alias: pre-Sunset responsiveness', () => { let app: ReturnType; beforeAll(() => { diff --git a/scripts/pack-install-smoke.sh b/scripts/pack-install-smoke.sh index c4ebae61b..79dedc8dc 100755 --- a/scripts/pack-install-smoke.sh +++ b/scripts/pack-install-smoke.sh @@ -157,38 +157,34 @@ EOF node test.mjs -# Schema package-edge import smoke (deprecated alias stability) +# Schema package-edge import smoke echo "" echo "6. Running @peac/schema package-edge import smoke..." cat > schema-smoke.mjs << 'EOF' -import { - ProofMethodSchema, - PROOF_METHODS, - ProofTypeSchema, - CommerceExtensionSchema, -} from '@peac/schema'; - -// Verify deprecated ProofMethodSchema is importable and functional -if (!ProofMethodSchema) { - console.error('FAIL: ProofMethodSchema not exported from @peac/schema'); - process.exit(1); -} -const parsed = ProofMethodSchema.parse('dpop'); -if (parsed !== 'dpop') { - console.error('FAIL: ProofMethodSchema.parse("dpop") returned:', parsed); +import { ProofTypeSchema, AgentProofSchema, CommerceExtensionSchema } from '@peac/schema'; + +// Verify ProofTypeSchema (canonical trust-root proof model) +if (!ProofTypeSchema) { + console.error('FAIL: ProofTypeSchema not exported from @peac/schema'); process.exit(1); } -// Verify PROOF_METHODS array -if (!Array.isArray(PROOF_METHODS) || PROOF_METHODS.length !== 4) { - console.error('FAIL: PROOF_METHODS not exported or wrong length:', PROOF_METHODS); +// Verify AgentProofSchema accepts the inlined transport-binding method enum. +const proof = AgentProofSchema.parse({ + type: 'did:key', + method: 'dpop', + value: 'eyJ...', +}); +if (proof.method !== 'dpop') { + console.error('FAIL: AgentProofSchema did not preserve method value:', proof); process.exit(1); } - -// Verify ProofTypeSchema (canonical, non-deprecated) -if (!ProofTypeSchema) { - console.error('FAIL: ProofTypeSchema not exported from @peac/schema'); +try { + AgentProofSchema.parse({ type: 'did:key', method: 'not-a-method', value: 'x' }); + console.error('FAIL: AgentProofSchema accepted unknown method value'); process.exit(1); +} catch { + // expected: unknown method values are rejected } // Verify CommerceExtensionSchema accepts optional event field @@ -214,8 +210,7 @@ if (noEvent.event !== undefined) { process.exit(1); } -console.log(' ProofMethodSchema: importable, parse works (deprecated alias stable)'); -console.log(' PROOF_METHODS:', PROOF_METHODS.join(', ')); +console.log(' AgentProofSchema: inlined method enum parsed correctly'); console.log(' CommerceExtensionSchema: event field accepted and absent-event valid'); console.log(' OK: @peac/schema package-edge imports verified'); EOF From ee013dd164658136903c0d50c02e4fa88fe64302 Mon Sep 17 00:00:00 2001 From: jithinraj <7850727+jithinraj@users.noreply.github.com> Date: Fri, 24 Apr 2026 18:58:13 +0530 Subject: [PATCH 07/10] test(smoke): correct AgentProofSchema fixture shape The schema expects a key_id string and rejects the earlier sketch that passed `type` and `value`. Use the real shape (method + key_id + dpop_proof) and drop a stale decision-record reference from the AgentProofSchema JSDoc. --- packages/schema/src/agent-identity.ts | 1 - scripts/pack-install-smoke.sh | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/schema/src/agent-identity.ts b/packages/schema/src/agent-identity.ts index a1574015b..879c27d55 100644 --- a/packages/schema/src/agent-identity.ts +++ b/packages/schema/src/agent-identity.ts @@ -70,7 +70,6 @@ export const AgentProofSchema = z * Transport-binding proof method. The four values below are the * canonical transport-layer bindings recognized by PEAC; they are * semantically distinct from trust-root models (see `ProofTypeSchema`). - * Inlined in v0.13.0 after the DD-185 deprecation horizon closed. */ method: z.enum(['http-message-signature', 'dpop', 'mtls', 'jwk-thumbprint']), diff --git a/scripts/pack-install-smoke.sh b/scripts/pack-install-smoke.sh index 79dedc8dc..2af3642bf 100755 --- a/scripts/pack-install-smoke.sh +++ b/scripts/pack-install-smoke.sh @@ -171,16 +171,16 @@ if (!ProofTypeSchema) { // Verify AgentProofSchema accepts the inlined transport-binding method enum. const proof = AgentProofSchema.parse({ - type: 'did:key', method: 'dpop', - value: 'eyJ...', + key_id: 'test-key-1', + dpop_proof: 'eyJ0eXAiOiJkcG9wK2p3dCJ9..', }); if (proof.method !== 'dpop') { console.error('FAIL: AgentProofSchema did not preserve method value:', proof); process.exit(1); } try { - AgentProofSchema.parse({ type: 'did:key', method: 'not-a-method', value: 'x' }); + AgentProofSchema.parse({ method: 'not-a-method', key_id: 'test-key-1' }); console.error('FAIL: AgentProofSchema accepted unknown method value'); process.exit(1); } catch { From 967d1ebcbaa5847caade14f29f3e3c1cce02611c Mon Sep 17 00:00:00 2001 From: jithinraj <7850727+jithinraj@users.noreply.github.com> Date: Fri, 24 Apr 2026 22:17:19 +0530 Subject: [PATCH 08/10] fix(ci): point nightly crypto smoke at @peac/crypto @peac/crypto emits index.mjs (ESM) and index.cjs (CJS); the previous path referenced a non-existent index.js, so the migrated smoke would have crashed the first time the nightly workflow fired. --- .github/workflows/nightly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 0fe2bac1d..8d45db944 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -138,7 +138,7 @@ jobs: echo "Testing @peac/crypto sign/verify round-trip with Node.js..." node --input-type=module -e " - const m = await import('./packages/crypto/dist/index.js'); + const m = await import('./packages/crypto/dist/index.mjs'); const { sign, verify, generateKeypair } = m; console.log('Testing with Node.js...'); const { privateKey, publicKey } = await generateKeypair(); From af592841dd22e017372a4e6d2f2cfb7e1b202c89 Mon Sep 17 00:00:00 2001 From: jithinraj <7850727+jithinraj@users.noreply.github.com> Date: Fri, 24 Apr 2026 23:32:26 +0530 Subject: [PATCH 09/10] refactor(api): extract createLegacyVerifyAliasHandler helper Move the deprecation-header stamping + /v1/verify delegation into a single exported helper. Production routing (/verify and /api/v1/verify) and the two alias regression tests now go through the same code path, so header set, header values, and delegation target cannot drift between them. Behavior unchanged. Response bytes, header values, and route semantics are identical to the prior inline implementation. --- apps/api/src/index.ts | 29 ++++++++++++------- .../tests/legacy-verify-alias-headers.test.ts | 12 +++----- .../legacy-verify-alias-pre-sunset.test.ts | 7 ++--- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 11c2da3f0..66e8e457c 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -75,6 +75,22 @@ export const LEGACY_VERIFY_DEPRECATION_HEADERS = { Link: '; rel="deprecation"', } as const; +/** + * Build a Hono handler that stamps `LEGACY_VERIFY_DEPRECATION_HEADERS` on + * the response and then delegates to the canonical `/v1/verify` handler. + * Used by both the legacy `POST /verify` route and the `POST /api/v1/verify` + * alias. Production routing and tests both go through this helper so the + * two cannot drift on header set, header values, or delegation target. + */ +export function createLegacyVerifyAliasHandler(verifyV1: ReturnType) { + return (c: Parameters[0]) => { + for (const [key, value] of Object.entries(LEGACY_VERIFY_DEPRECATION_HEADERS)) { + c.header(key, value); + } + return verifyV1(c); + }; +} + // HTTP Server (when run as application) if (import.meta.url === `file://${process.argv[1]}`) { const app = new Hono(); @@ -110,16 +126,9 @@ if (import.meta.url === `file://${process.argv[1]}`) { // Legacy verify endpoint. Kept runtime-reachable through the advertised // Sunset date. The alias delegates in-process to the canonical v1 // handler and stamps deprecation headers on every response. - app.post('/verify', (c) => { - for (const [k, v] of Object.entries(LEGACY_VERIFY_DEPRECATION_HEADERS)) c.header(k, v); - return verifyV1(c); - }); - - // Deprecated alias (Sunset: Nov 1 2026) - app.post('/api/v1/verify', (c) => { - for (const [k, v] of Object.entries(LEGACY_VERIFY_DEPRECATION_HEADERS)) c.header(k, v); - return verifyV1(c); - }); + const legacyVerifyAlias = createLegacyVerifyAliasHandler(verifyV1); + app.post('/verify', legacyVerifyAlias); + app.post('/api/v1/verify', legacyVerifyAlias); // Provisional v1 issue endpoint (BYO-key, disable via PEAC_HOSTED_ISSUE=false) app.post('/v1/issue', createIssueV1Handler()); diff --git a/apps/api/tests/legacy-verify-alias-headers.test.ts b/apps/api/tests/legacy-verify-alias-headers.test.ts index ec777ba8b..06e5cc7d6 100644 --- a/apps/api/tests/legacy-verify-alias-headers.test.ts +++ b/apps/api/tests/legacy-verify-alias-headers.test.ts @@ -22,6 +22,7 @@ import { isProblemError, PROBLEM_MEDIA_TYPE, LEGACY_VERIFY_DEPRECATION_HEADERS, + createLegacyVerifyAliasHandler, } from '../src/index.js'; function buildApp() { @@ -35,15 +36,10 @@ function buildApp() { return c.json({ title: 'Internal Server Error', status: 500 }, 500); }); const verifyV1 = createVerifyV1Handler(); + const legacyVerifyAlias = createLegacyVerifyAliasHandler(verifyV1); app.post('/v1/verify', verifyV1); - app.post('/verify', (c) => { - for (const [k, v] of Object.entries(LEGACY_VERIFY_DEPRECATION_HEADERS)) c.header(k, v); - return verifyV1(c); - }); - app.post('/api/v1/verify', (c) => { - for (const [k, v] of Object.entries(LEGACY_VERIFY_DEPRECATION_HEADERS)) c.header(k, v); - return verifyV1(c); - }); + app.post('/verify', legacyVerifyAlias); + app.post('/api/v1/verify', legacyVerifyAlias); return app; } diff --git a/apps/api/tests/legacy-verify-alias-pre-sunset.test.ts b/apps/api/tests/legacy-verify-alias-pre-sunset.test.ts index 3b1d97c92..4231a7bfb 100644 --- a/apps/api/tests/legacy-verify-alias-pre-sunset.test.ts +++ b/apps/api/tests/legacy-verify-alias-pre-sunset.test.ts @@ -20,7 +20,7 @@ import { resetVerifyV1RateLimit, isProblemError, PROBLEM_MEDIA_TYPE, - LEGACY_VERIFY_DEPRECATION_HEADERS, + createLegacyVerifyAliasHandler, } from '../src/index.js'; function buildApp() { @@ -35,10 +35,7 @@ function buildApp() { }); const verifyV1 = createVerifyV1Handler(); app.post('/v1/verify', verifyV1); - app.post('/verify', (c) => { - for (const [k, v] of Object.entries(LEGACY_VERIFY_DEPRECATION_HEADERS)) c.header(k, v); - return verifyV1(c); - }); + app.post('/verify', createLegacyVerifyAliasHandler(verifyV1)); return app; } From 7109c2521a726f6280881a125450254acc474abe Mon Sep 17 00:00:00 2001 From: jithinraj <7850727+jithinraj@users.noreply.github.com> Date: Sat, 25 Apr 2026 06:07:09 +0530 Subject: [PATCH 10/10] fix(ci): rename nightly crypto smoke step to name @peac/crypto --- .github/workflows/nightly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 8d45db944..c915a8595 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -133,7 +133,7 @@ jobs: - name: Build all packages run: pnpm -w build - - name: Test core crypto functions + - name: Test @peac/crypto sign/verify runtime smoke run: | echo "Testing @peac/crypto sign/verify round-trip with Node.js..."