@peac/schema 0.10.13 → 0.10.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/constants.ts","../src/json.ts","../src/purpose.ts","../src/validators.ts","../src/attestation-receipt.ts","../src/receipt-parser.ts"],"names":["z","httpsUrl","uuidv7","result"],"mappings":"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","file":"receipt-parser.mjs","sourcesContent":["/**\n * Wire format constants - FROZEN\n *\n * These constants are now sourced from @peac/kernel\n * (normative source: specs/kernel/constants.json)\n */\n\nimport { WIRE_TYPE, ALGORITHMS, HEADERS, POLICY, ISSUER_CONFIG, DISCOVERY } from '@peac/kernel';\n\n/**\n * Wire format version - peac-receipt/0.1\n * Normalized in v0.10.0 to peac-<artifact>/<major>.<minor> pattern\n */\nexport const PEAC_WIRE_TYP = WIRE_TYPE;\n\n/**\n * Signature algorithm - FROZEN forever\n */\nexport const PEAC_ALG = ALGORITHMS.default;\n\n/**\n * Canonical header name\n */\nexport const PEAC_RECEIPT_HEADER = HEADERS.receipt;\n\n/**\n * Purpose header names (v0.9.24+)\n */\nexport const PEAC_PURPOSE_HEADER = HEADERS.purpose;\nexport const PEAC_PURPOSE_APPLIED_HEADER = HEADERS.purposeApplied;\nexport const PEAC_PURPOSE_REASON_HEADER = HEADERS.purposeReason;\n\n/**\n * Policy manifest path (/.well-known/peac.txt)\n * @see docs/specs/PEAC-TXT.md\n */\nexport const PEAC_POLICY_PATH = POLICY.manifestPath;\n\n/**\n * Policy manifest fallback path (/peac.txt)\n */\nexport const PEAC_POLICY_FALLBACK_PATH = POLICY.fallbackPath;\n\n/**\n * Maximum policy manifest size\n */\nexport const PEAC_POLICY_MAX_BYTES = POLICY.maxBytes;\n\n/**\n * Issuer configuration path (/.well-known/peac-issuer.json)\n * @see docs/specs/PEAC-ISSUER.md\n */\nexport const PEAC_ISSUER_CONFIG_PATH = ISSUER_CONFIG.configPath;\n\n/**\n * Issuer configuration version\n */\nexport const PEAC_ISSUER_CONFIG_VERSION = ISSUER_CONFIG.configVersion;\n\n/**\n * Maximum issuer configuration size\n */\nexport const PEAC_ISSUER_CONFIG_MAX_BYTES = ISSUER_CONFIG.maxBytes;\n\n/**\n * @deprecated Use PEAC_POLICY_PATH instead. Will be removed in v1.0.\n */\nexport const PEAC_DISCOVERY_PATH = DISCOVERY.manifestPath;\n\n/**\n * @deprecated Use PEAC_POLICY_MAX_BYTES instead. Will be removed in v1.0.\n */\nexport const PEAC_DISCOVERY_MAX_BYTES = 2000 as const;\n\n/**\n * JSON Schema URL for PEAC receipt wire format v0.1\n *\n * This is the canonical $id for the root schema.\n * Use for schema references and cross-implementation validation.\n *\n * @since v0.10.0\n */\nexport const PEAC_RECEIPT_SCHEMA_URL =\n 'https://www.peacprotocol.org/schemas/wire/0.1/peac-receipt.0.1.schema.json' as const;\n","/**\n * JSON-safe validation schemas\n *\n * Provides Zod schemas that guarantee JSON roundtrip safety:\n * - Rejects NaN, Infinity, -Infinity (not valid JSON numbers)\n * - Rejects undefined (dropped by JSON.stringify)\n * - Rejects non-plain objects (Date, Map, Set, class instances)\n * - Rejects functions, symbols, bigints\n */\n\nimport { z } from 'zod';\nimport type { JsonValue, JsonObject, JsonArray } from '@peac/kernel';\n\n/**\n * Check if value is a plain object (not Date, Map, Set, class instance, etc.)\n *\n * A plain object has prototype of Object.prototype or null.\n * This rejects Date, Map, Set, Array, and class instances even when\n * they have zero enumerable properties (which would pass z.record()).\n */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== 'object') {\n return false;\n }\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * JSON number schema - rejects NaN and Infinity\n *\n * JSON.stringify(NaN) === \"null\" and JSON.stringify(Infinity) === \"null\"\n * which silently corrupts data. We reject these at validation time.\n */\nconst JsonNumberSchema = z.number().finite();\n\n/**\n * JSON primitive schema - string, finite number, boolean, null\n */\nexport const JsonPrimitiveSchema = z.union([z.string(), JsonNumberSchema, z.boolean(), z.null()]);\n\n/**\n * Plain object schema (internal) - validates object is plain before recursive validation\n */\nconst PlainObjectSchema = z.unknown().refine(isPlainObject, {\n message: 'Expected plain object, received non-plain object (Date, Map, Set, or class instance)',\n});\n\n/**\n * JSON value schema - recursive type for any valid JSON value\n *\n * Validates:\n * - Primitives: string, finite number, boolean, null\n * - Arrays: containing valid JSON values\n * - Objects: plain objects with string keys and valid JSON values\n *\n * Rejects:\n * - undefined (dropped by JSON.stringify)\n * - NaN, Infinity, -Infinity (become null in JSON)\n * - BigInt (throws in JSON.stringify)\n * - Date (becomes ISO string - implicit conversion)\n * - Map, Set (become {} in JSON)\n * - Functions, Symbols (dropped by JSON.stringify)\n * - Class instances (prototype chain lost)\n */\nexport const JsonValueSchema: z.ZodType<JsonValue> = z.lazy(() =>\n z.union([\n JsonPrimitiveSchema,\n z.array(JsonValueSchema),\n // Plain object check then record validation\n PlainObjectSchema.transform((obj) => obj as Record<string, unknown>).pipe(\n z.record(JsonValueSchema)\n ),\n ])\n) as z.ZodType<JsonValue>;\n\n/**\n * JSON object schema - plain object with string keys and JSON values\n *\n * Rejects non-plain objects (Date, Map, Set, class instances).\n */\nexport const JsonObjectSchema: z.ZodType<JsonObject> = PlainObjectSchema.transform(\n (obj) => obj as Record<string, unknown>\n).pipe(z.record(JsonValueSchema)) as z.ZodType<JsonObject>;\n\n/**\n * JSON array schema - array of JSON values\n */\nexport const JsonArraySchema: z.ZodType<JsonArray> = z.array(JsonValueSchema);\n\n/**\n * Default limits for JSON evidence validation\n *\n * These are conservative defaults to prevent DoS attacks via deeply nested\n * or excessively large JSON structures.\n */\nexport const JSON_EVIDENCE_LIMITS = {\n /** Maximum nesting depth (default: 32) */\n maxDepth: 32,\n /** Maximum array length (default: 10,000) */\n maxArrayLength: 10_000,\n /** Maximum object keys (default: 1,000) */\n maxObjectKeys: 1_000,\n /** Maximum string length in bytes (default: 65,536 = 64KB) */\n maxStringLength: 65_536,\n /** Maximum total nodes to visit (default: 100,000) */\n maxTotalNodes: 100_000,\n} as const;\n\n/**\n * Limits for JSON evidence validation\n *\n * @internal - Not part of public API. Use validateEvidence() with defaults.\n *\n * For testing only: import via UNSAFE_JsonEvidenceLimits\n */\nexport interface JsonEvidenceLimits {\n maxDepth?: number;\n maxArrayLength?: number;\n maxObjectKeys?: number;\n maxStringLength?: number;\n maxTotalNodes?: number;\n}\n\n/**\n * Result of JSON safety validation\n */\nexport type JsonSafetyResult =\n | { ok: true }\n | { ok: false; error: string; path: (string | number)[] };\n\n/**\n * Stack entry type for iterative traversal\n *\n * - \"enter\": entering an object/array, need to validate and push children\n * - \"exit\": exiting an object/array, remove from current path (for cycle detection)\n */\ntype StackEntry =\n | { type: 'enter'; value: unknown; path: (string | number)[]; depth: number }\n | { type: 'exit'; obj: object };\n\n/**\n * Iterative JSON safety validator\n *\n * Validates that a value is JSON-safe without using recursion, preventing\n * stack overflow on deeply nested structures. Uses an explicit stack for\n * traversal with entry/exit markers for correct cycle detection.\n *\n * Cycle Detection:\n * Uses a path-based approach where only objects on the current traversal\n * path are tracked. This correctly allows diamond structures (same object\n * referenced from multiple paths) while rejecting actual cycles (object\n * references itself through its descendants).\n *\n * Rejects:\n * - Cycles (object references itself directly or indirectly)\n * - Non-plain objects (Date, Map, Set, class instances)\n * - Non-finite numbers (NaN, Infinity, -Infinity)\n * - undefined, BigInt, functions, symbols\n * - Structures exceeding depth/size limits\n *\n * Allows:\n * - Diamond structures (same object referenced from multiple paths)\n *\n * @param value - Value to validate\n * @param limits - Optional limits (defaults to JSON_EVIDENCE_LIMITS)\n * @returns Result indicating success or failure with error details\n */\nexport function assertJsonSafeIterative(\n value: unknown,\n limits: JsonEvidenceLimits = {}\n): JsonSafetyResult {\n const maxDepth = limits.maxDepth ?? JSON_EVIDENCE_LIMITS.maxDepth;\n const maxArrayLength = limits.maxArrayLength ?? JSON_EVIDENCE_LIMITS.maxArrayLength;\n const maxObjectKeys = limits.maxObjectKeys ?? JSON_EVIDENCE_LIMITS.maxObjectKeys;\n const maxStringLength = limits.maxStringLength ?? JSON_EVIDENCE_LIMITS.maxStringLength;\n const maxTotalNodes = limits.maxTotalNodes ?? JSON_EVIDENCE_LIMITS.maxTotalNodes;\n\n // Track objects on the current traversal path for cycle detection.\n // An object appearing twice on the same path is a cycle.\n // An object appearing on different paths (diamond) is NOT a cycle.\n const pathSet = new WeakSet<object>();\n\n // Track total nodes visited for DoS protection\n let nodeCount = 0;\n\n // Stack with entry/exit markers for path tracking\n const stack: StackEntry[] = [{ type: 'enter', value, path: [], depth: 0 }];\n\n while (stack.length > 0) {\n const entry = stack.pop()!;\n\n // Handle exit marker - remove object from current path\n if (entry.type === 'exit') {\n pathSet.delete(entry.obj);\n continue;\n }\n\n const { value: current, path, depth } = entry;\n\n // Check total node limit\n nodeCount++;\n if (nodeCount > maxTotalNodes) {\n return {\n ok: false,\n error: `Maximum total nodes exceeded (limit: ${maxTotalNodes})`,\n path,\n };\n }\n\n // Check depth limit\n if (depth > maxDepth) {\n return {\n ok: false,\n error: `Maximum depth exceeded (limit: ${maxDepth})`,\n path,\n };\n }\n\n // Handle null (valid JSON)\n if (current === null) {\n continue;\n }\n\n // Handle primitives\n const type = typeof current;\n\n if (type === 'string') {\n if ((current as string).length > maxStringLength) {\n return {\n ok: false,\n error: `String exceeds maximum length (limit: ${maxStringLength})`,\n path,\n };\n }\n continue;\n }\n\n if (type === 'number') {\n if (!Number.isFinite(current as number)) {\n return {\n ok: false,\n error: `Non-finite number: ${current}`,\n path,\n };\n }\n continue;\n }\n\n if (type === 'boolean') {\n continue;\n }\n\n // Reject non-JSON types\n if (type === 'undefined') {\n return { ok: false, error: 'undefined is not valid JSON', path };\n }\n\n if (type === 'bigint') {\n return { ok: false, error: 'BigInt is not valid JSON', path };\n }\n\n if (type === 'function') {\n return { ok: false, error: 'Function is not valid JSON', path };\n }\n\n if (type === 'symbol') {\n return { ok: false, error: 'Symbol is not valid JSON', path };\n }\n\n // Handle objects (arrays and plain objects)\n if (type === 'object') {\n const obj = current as object;\n\n // Cycle detection - check if object is already on the current path\n // (not just visited anywhere, but specifically an ancestor)\n if (pathSet.has(obj)) {\n return { ok: false, error: 'Cycle detected in object graph', path };\n }\n\n // Add to current path and push exit marker to remove when done\n pathSet.add(obj);\n stack.push({ type: 'exit', obj });\n\n // Handle arrays\n if (Array.isArray(obj)) {\n if (obj.length > maxArrayLength) {\n return {\n ok: false,\n error: `Array exceeds maximum length (limit: ${maxArrayLength})`,\n path,\n };\n }\n // Push array elements to stack in reverse order for correct traversal\n for (let i = obj.length - 1; i >= 0; i--) {\n stack.push({ type: 'enter', value: obj[i], path: [...path, i], depth: depth + 1 });\n }\n continue;\n }\n\n // Check for non-plain objects (Date, Map, Set, class instances, etc.)\n const proto = Object.getPrototypeOf(obj);\n if (proto !== Object.prototype && proto !== null) {\n const constructorName = obj.constructor?.name ?? 'unknown';\n return {\n ok: false,\n error: `Non-plain object (${constructorName}) is not valid JSON`,\n path,\n };\n }\n\n // Handle plain objects\n const keys = Object.keys(obj);\n if (keys.length > maxObjectKeys) {\n return {\n ok: false,\n error: `Object exceeds maximum key count (limit: ${maxObjectKeys})`,\n path,\n };\n }\n // Push object values to stack\n for (let i = keys.length - 1; i >= 0; i--) {\n const key = keys[i];\n stack.push({\n type: 'enter',\n value: (obj as Record<string, unknown>)[key],\n path: [...path, key],\n depth: depth + 1,\n });\n }\n continue;\n }\n\n // Shouldn't reach here, but reject unknown types\n return { ok: false, error: `Unknown type: ${type}`, path };\n }\n\n return { ok: true };\n}\n","/**\n * PEAC Purpose Types (v0.9.24+)\n *\n * Purpose type hierarchy for forward-compatible purpose handling:\n * - PurposeToken: Wire format (string) - preserves unknown tokens\n * - CanonicalPurpose: PEAC's normative vocabulary - enforcement semantics\n * - PurposeReason: Audit spine for enforcement decisions\n *\n * @see specs/kernel/constants.json for canonical values\n */\n\n/**\n * PurposeToken - Wire format string with validation grammar\n *\n * Allows unknown tokens for forward compatibility. Any valid token\n * that matches the grammar is accepted and preserved.\n *\n * Grammar: lowercase, max 64 chars, [a-z0-9_-] + optional vendor prefix (vendor:token)\n * Hyphens allowed for interop with external systems (Cloudflare, IETF AIPREF, etc.)\n *\n * Examples: \"train\", \"search\", \"user_action\", \"user-action\", \"cf:ai_crawler\", \"cf:ai-crawler\"\n */\nexport type PurposeToken = string;\n\n/**\n * CanonicalPurpose - PEAC's normative vocabulary\n *\n * These are the only tokens PEAC enforces semantics for.\n * Matches specs/kernel/constants.json purpose.canonical_tokens.\n *\n * - train: Model training data collection\n * - search: Traditional search indexing\n * - user_action: Agent acting on user behalf (v0.9.24+)\n * - inference: Runtime inference / RAG\n * - index: Content indexing (store)\n */\nexport type CanonicalPurpose = 'train' | 'search' | 'user_action' | 'inference' | 'index';\n\n/**\n * Internal-only purpose value (never valid on wire)\n *\n * Applied when PEAC-Purpose header is missing or empty.\n * Explicit \"undeclared\" in request -> 400 Bad Request.\n */\nexport type InternalPurpose = 'undeclared';\n\n/**\n * PurposeReason - Audit spine for enforcement decisions\n *\n * Captures WHY a purpose was enforced differently than declared.\n * Matches specs/kernel/constants.json purpose.reason_values.\n */\nexport type PurposeReason =\n | 'allowed' // Purpose permitted as declared (happy path)\n | 'constrained' // Allowed with rate limits applied\n | 'denied' // Purpose rejected by policy\n | 'downgraded' // More restrictive purpose applied\n | 'undeclared_default' // No purpose declared, default applied\n | 'unknown_preserved'; // Unknown purpose token, preserved but flagged\n\n/**\n * Legacy purpose tokens from pre-v0.9.24\n *\n * These are mapped to CanonicalPurpose via mapLegacyToCanonical().\n * Retained for backward compatibility with existing ControlPurpose usage.\n */\nexport type LegacyPurpose = 'crawl' | 'ai_input' | 'ai_index';\n\n// ============================================================================\n// Validation Constants\n// ============================================================================\n\n/**\n * Grammar validation for PurposeToken\n *\n * Pattern: lowercase letter, optionally followed by alphanumeric/underscore/hyphen\n * characters that MUST end with a letter or digit (no trailing separators).\n * Optional vendor prefix separated by colon follows the same rules.\n *\n * Hyphens are allowed for interoperability with external systems (Cloudflare,\n * IETF AIPREF, etc.) that use hyphenated tokens like \"user-action\" or \"train-ai\".\n *\n * Valid: \"train\", \"user_action\", \"user-action\", \"cf:ai_crawler\", \"cf:ai-crawler\", \"a\", \"a1\"\n * Invalid: \"Train\", \"123abc\", \"\", \"-train\", \"train-\", \"train_\", \"cf:ai-\", \"cf:-ai\"\n */\nexport const PURPOSE_TOKEN_REGEX =\n /^[a-z](?:[a-z0-9_-]*[a-z0-9])?(?::[a-z](?:[a-z0-9_-]*[a-z0-9])?)?$/;\n\n/** Maximum length for a purpose token */\nexport const MAX_PURPOSE_TOKEN_LENGTH = 64;\n\n/** Maximum number of purpose tokens per request (RECOMMENDED, not MUST) */\nexport const MAX_PURPOSE_TOKENS_PER_REQUEST = 10;\n\n/** Canonical purpose tokens (from constants.json) */\nexport const CANONICAL_PURPOSES: readonly CanonicalPurpose[] = [\n 'train',\n 'search',\n 'user_action',\n 'inference',\n 'index',\n] as const;\n\n/** Purpose reason values (from constants.json) */\nexport const PURPOSE_REASONS: readonly PurposeReason[] = [\n 'allowed',\n 'constrained',\n 'denied',\n 'downgraded',\n 'undeclared_default',\n 'unknown_preserved',\n] as const;\n\n/** Internal-only purpose value */\nexport const INTERNAL_PURPOSE_UNDECLARED: InternalPurpose = 'undeclared';\n\n// ============================================================================\n// Validation Functions\n// ============================================================================\n\n/**\n * Check if a string is a valid PurposeToken\n *\n * Validates against the purpose token grammar:\n * - Lowercase letters, digits, underscores, hyphens\n * - Optional vendor prefix with colon\n * - Max 64 characters\n * - Must start with lowercase letter\n *\n * @param token - String to validate\n * @returns true if valid PurposeToken\n */\nexport function isValidPurposeToken(token: string): token is PurposeToken {\n if (typeof token !== 'string') return false;\n if (token.length === 0 || token.length > MAX_PURPOSE_TOKEN_LENGTH) return false;\n return PURPOSE_TOKEN_REGEX.test(token);\n}\n\n/**\n * Check if a PurposeToken is a CanonicalPurpose\n *\n * @param token - Token to check\n * @returns true if token is in canonical vocabulary\n */\nexport function isCanonicalPurpose(token: string): token is CanonicalPurpose {\n return (CANONICAL_PURPOSES as readonly string[]).includes(token);\n}\n\n/**\n * Check if a PurposeToken is a LegacyPurpose\n *\n * @param token - Token to check\n * @returns true if token is a legacy purpose\n */\nexport function isLegacyPurpose(token: string): token is LegacyPurpose {\n return token === 'crawl' || token === 'ai_input' || token === 'ai_index';\n}\n\n/**\n * Check if a string is a valid PurposeReason\n *\n * @param reason - String to check\n * @returns true if valid PurposeReason\n */\nexport function isValidPurposeReason(reason: string): reason is PurposeReason {\n return (PURPOSE_REASONS as readonly string[]).includes(reason);\n}\n\n/**\n * Check if a purpose token is the internal-only \"undeclared\" value\n *\n * Used to reject explicit \"undeclared\" on wire (400 Bad Request).\n *\n * @param token - Token to check\n * @returns true if token is \"undeclared\"\n */\nexport function isUndeclaredPurpose(token: string): boolean {\n return token === INTERNAL_PURPOSE_UNDECLARED;\n}\n\n// ============================================================================\n// Normalization Functions\n// ============================================================================\n\n/**\n * Normalize a purpose token\n *\n * Applies normalization rules:\n * - Trim whitespace\n * - Lowercase\n *\n * @param token - Raw token from header\n * @returns Normalized token\n */\nexport function normalizePurposeToken(token: string): string {\n return token.trim().toLowerCase();\n}\n\n/**\n * Parse PEAC-Purpose header value into array of tokens\n *\n * Applies parsing rules:\n * - Split on commas\n * - Trim optional whitespace (OWS) around tokens\n * - Lowercase all tokens\n * - Drop empty tokens\n * - Deduplicate\n * - Preserve input order\n *\n * @param headerValue - Raw PEAC-Purpose header value\n * @returns Array of normalized PurposeToken values\n */\nexport function parsePurposeHeader(headerValue: string): PurposeToken[] {\n if (!headerValue || typeof headerValue !== 'string') {\n return [];\n }\n\n const seen = new Set<string>();\n const tokens: PurposeToken[] = [];\n\n for (const part of headerValue.split(',')) {\n const normalized = normalizePurposeToken(part);\n if (normalized.length > 0 && !seen.has(normalized)) {\n seen.add(normalized);\n tokens.push(normalized);\n }\n }\n\n return tokens;\n}\n\n/**\n * Validate parsed purpose tokens\n *\n * Returns validation result with:\n * - valid: All tokens pass grammar validation\n * - tokens: All normalized tokens (including invalid ones)\n * - invalidTokens: Tokens that failed grammar validation\n * - undeclaredPresent: true if explicit \"undeclared\" was found (should reject)\n *\n * @param tokens - Array of parsed tokens\n * @returns Validation result\n */\nexport interface PurposeValidationResult {\n valid: boolean;\n tokens: PurposeToken[];\n invalidTokens: string[];\n undeclaredPresent: boolean;\n}\n\nexport function validatePurposeTokens(tokens: PurposeToken[]): PurposeValidationResult {\n const invalidTokens: string[] = [];\n let undeclaredPresent = false;\n\n for (const token of tokens) {\n if (isUndeclaredPurpose(token)) {\n undeclaredPresent = true;\n }\n if (!isValidPurposeToken(token)) {\n invalidTokens.push(token);\n }\n }\n\n return {\n valid: invalidTokens.length === 0 && !undeclaredPresent,\n tokens,\n invalidTokens,\n undeclaredPresent,\n };\n}\n\n/**\n * Derive known canonical purposes from declared tokens\n *\n * Filters purpose_declared to get only canonical purposes.\n * This is a helper derivation, NOT stored on wire.\n *\n * @param declared - Array of declared PurposeTokens\n * @returns Array of CanonicalPurpose tokens\n */\nexport function deriveKnownPurposes(declared: PurposeToken[]): CanonicalPurpose[] {\n return declared.filter(isCanonicalPurpose);\n}\n\n// ============================================================================\n// Legacy Mapping\n// ============================================================================\n\n/**\n * Legacy purpose to canonical mapping\n */\nconst LEGACY_TO_CANONICAL: Record<LegacyPurpose, CanonicalPurpose> = {\n crawl: 'index', // Crawl implies indexing\n ai_input: 'inference', // RAG/grounding -> inference context\n ai_index: 'index', // AI-powered indexing -> index\n};\n\n/**\n * Map legacy purpose to canonical purpose\n *\n * Used for backward compatibility with pre-v0.9.24 ControlPurpose values.\n *\n * @param legacy - Legacy purpose token\n * @returns Mapping result with canonical purpose and audit note\n */\nexport interface LegacyMappingResult {\n canonical: CanonicalPurpose;\n mapping_note: string;\n}\n\nexport function mapLegacyToCanonical(legacy: LegacyPurpose): LegacyMappingResult {\n const canonical = LEGACY_TO_CANONICAL[legacy];\n return {\n canonical,\n mapping_note: `Mapped legacy '${legacy}' to canonical '${canonical}'`,\n };\n}\n\n/**\n * Normalize any purpose token (canonical, legacy, or unknown)\n *\n * Returns the canonical form if known, otherwise preserves the token.\n *\n * @param token - Any valid PurposeToken\n * @returns Canonical purpose if mapped, otherwise original token\n */\nexport function normalizeToCanonicalOrPreserve(\n token: PurposeToken\n):\n | { purpose: CanonicalPurpose; mapped: false }\n | { purpose: PurposeToken; mapped: true; from: LegacyPurpose }\n | { purpose: PurposeToken; mapped: false; unknown: true } {\n if (isCanonicalPurpose(token)) {\n return { purpose: token, mapped: false };\n }\n if (isLegacyPurpose(token)) {\n return { purpose: LEGACY_TO_CANONICAL[token], mapped: true, from: token };\n }\n return { purpose: token, mapped: false, unknown: true };\n}\n\n// ============================================================================\n// Purpose Reason Determination (v0.9.24+)\n// ============================================================================\n\n/**\n * Decision type for purpose reason determination\n *\n * Maps to policy decisions that affect purpose enforcement.\n */\nexport type PurposeDecision = 'allowed' | 'constrained' | 'denied' | 'downgraded';\n\n/**\n * Context for determining purpose reason\n */\nexport interface PurposeReasonContext {\n /**\n * Whether purposes were declared (PEAC-Purpose header present and non-empty).\n * If false, reason will be 'undeclared_default'.\n */\n declared: boolean;\n\n /**\n * Whether any unknown (non-canonical) tokens are present in declared purposes.\n * If true and declared is true, reason will be 'unknown_preserved'.\n */\n hasUnknownTokens: boolean;\n\n /**\n * The policy decision (only used if declared and no unknown tokens).\n * Defaults to 'allowed' if not provided.\n */\n decision?: PurposeDecision;\n}\n\n/**\n * Determine the appropriate PurposeReason based on context\n *\n * This helper implements the decision logic for the audit spine:\n * 1. If no purposes declared -> 'undeclared_default'\n * 2. If unknown tokens present -> 'unknown_preserved'\n * 3. Otherwise -> maps to policy decision\n *\n * @param context - Context for determination\n * @returns The appropriate PurposeReason for the audit spine\n *\n * @example\n * ```typescript\n * // Missing PEAC-Purpose header\n * determinePurposeReason({ declared: false, hasUnknownTokens: false });\n * // => 'undeclared_default'\n *\n * // Has vendor-prefixed tokens\n * determinePurposeReason({ declared: true, hasUnknownTokens: true, decision: 'allowed' });\n * // => 'unknown_preserved'\n *\n * // All canonical tokens, allowed by policy\n * determinePurposeReason({ declared: true, hasUnknownTokens: false, decision: 'allowed' });\n * // => 'allowed'\n * ```\n */\nexport function determinePurposeReason(context: PurposeReasonContext): PurposeReason {\n // Priority 1: No purposes declared\n if (!context.declared) {\n return 'undeclared_default';\n }\n\n // Priority 2: Unknown tokens present (preserved for forward-compat)\n if (context.hasUnknownTokens) {\n return 'unknown_preserved';\n }\n\n // Priority 3: Map policy decision to reason\n const decision = context.decision ?? 'allowed';\n switch (decision) {\n case 'allowed':\n return 'allowed';\n case 'constrained':\n return 'constrained';\n case 'denied':\n return 'denied';\n case 'downgraded':\n return 'downgraded';\n default:\n return 'allowed';\n }\n}\n\n/**\n * Check if any tokens in an array are unknown (non-canonical)\n *\n * @param tokens - Array of purpose tokens\n * @returns true if any token is not a canonical purpose\n */\nexport function hasUnknownPurposeTokens(tokens: PurposeToken[]): boolean {\n return tokens.some((token) => !isCanonicalPurpose(token));\n}\n","/**\n * Zod validators for PEAC protocol types\n */\nimport { z } from 'zod';\nimport { PEAC_WIRE_TYP, PEAC_ALG } from './constants';\nimport {\n JsonValueSchema,\n JsonObjectSchema,\n assertJsonSafeIterative,\n type JsonEvidenceLimits,\n} from './json';\nimport { createEvidenceNotJsonError, type PEACError } from './errors';\nimport { PURPOSE_TOKEN_REGEX, MAX_PURPOSE_TOKEN_LENGTH } from './purpose';\n\nconst httpsUrl = z\n .string()\n .url()\n .refine((u) => u.startsWith('https://'), 'must be https://');\nconst iso4217 = z.string().regex(/^[A-Z]{3}$/);\nconst uuidv7 = z\n .string()\n .regex(/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);\n\nexport const NormalizedPayment = z\n .object({\n rail: z.string().min(1),\n reference: z.string().min(1),\n amount: z.number().int().nonnegative(),\n currency: iso4217,\n asset: z.string().optional(),\n env: z.string().optional(),\n evidence: JsonValueSchema.optional(),\n metadata: JsonObjectSchema.optional(),\n })\n .strict();\n\nexport const Subject = z.object({ uri: httpsUrl }).strict();\n\nexport const AIPREFSnapshot = z\n .object({\n url: httpsUrl,\n hash: z.string().min(8),\n })\n .strict();\n\n// Note: Extensions uses a forward reference pattern since ControlBlockSchema\n// is defined after this. We use catchall for now and validate control separately.\nexport const Extensions = z\n .object({\n aipref_snapshot: AIPREFSnapshot.optional(),\n // control block validated via ControlBlockSchema when present\n })\n .catchall(z.unknown());\n\nexport const JWSHeader = z\n .object({\n typ: z.literal(PEAC_WIRE_TYP),\n alg: z.literal(PEAC_ALG),\n kid: z.string().min(8),\n })\n .strict();\n\n// Forward-declare purpose validators used in ReceiptClaims\n// Full definitions are below\nconst CanonicalPurposeValues = ['train', 'search', 'user_action', 'inference', 'index'] as const;\nconst PurposeReasonValues = [\n 'allowed',\n 'constrained',\n 'denied',\n 'downgraded',\n 'undeclared_default',\n 'unknown_preserved',\n] as const;\n\nexport const ReceiptClaimsSchema = z\n .object({\n iss: httpsUrl,\n aud: httpsUrl,\n iat: z.number().int().nonnegative(),\n exp: z.number().int().optional(),\n rid: uuidv7,\n amt: z.number().int().nonnegative(),\n cur: iso4217,\n payment: NormalizedPayment,\n subject: Subject.optional(),\n ext: Extensions.optional(),\n // Purpose claims (v0.9.24+)\n // purpose_declared: string[] - preserves unknown tokens for forward-compat\n purpose_declared: z.array(z.string()).optional(),\n // purpose_enforced: CanonicalPurpose - must be one with enforcement semantics\n purpose_enforced: z.enum(CanonicalPurposeValues).optional(),\n // purpose_reason: PurposeReason - audit spine for enforcement decisions\n purpose_reason: z.enum(PurposeReasonValues).optional(),\n })\n .strict();\n\n/**\n * Schema-derived receipt claims type (v0.9.30+)\n *\n * This is the canonical type for receipt claims - derived from the Zod schema.\n * Use this type instead of manually-defined interfaces to ensure type/schema parity.\n */\nexport type ReceiptClaimsType = z.infer<typeof ReceiptClaimsSchema>;\n\n/**\n * @deprecated Use ReceiptClaimsSchema instead. Renamed in v0.9.30.\n */\nexport const ReceiptClaims = ReceiptClaimsSchema;\n\nexport const VerifyRequest = z\n .object({\n receipt_jws: z.string().min(16),\n })\n .strict();\n\n// -----------------------------------------------------------------------------\n// Control Abstraction Layer (CAL) Validators (v0.9.16+)\n// -----------------------------------------------------------------------------\n\n/**\n * Control purpose - what the access is for\n *\n * v0.9.17+: Added ai_input, search for RSL alignment\n * v0.9.18+: Added ai_index (RSL 1.0 canonical token). Removed ai_search.\n * v0.9.24+: Added user_action for agent-on-behalf-of-user scenarios.\n *\n * @see https://rslstandard.org/rsl for RSL 1.0 specification\n */\nexport const ControlPurposeSchema = z.enum([\n 'crawl',\n 'index',\n 'train',\n 'inference',\n 'user_action',\n 'ai_input',\n 'ai_index',\n 'search',\n]);\n\n/**\n * Control licensing mode - how access is licensed\n */\nexport const ControlLicensingModeSchema = z.enum([\n 'subscription',\n 'pay_per_crawl',\n 'pay_per_inference',\n]);\n\n/**\n * Control decision type\n */\nexport const ControlDecisionSchema = z.enum(['allow', 'deny', 'review']);\n\n/**\n * Single control step in governance chain\n */\nexport const ControlStepSchema = z.object({\n engine: z.string().min(1),\n version: z.string().optional(),\n policy_id: z.string().optional(),\n result: ControlDecisionSchema,\n reason: z.string().optional(),\n purpose: ControlPurposeSchema.optional(),\n licensing_mode: ControlLicensingModeSchema.optional(),\n scope: z.union([z.string(), z.array(z.string())]).optional(),\n limits_snapshot: z.unknown().optional(),\n evidence_ref: z.string().optional(),\n});\n\n/**\n * Composable control block - multi-party governance\n */\nexport const ControlBlockSchema = z\n .object({\n chain: z.array(ControlStepSchema).min(1),\n decision: ControlDecisionSchema,\n combinator: z.literal('any_can_veto').optional(),\n })\n .refine(\n (data) => {\n // Validate decision consistency with chain\n const hasAnyDeny = data.chain.some((step) => step.result === 'deny');\n const allAllow = data.chain.every((step) => step.result === 'allow');\n const hasReview = data.chain.some((step) => step.result === 'review');\n\n if (hasAnyDeny && data.decision !== 'deny') {\n return false;\n }\n if (allAllow && data.decision !== 'allow') {\n return false;\n }\n // If has review but no deny, decision can be review or allow\n if (hasReview && !hasAnyDeny && data.decision === 'deny') {\n return false;\n }\n return true;\n },\n {\n message: 'Control block decision must be consistent with chain results',\n }\n );\n\n// -----------------------------------------------------------------------------\n// Purpose Type Validators (v0.9.24+)\n// -----------------------------------------------------------------------------\n\n/**\n * Purpose token validator\n *\n * PurposeToken is a string that matches the purpose grammar:\n * - Lowercase letters, digits, underscores\n * - Optional vendor prefix with colon (e.g., \"cf:ai_crawler\")\n * - Max 64 characters\n *\n * Uses string type (not enum) to preserve unknown tokens for forward-compat.\n */\nexport const PurposeTokenSchema = z\n .string()\n .min(1)\n .max(MAX_PURPOSE_TOKEN_LENGTH)\n .refine((token) => PURPOSE_TOKEN_REGEX.test(token), {\n message:\n 'Invalid purpose token format. Must be lowercase, alphanumeric with underscores, optional vendor prefix.',\n });\n\n/**\n * Canonical purpose validator\n *\n * CanonicalPurpose is one of PEAC's normative purpose tokens.\n * Only these tokens have enforcement semantics.\n */\nexport const CanonicalPurposeSchema = z.enum([\n 'train',\n 'search',\n 'user_action',\n 'inference',\n 'index',\n]);\n\n/**\n * Purpose reason validator\n *\n * PurposeReason is the audit spine explaining enforcement decisions.\n */\nexport const PurposeReasonSchema = z.enum([\n 'allowed',\n 'constrained',\n 'denied',\n 'downgraded',\n 'undeclared_default',\n 'unknown_preserved',\n]);\n\n// -----------------------------------------------------------------------------\n// Payment Evidence Validators (v0.9.16+)\n// -----------------------------------------------------------------------------\n\n/**\n * Payment split schema\n *\n * Invariants:\n * - party is required (non-empty string)\n * - amount if present must be >= 0\n * - share if present must be in [0,1]\n * - At least one of amount or share must be specified\n */\nexport const PaymentSplitSchema = z\n .object({\n party: z.string().min(1),\n amount: z.number().int().nonnegative().optional(),\n currency: iso4217.optional(),\n share: z.number().min(0).max(1).optional(),\n rail: z.string().min(1).optional(),\n account_ref: z.string().min(1).optional(),\n metadata: JsonObjectSchema.optional(),\n })\n .strict()\n .refine((data) => data.amount !== undefined || data.share !== undefined, {\n message: 'At least one of amount or share must be specified',\n });\n\n/**\n * Payment routing mode schema (rail-agnostic)\n *\n * Describes how the payment is routed between payer, aggregator, and merchant.\n * This is a generic hint - specific rails populate it from their native formats.\n *\n * Values:\n * - \"direct\": Direct payment to merchant (no intermediary)\n * - \"callback\": Routed via callback URL / payment service\n * - \"role\": Role-based routing (e.g., \"publisher\", \"platform\")\n *\n * Examples of producers:\n * - x402 v2 `payTo.mode` -> routing\n * - Stripe Connect `destination` -> routing = 'direct' or 'callback'\n * - UPI `pa` (payee address) -> routing = 'direct'\n */\nexport const PaymentRoutingSchema = z.enum(['direct', 'callback', 'role']);\n\n/**\n * Payment evidence schema\n *\n * Full schema for PaymentEvidence including aggregator/splits support.\n */\nexport const PaymentEvidenceSchema = z\n .object({\n rail: z.string().min(1),\n reference: z.string().min(1),\n amount: z.number().int().nonnegative(),\n currency: iso4217,\n asset: z.string().min(1),\n env: z.enum(['live', 'test']),\n network: z.string().min(1).optional(),\n facilitator_ref: z.string().min(1).optional(),\n evidence: JsonValueSchema,\n aggregator: z.string().min(1).optional(),\n splits: z.array(PaymentSplitSchema).optional(),\n routing: PaymentRoutingSchema.optional(),\n })\n .strict();\n\n// -----------------------------------------------------------------------------\n// Subject Profile Validators (v0.9.16+)\n// -----------------------------------------------------------------------------\n\n/**\n * Subject type schema\n */\nexport const SubjectTypeSchema = z.enum(['human', 'org', 'agent']);\n\n/**\n * Subject profile schema\n *\n * Invariants:\n * - id is required (non-empty string)\n * - type is required (human, org, or agent)\n * - labels if present must be non-empty strings\n */\nexport const SubjectProfileSchema = z\n .object({\n id: z.string().min(1),\n type: SubjectTypeSchema,\n labels: z.array(z.string().min(1)).optional(),\n metadata: JsonObjectSchema.optional(),\n })\n .strict();\n\n/**\n * Subject profile snapshot schema\n *\n * Invariants:\n * - subject is required (valid SubjectProfile)\n * - captured_at is required (non-empty string)\n * MUST be RFC 3339 / ISO 8601 UTC; format not enforced in schema for v0.9.16\n */\nexport const SubjectProfileSnapshotSchema = z\n .object({\n subject: SubjectProfileSchema,\n captured_at: z.string().min(1),\n source: z.string().min(1).optional(),\n version: z.string().min(1).optional(),\n })\n .strict();\n\n// -----------------------------------------------------------------------------\n// Attestation Validators (v0.9.21+)\n// -----------------------------------------------------------------------------\n\n/**\n * Namespaced extensions schema\n *\n * Keys must be namespaced (e.g., \"com.example/field\", \"io.vendor/data\").\n * This provides a forward-compatible extension mechanism.\n */\nexport const ExtensionsSchema = z.record(\n z.string().regex(/^[a-z0-9_.-]+\\/[a-z0-9_.-]+$/),\n JsonValueSchema\n);\n\n/**\n * Generic attestation schema\n *\n * Invariants:\n * - issuer, type, issued_at, evidence are required\n * - issued_at and expires_at must be RFC 3339 date-time\n * - ref if present must be a valid URI\n */\nexport const AttestationSchema = z\n .object({\n issuer: z.string().min(1),\n type: z.string().min(1),\n issued_at: z.string().datetime(),\n expires_at: z.string().datetime().optional(),\n ref: z.string().url().optional(),\n evidence: JsonValueSchema,\n })\n .strict();\n\n// -----------------------------------------------------------------------------\n// Subject Snapshot Validation Helper (v0.9.17+)\n// -----------------------------------------------------------------------------\n\n// Module-level set for PII warning deduplication\nconst warnedSubjectIds = new Set<string>();\n\n/**\n * Heuristic check if a subject ID looks like PII (email/phone)\n */\nfunction looksLikePII(id: string): boolean {\n // Email pattern\n if (/^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(id)) {\n return true;\n }\n // Phone pattern (starts with + followed by digits)\n if (/^\\+?\\d{10,15}$/.test(id.replace(/[\\s\\-()]/g, ''))) {\n return true;\n }\n return false;\n}\n\n/**\n * Validate a subject snapshot (if present)\n *\n * - Returns validated snapshot or null if absent\n * - Throws ZodError for malformed data\n * - Logs advisory warning if id looks like PII (deduplicated)\n */\nexport function validateSubjectSnapshot(\n snapshot: unknown\n): z.infer<typeof SubjectProfileSnapshotSchema> | null {\n if (snapshot === undefined || snapshot === null) {\n return null;\n }\n\n // Validate against schema (throws on malformed data)\n const validated = SubjectProfileSnapshotSchema.parse(snapshot);\n\n // Advisory PII warning (deduplicated)\n const subjectId = validated.subject.id;\n if (looksLikePII(subjectId) && !warnedSubjectIds.has(subjectId)) {\n warnedSubjectIds.add(subjectId);\n console.warn(\n `[peac:subject] Advisory: subject.id \"${subjectId}\" looks like PII. ` +\n 'Prefer opaque identifiers (e.g., \"user:abc123\").'\n );\n }\n\n return validated;\n}\n\n// -----------------------------------------------------------------------------\n// Evidence Validation (v0.9.21+)\n// -----------------------------------------------------------------------------\n\n/**\n * Result type for evidence validation\n */\nexport type EvidenceValidationResult =\n | { ok: true; value: unknown }\n | { ok: false; error: PEACError };\n\n/**\n * Validate payment evidence for JSON safety\n *\n * Uses iterative validation (no recursion) to prevent stack overflow on\n * deeply nested structures. Enforces limits on depth, array length,\n * object keys, and string length.\n *\n * @param evidence - Evidence value to validate\n * @param limits - Optional limits (internal, not part of public API)\n * @returns Result indicating success with validated value, or failure with PEACError\n *\n * @example\n * ```ts\n * const result = validateEvidence({ txId: '123', amount: 100 });\n * if (!result.ok) {\n * console.error(result.error.code, result.error.remediation);\n * }\n * ```\n */\nexport function validateEvidence(\n evidence: unknown,\n limits?: JsonEvidenceLimits\n): EvidenceValidationResult {\n const result = assertJsonSafeIterative(evidence, limits);\n\n if (!result.ok) {\n return {\n ok: false,\n error: createEvidenceNotJsonError(result.error, result.path),\n };\n }\n\n return { ok: true, value: evidence };\n}\n","/**\n * PEAC Attestation Receipt Types (v0.10.8+)\n *\n * Attestation receipts are lightweight signed tokens that attest to API\n * interactions WITHOUT payment fields. This is a distinct profile from\n * full payment receipts (PEACReceiptClaims).\n *\n * Use cases:\n * - API interaction logging with evidentiary value\n * - Middleware-issued receipts for non-payment flows\n * - Audit trails for agent/tool interactions\n *\n * Claims structure:\n * - Core JWT claims: iss, aud, iat, exp\n * - PEAC claims: rid (UUIDv7 receipt ID)\n * - Optional: sub, ext (extensions including interaction binding)\n *\n * @see docs/specs/ATTESTATION-RECEIPTS.md\n */\n\nimport { z } from 'zod';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/**\n * Attestation receipt type constant\n */\nexport const ATTESTATION_RECEIPT_TYPE = 'peac/attestation-receipt' as const;\n\n/**\n * Extension key for minimal interaction binding (middleware profile)\n *\n * This is a simplified binding used by middleware packages. For full\n * interaction evidence, use INTERACTION_EXTENSION_KEY from ./interaction.ts\n */\nexport const MIDDLEWARE_INTERACTION_KEY = 'org.peacprotocol/middleware-interaction@0.1';\n\n/**\n * Limits for attestation receipt fields (DoS protection)\n */\nexport const ATTESTATION_LIMITS = {\n /** Maximum issuer URL length */\n maxIssuerLength: 2048,\n /** Maximum audience URL length */\n maxAudienceLength: 2048,\n /** Maximum subject length */\n maxSubjectLength: 256,\n /** Maximum path length in interaction binding */\n maxPathLength: 2048,\n /** Maximum method length */\n maxMethodLength: 16,\n /** Maximum HTTP status code */\n maxStatusCode: 599,\n /** Minimum HTTP status code */\n minStatusCode: 100,\n} as const;\n\n// ============================================================================\n// Zod Schemas\n// ============================================================================\n\n/**\n * HTTPS URL validation (reused from validators.ts pattern)\n */\nconst httpsUrl = z\n .string()\n .url()\n .max(ATTESTATION_LIMITS.maxIssuerLength)\n .refine((url) => url.startsWith('https://'), 'Must be HTTPS URL');\n\n/**\n * UUIDv7 format validation\n */\nconst uuidv7 = z\n .string()\n .regex(\n /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,\n 'Must be UUIDv7 format'\n );\n\n/**\n * Minimal interaction binding schema (for middleware use)\n *\n * This is a simplified version of full interaction evidence.\n * Contains only: method, path, status.\n *\n * Privacy note: Query strings are excluded by default to avoid\n * leaking sensitive data (API keys, tokens, PII in parameters).\n */\nexport const MinimalInteractionBindingSchema = z\n .object({\n /** HTTP method (uppercase, e.g., GET, POST) */\n method: z\n .string()\n .min(1)\n .max(ATTESTATION_LIMITS.maxMethodLength)\n .transform((m) => m.toUpperCase()),\n /** Request path (no query string by default) */\n path: z.string().min(1).max(ATTESTATION_LIMITS.maxPathLength),\n /** HTTP response status code */\n status: z\n .number()\n .int()\n .min(ATTESTATION_LIMITS.minStatusCode)\n .max(ATTESTATION_LIMITS.maxStatusCode),\n })\n .strict();\n\n/**\n * Attestation receipt extensions schema\n *\n * Allows interaction binding and other namespaced extensions.\n */\nexport const AttestationExtensionsSchema = z.record(z.string(), z.unknown());\n\n/**\n * PEAC Attestation Receipt Claims schema\n *\n * This is the claims structure for attestation receipts - lightweight\n * receipts without payment fields. For full payment receipts, use\n * ReceiptClaimsSchema from ./validators.ts\n */\nexport const AttestationReceiptClaimsSchema = z\n .object({\n /** Issuer URL (normalized, no trailing slash) */\n iss: httpsUrl,\n /** Audience URL */\n aud: httpsUrl,\n /** Issued at (Unix seconds) */\n iat: z.number().int().nonnegative(),\n /** Expiration (Unix seconds) */\n exp: z.number().int().nonnegative(),\n /** Receipt ID (UUIDv7) */\n rid: uuidv7,\n /** Subject identifier (optional) */\n sub: z.string().max(ATTESTATION_LIMITS.maxSubjectLength).optional(),\n /** Extensions (optional) */\n ext: AttestationExtensionsSchema.optional(),\n })\n .strict();\n\n// ============================================================================\n// TypeScript Types (inferred from Zod schemas)\n// ============================================================================\n\nexport type MinimalInteractionBinding = z.infer<typeof MinimalInteractionBindingSchema>;\nexport type AttestationExtensions = z.infer<typeof AttestationExtensionsSchema>;\nexport type AttestationReceiptClaims = z.infer<typeof AttestationReceiptClaimsSchema>;\n\n// ============================================================================\n// Validation Helpers\n// ============================================================================\n\n/**\n * Validation result type\n */\nexport interface AttestationValidationResult {\n valid: boolean;\n error_code?: string;\n error_message?: string;\n}\n\n/**\n * Validate attestation receipt claims\n *\n * @param input - Raw input to validate\n * @returns Validation result\n */\nexport function validateAttestationReceiptClaims(input: unknown): AttestationValidationResult {\n const result = AttestationReceiptClaimsSchema.safeParse(input);\n if (result.success) {\n return { valid: true };\n }\n const firstIssue = result.error.issues[0];\n return {\n valid: false,\n error_code: 'E_ATTESTATION_INVALID_CLAIMS',\n error_message: firstIssue?.message || 'Invalid attestation receipt claims',\n };\n}\n\n/**\n * Check if an object is valid attestation receipt claims (non-throwing)\n *\n * @param claims - Object to check\n * @returns True if valid AttestationReceiptClaims\n */\nexport function isAttestationReceiptClaims(claims: unknown): claims is AttestationReceiptClaims {\n return AttestationReceiptClaimsSchema.safeParse(claims).success;\n}\n\n/**\n * Validate minimal interaction binding\n *\n * @param input - Raw input to validate\n * @returns Validation result\n */\nexport function validateMinimalInteractionBinding(input: unknown): AttestationValidationResult {\n const result = MinimalInteractionBindingSchema.safeParse(input);\n if (result.success) {\n return { valid: true };\n }\n const firstIssue = result.error.issues[0];\n return {\n valid: false,\n error_code: 'E_ATTESTATION_INVALID_INTERACTION',\n error_message: firstIssue?.message || 'Invalid interaction binding',\n };\n}\n\n/**\n * Check if an object is valid minimal interaction binding (non-throwing)\n *\n * @param binding - Object to check\n * @returns True if valid MinimalInteractionBinding\n */\nexport function isMinimalInteractionBinding(\n binding: unknown\n): binding is MinimalInteractionBinding {\n return MinimalInteractionBindingSchema.safeParse(binding).success;\n}\n\n// ============================================================================\n// Factory Functions\n// ============================================================================\n\n/**\n * Parameters for creating attestation receipt claims\n */\nexport interface CreateAttestationReceiptParams {\n /** Issuer URL (will be normalized) */\n issuer: string;\n /** Audience URL */\n audience: string;\n /** Receipt ID (UUIDv7) */\n rid: string;\n /** Subject identifier (optional) */\n sub?: string;\n /** Interaction binding (optional) */\n interaction?: MinimalInteractionBinding;\n /** Additional extensions (optional) */\n extensions?: Record<string, unknown>;\n /** Expiration in seconds from now (default: 300) */\n expiresIn?: number;\n}\n\n/**\n * Create validated attestation receipt claims\n *\n * @param params - Attestation receipt parameters\n * @returns Validated AttestationReceiptClaims\n * @throws ZodError if validation fails\n */\nexport function createAttestationReceiptClaims(\n params: CreateAttestationReceiptParams\n): AttestationReceiptClaims {\n const now = Math.floor(Date.now() / 1000);\n const expiresIn = params.expiresIn ?? 300;\n\n // Normalize issuer (remove trailing slashes)\n // Using explicit loop instead of regex to avoid ReDoS with quantifiers\n let normalizedIssuer = params.issuer;\n while (normalizedIssuer.endsWith('/')) {\n normalizedIssuer = normalizedIssuer.slice(0, -1);\n }\n\n // Build extensions\n const ext: Record<string, unknown> = { ...params.extensions };\n if (params.interaction) {\n ext[MIDDLEWARE_INTERACTION_KEY] = params.interaction;\n }\n\n const claims: AttestationReceiptClaims = {\n iss: normalizedIssuer,\n aud: params.audience,\n iat: now,\n exp: now + expiresIn,\n rid: params.rid,\n ...(params.sub && { sub: params.sub }),\n ...(Object.keys(ext).length > 0 && { ext }),\n };\n\n return AttestationReceiptClaimsSchema.parse(claims);\n}\n\n// ============================================================================\n// Type Guard for Receipt Profile Discrimination\n// ============================================================================\n\n/**\n * Check if claims are attestation-only (no payment fields)\n *\n * This helps discriminate between attestation receipts and\n * full payment receipts at runtime.\n *\n * @param claims - Receipt claims to check\n * @returns True if claims lack payment fields (amt, cur, payment)\n */\nexport function isAttestationOnly(claims: Record<string, unknown>): boolean {\n return !('amt' in claims) && !('cur' in claims) && !('payment' in claims);\n}\n\n/**\n * Check if claims are payment receipt (has payment fields)\n *\n * @param claims - Receipt claims to check\n * @returns True if claims have payment fields\n */\nexport function isPaymentReceipt(claims: Record<string, unknown>): boolean {\n return 'amt' in claims && 'cur' in claims && 'payment' in claims;\n}\n","/**\n * Unified Receipt Parser\n *\n * Single entry point for classifying and validating receipt claims.\n * Supports both commerce (payment) and attestation receipt profiles.\n *\n * Classification uses key presence ('amt' in obj), NOT truthy values.\n * If any of amt|cur|payment are present, the receipt is classified as commerce.\n * If commerce validation fails, it returns a commerce error -- never falls\n * through to attestation.\n */\n\nimport { ZodError } from 'zod';\nimport { ReceiptClaimsSchema, type ReceiptClaimsType } from './validators.js';\nimport {\n AttestationReceiptClaimsSchema,\n type AttestationReceiptClaims,\n} from './attestation-receipt.js';\n\n/**\n * Receipt variant discriminator\n */\nexport type ReceiptVariant = 'commerce' | 'attestation';\n\n/**\n * Parse error with canonical error code\n */\nexport interface PEACParseError {\n /** Canonical error code from specs/kernel/errors.json */\n code: string;\n /** Human-readable message */\n message: string;\n /** Zod issues (if schema validation failed) */\n issues?: ZodError['issues'];\n}\n\n/**\n * Successful parse result\n */\nexport interface ParseSuccess {\n ok: true;\n variant: ReceiptVariant;\n claims: ReceiptClaimsType | AttestationReceiptClaims;\n}\n\n/**\n * Failed parse result\n */\nexport interface ParseFailure {\n ok: false;\n error: PEACParseError;\n}\n\n/**\n * Parse result type\n */\nexport type ParseReceiptResult = ParseSuccess | ParseFailure;\n\n/**\n * Options for parseReceiptClaims (extensible for future wire versions)\n */\nexport interface ParseReceiptOptions {\n /** Reserved for future use (wire version discrimination in v0.12.0+) */\n wireVersion?: string;\n}\n\n/**\n * Classify a claims object as commerce or attestation.\n *\n * Uses key presence (not truthiness). If ANY of amt, cur, payment\n * are present as keys, the receipt is classified as commerce.\n */\nfunction classifyReceipt(obj: Record<string, unknown>): ReceiptVariant {\n if ('amt' in obj || 'cur' in obj || 'payment' in obj) {\n return 'commerce';\n }\n return 'attestation';\n}\n\n/**\n * Parse and validate receipt claims.\n *\n * Unified entry point for both commerce and attestation receipt validation.\n * Classification is strict: if any commerce key (amt, cur, payment) is present,\n * the receipt MUST validate as commerce. There is no fallback to attestation.\n *\n * @param input - Raw claims object (typically decoded from JWS payload)\n * @param _opts - Reserved for future use\n * @returns Parse result with variant discrimination and validated claims, or error\n */\nexport function parseReceiptClaims(\n input: unknown,\n _opts?: ParseReceiptOptions\n): ParseReceiptResult {\n // Guard: input must be a non-null object\n if (input === null || input === undefined || typeof input !== 'object' || Array.isArray(input)) {\n return {\n ok: false,\n error: {\n code: 'E_PARSE_INVALID_INPUT',\n message: 'Input must be a non-null object',\n },\n };\n }\n\n const obj = input as Record<string, unknown>;\n const variant = classifyReceipt(obj);\n\n if (variant === 'commerce') {\n const result = ReceiptClaimsSchema.safeParse(obj);\n if (!result.success) {\n return {\n ok: false,\n error: {\n code: 'E_PARSE_COMMERCE_INVALID',\n message: `Commerce receipt validation failed: ${result.error.issues.map((i) => i.message).join('; ')}`,\n issues: result.error.issues,\n },\n };\n }\n return {\n ok: true,\n variant: 'commerce',\n claims: result.data,\n };\n }\n\n // Attestation path\n const result = AttestationReceiptClaimsSchema.safeParse(obj);\n if (!result.success) {\n return {\n ok: false,\n error: {\n code: 'E_PARSE_ATTESTATION_INVALID',\n message: `Attestation receipt validation failed: ${result.error.issues.map((i) => i.message).join('; ')}`,\n issues: result.error.issues,\n },\n };\n }\n return {\n ok: true,\n variant: 'attestation',\n claims: result.data,\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/constants.ts","../src/json.ts","../src/purpose.ts","../src/validators.ts","../src/attestation-receipt.ts","../src/receipt-parser.ts"],"names":["z","httpsUrl","uuidv7","result"],"mappings":"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","file":"receipt-parser.mjs","sourcesContent":["/**\n * Wire format constants - FROZEN\n *\n * These constants are now sourced from @peac/kernel\n * (normative source: specs/kernel/constants.json)\n */\n\nimport { WIRE_TYPE, ALGORITHMS, HEADERS, POLICY, ISSUER_CONFIG, DISCOVERY } from '@peac/kernel';\n\n/**\n * Wire format version - peac-receipt/0.1\n * Normalized in v0.10.0 to peac-<artifact>/<major>.<minor> pattern\n */\nexport const PEAC_WIRE_TYP = WIRE_TYPE;\n\n/**\n * Signature algorithm - FROZEN forever\n */\nexport const PEAC_ALG = ALGORITHMS.default;\n\n/**\n * Canonical header name\n */\nexport const PEAC_RECEIPT_HEADER = HEADERS.receipt;\n\n/**\n * Purpose header names (v0.9.24+)\n */\nexport const PEAC_PURPOSE_HEADER = HEADERS.purpose;\nexport const PEAC_PURPOSE_APPLIED_HEADER = HEADERS.purposeApplied;\nexport const PEAC_PURPOSE_REASON_HEADER = HEADERS.purposeReason;\n\n/**\n * Policy manifest path (/.well-known/peac.txt)\n * @see docs/specs/PEAC-TXT.md\n */\nexport const PEAC_POLICY_PATH = POLICY.manifestPath;\n\n/**\n * Policy manifest fallback path (/peac.txt)\n */\nexport const PEAC_POLICY_FALLBACK_PATH = POLICY.fallbackPath;\n\n/**\n * Maximum policy manifest size\n */\nexport const PEAC_POLICY_MAX_BYTES = POLICY.maxBytes;\n\n/**\n * Issuer configuration path (/.well-known/peac-issuer.json)\n * @see docs/specs/PEAC-ISSUER.md\n */\nexport const PEAC_ISSUER_CONFIG_PATH = ISSUER_CONFIG.configPath;\n\n/**\n * Issuer configuration version\n */\nexport const PEAC_ISSUER_CONFIG_VERSION = ISSUER_CONFIG.configVersion;\n\n/**\n * Maximum issuer configuration size\n */\nexport const PEAC_ISSUER_CONFIG_MAX_BYTES = ISSUER_CONFIG.maxBytes;\n\n/**\n * @deprecated Use PEAC_POLICY_PATH instead. Will be removed in v1.0.\n */\nexport const PEAC_DISCOVERY_PATH = DISCOVERY.manifestPath;\n\n/**\n * @deprecated Use PEAC_POLICY_MAX_BYTES instead. Will be removed in v1.0.\n */\nexport const PEAC_DISCOVERY_MAX_BYTES = 2000 as const;\n\n/**\n * JSON Schema URL for PEAC receipt wire format v0.1\n *\n * This is the canonical $id for the root schema.\n * Use for schema references and cross-implementation validation.\n *\n * @since v0.10.0\n */\nexport const PEAC_RECEIPT_SCHEMA_URL =\n 'https://www.peacprotocol.org/schemas/wire/0.1/peac-receipt.0.1.schema.json' as const;\n","/**\n * JSON-safe validation schemas\n *\n * Provides Zod schemas that guarantee JSON roundtrip safety:\n * - Rejects NaN, Infinity, -Infinity (not valid JSON numbers)\n * - Rejects undefined (dropped by JSON.stringify)\n * - Rejects non-plain objects (Date, Map, Set, class instances)\n * - Rejects functions, symbols, bigints\n */\n\nimport { z } from 'zod';\nimport type { JsonValue, JsonObject, JsonArray } from '@peac/kernel';\nimport { KERNEL_CONSTRAINTS } from './constraints';\n\n/**\n * Check if value is a plain object (not Date, Map, Set, class instance, etc.)\n *\n * A plain object has prototype of Object.prototype or null.\n * This rejects Date, Map, Set, Array, and class instances even when\n * they have zero enumerable properties (which would pass z.record()).\n */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== 'object') {\n return false;\n }\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * JSON number schema - rejects NaN and Infinity\n *\n * JSON.stringify(NaN) === \"null\" and JSON.stringify(Infinity) === \"null\"\n * which silently corrupts data. We reject these at validation time.\n */\nconst JsonNumberSchema = z.number().finite();\n\n/**\n * JSON primitive schema - string, finite number, boolean, null\n */\nexport const JsonPrimitiveSchema = z.union([z.string(), JsonNumberSchema, z.boolean(), z.null()]);\n\n/**\n * Plain object schema (internal) - validates object is plain before recursive validation\n */\nconst PlainObjectSchema = z.unknown().refine(isPlainObject, {\n message: 'Expected plain object, received non-plain object (Date, Map, Set, or class instance)',\n});\n\n/**\n * JSON value schema - recursive type for any valid JSON value\n *\n * Validates:\n * - Primitives: string, finite number, boolean, null\n * - Arrays: containing valid JSON values\n * - Objects: plain objects with string keys and valid JSON values\n *\n * Rejects:\n * - undefined (dropped by JSON.stringify)\n * - NaN, Infinity, -Infinity (become null in JSON)\n * - BigInt (throws in JSON.stringify)\n * - Date (becomes ISO string - implicit conversion)\n * - Map, Set (become {} in JSON)\n * - Functions, Symbols (dropped by JSON.stringify)\n * - Class instances (prototype chain lost)\n */\nexport const JsonValueSchema: z.ZodType<JsonValue> = z.lazy(() =>\n z.union([\n JsonPrimitiveSchema,\n z.array(JsonValueSchema),\n // Plain object check then record validation\n PlainObjectSchema.transform((obj) => obj as Record<string, unknown>).pipe(\n z.record(JsonValueSchema)\n ),\n ])\n) as z.ZodType<JsonValue>;\n\n/**\n * JSON object schema - plain object with string keys and JSON values\n *\n * Rejects non-plain objects (Date, Map, Set, class instances).\n */\nexport const JsonObjectSchema: z.ZodType<JsonObject> = PlainObjectSchema.transform(\n (obj) => obj as Record<string, unknown>\n).pipe(z.record(JsonValueSchema)) as z.ZodType<JsonObject>;\n\n/**\n * JSON array schema - array of JSON values\n */\nexport const JsonArraySchema: z.ZodType<JsonArray> = z.array(JsonValueSchema);\n\n/**\n * Default limits for JSON evidence validation.\n *\n * Derived from KERNEL_CONSTRAINTS (single source of truth).\n * These are conservative defaults to prevent DoS attacks via deeply nested\n * or excessively large JSON structures.\n */\nexport const JSON_EVIDENCE_LIMITS = {\n /** Maximum nesting depth (default: 32) */\n maxDepth: KERNEL_CONSTRAINTS.MAX_NESTED_DEPTH,\n /** Maximum array length (default: 10,000) */\n maxArrayLength: KERNEL_CONSTRAINTS.MAX_ARRAY_LENGTH,\n /** Maximum object keys (default: 1,000) */\n maxObjectKeys: KERNEL_CONSTRAINTS.MAX_OBJECT_KEYS,\n /** Maximum string length in code units (default: 65,536) */\n maxStringLength: KERNEL_CONSTRAINTS.MAX_STRING_LENGTH,\n /** Maximum total nodes to visit (default: 100,000) */\n maxTotalNodes: KERNEL_CONSTRAINTS.MAX_TOTAL_NODES,\n} as const;\n\n/**\n * Limits for JSON evidence validation\n *\n * @internal - Not part of public API. Use validateEvidence() with defaults.\n *\n * For testing only: import via UNSAFE_JsonEvidenceLimits\n */\nexport interface JsonEvidenceLimits {\n maxDepth?: number;\n maxArrayLength?: number;\n maxObjectKeys?: number;\n maxStringLength?: number;\n maxTotalNodes?: number;\n}\n\n/**\n * Result of JSON safety validation\n */\nexport type JsonSafetyResult =\n | { ok: true }\n | { ok: false; error: string; path: (string | number)[] };\n\n/**\n * Stack entry type for iterative traversal\n *\n * - \"enter\": entering an object/array, need to validate and push children\n * - \"exit\": exiting an object/array, remove from current path (for cycle detection)\n */\ntype StackEntry =\n | { type: 'enter'; value: unknown; path: (string | number)[]; depth: number }\n | { type: 'exit'; obj: object };\n\n/**\n * Iterative JSON safety validator\n *\n * Validates that a value is JSON-safe without using recursion, preventing\n * stack overflow on deeply nested structures. Uses an explicit stack for\n * traversal with entry/exit markers for correct cycle detection.\n *\n * Cycle Detection:\n * Uses a path-based approach where only objects on the current traversal\n * path are tracked. This correctly allows diamond structures (same object\n * referenced from multiple paths) while rejecting actual cycles (object\n * references itself through its descendants).\n *\n * Rejects:\n * - Cycles (object references itself directly or indirectly)\n * - Non-plain objects (Date, Map, Set, class instances)\n * - Non-finite numbers (NaN, Infinity, -Infinity)\n * - undefined, BigInt, functions, symbols\n * - Structures exceeding depth/size limits\n *\n * Allows:\n * - Diamond structures (same object referenced from multiple paths)\n *\n * @param value - Value to validate\n * @param limits - Optional limits (defaults to JSON_EVIDENCE_LIMITS)\n * @returns Result indicating success or failure with error details\n */\nexport function assertJsonSafeIterative(\n value: unknown,\n limits: JsonEvidenceLimits = {}\n): JsonSafetyResult {\n const maxDepth = limits.maxDepth ?? JSON_EVIDENCE_LIMITS.maxDepth;\n const maxArrayLength = limits.maxArrayLength ?? JSON_EVIDENCE_LIMITS.maxArrayLength;\n const maxObjectKeys = limits.maxObjectKeys ?? JSON_EVIDENCE_LIMITS.maxObjectKeys;\n const maxStringLength = limits.maxStringLength ?? JSON_EVIDENCE_LIMITS.maxStringLength;\n const maxTotalNodes = limits.maxTotalNodes ?? JSON_EVIDENCE_LIMITS.maxTotalNodes;\n\n // Track objects on the current traversal path for cycle detection.\n // An object appearing twice on the same path is a cycle.\n // An object appearing on different paths (diamond) is NOT a cycle.\n const pathSet = new WeakSet<object>();\n\n // Track total nodes visited for DoS protection\n let nodeCount = 0;\n\n // Stack with entry/exit markers for path tracking\n const stack: StackEntry[] = [{ type: 'enter', value, path: [], depth: 0 }];\n\n while (stack.length > 0) {\n const entry = stack.pop()!;\n\n // Handle exit marker - remove object from current path\n if (entry.type === 'exit') {\n pathSet.delete(entry.obj);\n continue;\n }\n\n const { value: current, path, depth } = entry;\n\n // Check total node limit\n nodeCount++;\n if (nodeCount > maxTotalNodes) {\n return {\n ok: false,\n error: `Maximum total nodes exceeded (limit: ${maxTotalNodes})`,\n path,\n };\n }\n\n // Check depth limit\n if (depth > maxDepth) {\n return {\n ok: false,\n error: `Maximum depth exceeded (limit: ${maxDepth})`,\n path,\n };\n }\n\n // Handle null (valid JSON)\n if (current === null) {\n continue;\n }\n\n // Handle primitives\n const type = typeof current;\n\n if (type === 'string') {\n if ((current as string).length > maxStringLength) {\n return {\n ok: false,\n error: `String exceeds maximum length (limit: ${maxStringLength})`,\n path,\n };\n }\n continue;\n }\n\n if (type === 'number') {\n if (!Number.isFinite(current as number)) {\n return {\n ok: false,\n error: `Non-finite number: ${current}`,\n path,\n };\n }\n continue;\n }\n\n if (type === 'boolean') {\n continue;\n }\n\n // Reject non-JSON types\n if (type === 'undefined') {\n return { ok: false, error: 'undefined is not valid JSON', path };\n }\n\n if (type === 'bigint') {\n return { ok: false, error: 'BigInt is not valid JSON', path };\n }\n\n if (type === 'function') {\n return { ok: false, error: 'Function is not valid JSON', path };\n }\n\n if (type === 'symbol') {\n return { ok: false, error: 'Symbol is not valid JSON', path };\n }\n\n // Handle objects (arrays and plain objects)\n if (type === 'object') {\n const obj = current as object;\n\n // Cycle detection - check if object is already on the current path\n // (not just visited anywhere, but specifically an ancestor)\n if (pathSet.has(obj)) {\n return { ok: false, error: 'Cycle detected in object graph', path };\n }\n\n // Add to current path and push exit marker to remove when done\n pathSet.add(obj);\n stack.push({ type: 'exit', obj });\n\n // Handle arrays\n if (Array.isArray(obj)) {\n if (obj.length > maxArrayLength) {\n return {\n ok: false,\n error: `Array exceeds maximum length (limit: ${maxArrayLength})`,\n path,\n };\n }\n // Push array elements to stack in reverse order for correct traversal\n for (let i = obj.length - 1; i >= 0; i--) {\n stack.push({ type: 'enter', value: obj[i], path: [...path, i], depth: depth + 1 });\n }\n continue;\n }\n\n // Check for non-plain objects (Date, Map, Set, class instances, etc.)\n const proto = Object.getPrototypeOf(obj);\n if (proto !== Object.prototype && proto !== null) {\n const constructorName = obj.constructor?.name ?? 'unknown';\n return {\n ok: false,\n error: `Non-plain object (${constructorName}) is not valid JSON`,\n path,\n };\n }\n\n // Handle plain objects\n const keys = Object.keys(obj);\n if (keys.length > maxObjectKeys) {\n return {\n ok: false,\n error: `Object exceeds maximum key count (limit: ${maxObjectKeys})`,\n path,\n };\n }\n // Push object values to stack\n for (let i = keys.length - 1; i >= 0; i--) {\n const key = keys[i];\n stack.push({\n type: 'enter',\n value: (obj as Record<string, unknown>)[key],\n path: [...path, key],\n depth: depth + 1,\n });\n }\n continue;\n }\n\n // Shouldn't reach here, but reject unknown types\n return { ok: false, error: `Unknown type: ${type}`, path };\n }\n\n return { ok: true };\n}\n","/**\n * PEAC Purpose Types (v0.9.24+)\n *\n * Purpose type hierarchy for forward-compatible purpose handling:\n * - PurposeToken: Wire format (string) - preserves unknown tokens\n * - CanonicalPurpose: PEAC's normative vocabulary - enforcement semantics\n * - PurposeReason: Audit spine for enforcement decisions\n *\n * @see specs/kernel/constants.json for canonical values\n */\n\n/**\n * PurposeToken - Wire format string with validation grammar\n *\n * Allows unknown tokens for forward compatibility. Any valid token\n * that matches the grammar is accepted and preserved.\n *\n * Grammar: lowercase, max 64 chars, [a-z0-9_-] + optional vendor prefix (vendor:token)\n * Hyphens allowed for interop with external systems (Cloudflare, IETF AIPREF, etc.)\n *\n * Examples: \"train\", \"search\", \"user_action\", \"user-action\", \"cf:ai_crawler\", \"cf:ai-crawler\"\n */\nexport type PurposeToken = string;\n\n/**\n * CanonicalPurpose - PEAC's normative vocabulary\n *\n * These are the only tokens PEAC enforces semantics for.\n * Matches specs/kernel/constants.json purpose.canonical_tokens.\n *\n * - train: Model training data collection\n * - search: Traditional search indexing\n * - user_action: Agent acting on user behalf (v0.9.24+)\n * - inference: Runtime inference / RAG\n * - index: Content indexing (store)\n */\nexport type CanonicalPurpose = 'train' | 'search' | 'user_action' | 'inference' | 'index';\n\n/**\n * Internal-only purpose value (never valid on wire)\n *\n * Applied when PEAC-Purpose header is missing or empty.\n * Explicit \"undeclared\" in request -> 400 Bad Request.\n */\nexport type InternalPurpose = 'undeclared';\n\n/**\n * PurposeReason - Audit spine for enforcement decisions\n *\n * Captures WHY a purpose was enforced differently than declared.\n * Matches specs/kernel/constants.json purpose.reason_values.\n */\nexport type PurposeReason =\n | 'allowed' // Purpose permitted as declared (happy path)\n | 'constrained' // Allowed with rate limits applied\n | 'denied' // Purpose rejected by policy\n | 'downgraded' // More restrictive purpose applied\n | 'undeclared_default' // No purpose declared, default applied\n | 'unknown_preserved'; // Unknown purpose token, preserved but flagged\n\n/**\n * Legacy purpose tokens from pre-v0.9.24\n *\n * These are mapped to CanonicalPurpose via mapLegacyToCanonical().\n * Retained for backward compatibility with existing ControlPurpose usage.\n */\nexport type LegacyPurpose = 'crawl' | 'ai_input' | 'ai_index';\n\n// ============================================================================\n// Validation Constants\n// ============================================================================\n\n/**\n * Grammar validation for PurposeToken\n *\n * Pattern: lowercase letter, optionally followed by alphanumeric/underscore/hyphen\n * characters that MUST end with a letter or digit (no trailing separators).\n * Optional vendor prefix separated by colon follows the same rules.\n *\n * Hyphens are allowed for interoperability with external systems (Cloudflare,\n * IETF AIPREF, etc.) that use hyphenated tokens like \"user-action\" or \"train-ai\".\n *\n * Valid: \"train\", \"user_action\", \"user-action\", \"cf:ai_crawler\", \"cf:ai-crawler\", \"a\", \"a1\"\n * Invalid: \"Train\", \"123abc\", \"\", \"-train\", \"train-\", \"train_\", \"cf:ai-\", \"cf:-ai\"\n */\nexport const PURPOSE_TOKEN_REGEX =\n /^[a-z](?:[a-z0-9_-]*[a-z0-9])?(?::[a-z](?:[a-z0-9_-]*[a-z0-9])?)?$/;\n\n/** Maximum length for a purpose token */\nexport const MAX_PURPOSE_TOKEN_LENGTH = 64;\n\n/** Maximum number of purpose tokens per request (RECOMMENDED, not MUST) */\nexport const MAX_PURPOSE_TOKENS_PER_REQUEST = 10;\n\n/** Canonical purpose tokens (from constants.json) */\nexport const CANONICAL_PURPOSES: readonly CanonicalPurpose[] = [\n 'train',\n 'search',\n 'user_action',\n 'inference',\n 'index',\n] as const;\n\n/** Purpose reason values (from constants.json) */\nexport const PURPOSE_REASONS: readonly PurposeReason[] = [\n 'allowed',\n 'constrained',\n 'denied',\n 'downgraded',\n 'undeclared_default',\n 'unknown_preserved',\n] as const;\n\n/** Internal-only purpose value */\nexport const INTERNAL_PURPOSE_UNDECLARED: InternalPurpose = 'undeclared';\n\n// ============================================================================\n// Validation Functions\n// ============================================================================\n\n/**\n * Check if a string is a valid PurposeToken\n *\n * Validates against the purpose token grammar:\n * - Lowercase letters, digits, underscores, hyphens\n * - Optional vendor prefix with colon\n * - Max 64 characters\n * - Must start with lowercase letter\n *\n * @param token - String to validate\n * @returns true if valid PurposeToken\n */\nexport function isValidPurposeToken(token: string): token is PurposeToken {\n if (typeof token !== 'string') return false;\n if (token.length === 0 || token.length > MAX_PURPOSE_TOKEN_LENGTH) return false;\n return PURPOSE_TOKEN_REGEX.test(token);\n}\n\n/**\n * Check if a PurposeToken is a CanonicalPurpose\n *\n * @param token - Token to check\n * @returns true if token is in canonical vocabulary\n */\nexport function isCanonicalPurpose(token: string): token is CanonicalPurpose {\n return (CANONICAL_PURPOSES as readonly string[]).includes(token);\n}\n\n/**\n * Check if a PurposeToken is a LegacyPurpose\n *\n * @param token - Token to check\n * @returns true if token is a legacy purpose\n */\nexport function isLegacyPurpose(token: string): token is LegacyPurpose {\n return token === 'crawl' || token === 'ai_input' || token === 'ai_index';\n}\n\n/**\n * Check if a string is a valid PurposeReason\n *\n * @param reason - String to check\n * @returns true if valid PurposeReason\n */\nexport function isValidPurposeReason(reason: string): reason is PurposeReason {\n return (PURPOSE_REASONS as readonly string[]).includes(reason);\n}\n\n/**\n * Check if a purpose token is the internal-only \"undeclared\" value\n *\n * Used to reject explicit \"undeclared\" on wire (400 Bad Request).\n *\n * @param token - Token to check\n * @returns true if token is \"undeclared\"\n */\nexport function isUndeclaredPurpose(token: string): boolean {\n return token === INTERNAL_PURPOSE_UNDECLARED;\n}\n\n// ============================================================================\n// Normalization Functions\n// ============================================================================\n\n/**\n * Normalize a purpose token\n *\n * Applies normalization rules:\n * - Trim whitespace\n * - Lowercase\n *\n * @param token - Raw token from header\n * @returns Normalized token\n */\nexport function normalizePurposeToken(token: string): string {\n return token.trim().toLowerCase();\n}\n\n/**\n * Parse PEAC-Purpose header value into array of tokens\n *\n * Applies parsing rules:\n * - Split on commas\n * - Trim optional whitespace (OWS) around tokens\n * - Lowercase all tokens\n * - Drop empty tokens\n * - Deduplicate\n * - Preserve input order\n *\n * @param headerValue - Raw PEAC-Purpose header value\n * @returns Array of normalized PurposeToken values\n */\nexport function parsePurposeHeader(headerValue: string): PurposeToken[] {\n if (!headerValue || typeof headerValue !== 'string') {\n return [];\n }\n\n const seen = new Set<string>();\n const tokens: PurposeToken[] = [];\n\n for (const part of headerValue.split(',')) {\n const normalized = normalizePurposeToken(part);\n if (normalized.length > 0 && !seen.has(normalized)) {\n seen.add(normalized);\n tokens.push(normalized);\n }\n }\n\n return tokens;\n}\n\n/**\n * Validate parsed purpose tokens\n *\n * Returns validation result with:\n * - valid: All tokens pass grammar validation\n * - tokens: All normalized tokens (including invalid ones)\n * - invalidTokens: Tokens that failed grammar validation\n * - undeclaredPresent: true if explicit \"undeclared\" was found (should reject)\n *\n * @param tokens - Array of parsed tokens\n * @returns Validation result\n */\nexport interface PurposeValidationResult {\n valid: boolean;\n tokens: PurposeToken[];\n invalidTokens: string[];\n undeclaredPresent: boolean;\n}\n\nexport function validatePurposeTokens(tokens: PurposeToken[]): PurposeValidationResult {\n const invalidTokens: string[] = [];\n let undeclaredPresent = false;\n\n for (const token of tokens) {\n if (isUndeclaredPurpose(token)) {\n undeclaredPresent = true;\n }\n if (!isValidPurposeToken(token)) {\n invalidTokens.push(token);\n }\n }\n\n return {\n valid: invalidTokens.length === 0 && !undeclaredPresent,\n tokens,\n invalidTokens,\n undeclaredPresent,\n };\n}\n\n/**\n * Derive known canonical purposes from declared tokens\n *\n * Filters purpose_declared to get only canonical purposes.\n * This is a helper derivation, NOT stored on wire.\n *\n * @param declared - Array of declared PurposeTokens\n * @returns Array of CanonicalPurpose tokens\n */\nexport function deriveKnownPurposes(declared: PurposeToken[]): CanonicalPurpose[] {\n return declared.filter(isCanonicalPurpose);\n}\n\n// ============================================================================\n// Legacy Mapping\n// ============================================================================\n\n/**\n * Legacy purpose to canonical mapping\n */\nconst LEGACY_TO_CANONICAL: Record<LegacyPurpose, CanonicalPurpose> = {\n crawl: 'index', // Crawl implies indexing\n ai_input: 'inference', // RAG/grounding -> inference context\n ai_index: 'index', // AI-powered indexing -> index\n};\n\n/**\n * Map legacy purpose to canonical purpose\n *\n * Used for backward compatibility with pre-v0.9.24 ControlPurpose values.\n *\n * @param legacy - Legacy purpose token\n * @returns Mapping result with canonical purpose and audit note\n */\nexport interface LegacyMappingResult {\n canonical: CanonicalPurpose;\n mapping_note: string;\n}\n\nexport function mapLegacyToCanonical(legacy: LegacyPurpose): LegacyMappingResult {\n const canonical = LEGACY_TO_CANONICAL[legacy];\n return {\n canonical,\n mapping_note: `Mapped legacy '${legacy}' to canonical '${canonical}'`,\n };\n}\n\n/**\n * Normalize any purpose token (canonical, legacy, or unknown)\n *\n * Returns the canonical form if known, otherwise preserves the token.\n *\n * @param token - Any valid PurposeToken\n * @returns Canonical purpose if mapped, otherwise original token\n */\nexport function normalizeToCanonicalOrPreserve(\n token: PurposeToken\n):\n | { purpose: CanonicalPurpose; mapped: false }\n | { purpose: PurposeToken; mapped: true; from: LegacyPurpose }\n | { purpose: PurposeToken; mapped: false; unknown: true } {\n if (isCanonicalPurpose(token)) {\n return { purpose: token, mapped: false };\n }\n if (isLegacyPurpose(token)) {\n return { purpose: LEGACY_TO_CANONICAL[token], mapped: true, from: token };\n }\n return { purpose: token, mapped: false, unknown: true };\n}\n\n// ============================================================================\n// Purpose Reason Determination (v0.9.24+)\n// ============================================================================\n\n/**\n * Decision type for purpose reason determination\n *\n * Maps to policy decisions that affect purpose enforcement.\n */\nexport type PurposeDecision = 'allowed' | 'constrained' | 'denied' | 'downgraded';\n\n/**\n * Context for determining purpose reason\n */\nexport interface PurposeReasonContext {\n /**\n * Whether purposes were declared (PEAC-Purpose header present and non-empty).\n * If false, reason will be 'undeclared_default'.\n */\n declared: boolean;\n\n /**\n * Whether any unknown (non-canonical) tokens are present in declared purposes.\n * If true and declared is true, reason will be 'unknown_preserved'.\n */\n hasUnknownTokens: boolean;\n\n /**\n * The policy decision (only used if declared and no unknown tokens).\n * Defaults to 'allowed' if not provided.\n */\n decision?: PurposeDecision;\n}\n\n/**\n * Determine the appropriate PurposeReason based on context\n *\n * This helper implements the decision logic for the audit spine:\n * 1. If no purposes declared -> 'undeclared_default'\n * 2. If unknown tokens present -> 'unknown_preserved'\n * 3. Otherwise -> maps to policy decision\n *\n * @param context - Context for determination\n * @returns The appropriate PurposeReason for the audit spine\n *\n * @example\n * ```typescript\n * // Missing PEAC-Purpose header\n * determinePurposeReason({ declared: false, hasUnknownTokens: false });\n * // => 'undeclared_default'\n *\n * // Has vendor-prefixed tokens\n * determinePurposeReason({ declared: true, hasUnknownTokens: true, decision: 'allowed' });\n * // => 'unknown_preserved'\n *\n * // All canonical tokens, allowed by policy\n * determinePurposeReason({ declared: true, hasUnknownTokens: false, decision: 'allowed' });\n * // => 'allowed'\n * ```\n */\nexport function determinePurposeReason(context: PurposeReasonContext): PurposeReason {\n // Priority 1: No purposes declared\n if (!context.declared) {\n return 'undeclared_default';\n }\n\n // Priority 2: Unknown tokens present (preserved for forward-compat)\n if (context.hasUnknownTokens) {\n return 'unknown_preserved';\n }\n\n // Priority 3: Map policy decision to reason\n const decision = context.decision ?? 'allowed';\n switch (decision) {\n case 'allowed':\n return 'allowed';\n case 'constrained':\n return 'constrained';\n case 'denied':\n return 'denied';\n case 'downgraded':\n return 'downgraded';\n default:\n return 'allowed';\n }\n}\n\n/**\n * Check if any tokens in an array are unknown (non-canonical)\n *\n * @param tokens - Array of purpose tokens\n * @returns true if any token is not a canonical purpose\n */\nexport function hasUnknownPurposeTokens(tokens: PurposeToken[]): boolean {\n return tokens.some((token) => !isCanonicalPurpose(token));\n}\n","/**\n * Zod validators for PEAC protocol types\n */\nimport { z } from 'zod';\nimport { PEAC_WIRE_TYP, PEAC_ALG } from './constants';\nimport {\n JsonValueSchema,\n JsonObjectSchema,\n assertJsonSafeIterative,\n type JsonEvidenceLimits,\n} from './json';\nimport { createEvidenceNotJsonError, type PEACError } from './errors';\nimport { PURPOSE_TOKEN_REGEX, MAX_PURPOSE_TOKEN_LENGTH } from './purpose';\n\nconst httpsUrl = z\n .string()\n .url()\n .refine((u) => u.startsWith('https://'), 'must be https://');\nconst iso4217 = z.string().regex(/^[A-Z]{3}$/);\nconst uuidv7 = z\n .string()\n .regex(/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);\n\nexport const NormalizedPayment = z\n .object({\n rail: z.string().min(1),\n reference: z.string().min(1),\n amount: z.number().int().nonnegative(),\n currency: iso4217,\n asset: z.string().optional(),\n env: z.string().optional(),\n evidence: JsonValueSchema.optional(),\n metadata: JsonObjectSchema.optional(),\n })\n .strict();\n\nexport const Subject = z.object({ uri: httpsUrl }).strict();\n\nexport const AIPREFSnapshot = z\n .object({\n url: httpsUrl,\n hash: z.string().min(8),\n })\n .strict();\n\n// Note: Extensions uses a forward reference pattern since ControlBlockSchema\n// is defined after this. We use catchall for now and validate control separately.\nexport const Extensions = z\n .object({\n aipref_snapshot: AIPREFSnapshot.optional(),\n // control block validated via ControlBlockSchema when present\n })\n .catchall(z.unknown());\n\nexport const JWSHeader = z\n .object({\n typ: z.literal(PEAC_WIRE_TYP),\n alg: z.literal(PEAC_ALG),\n kid: z.string().min(8),\n })\n .strict();\n\n// Forward-declare purpose validators used in ReceiptClaims\n// Full definitions are below\nconst CanonicalPurposeValues = ['train', 'search', 'user_action', 'inference', 'index'] as const;\nconst PurposeReasonValues = [\n 'allowed',\n 'constrained',\n 'denied',\n 'downgraded',\n 'undeclared_default',\n 'unknown_preserved',\n] as const;\n\nexport const ReceiptClaimsSchema = z\n .object({\n iss: httpsUrl,\n aud: httpsUrl,\n iat: z.number().int().nonnegative(),\n exp: z.number().int().optional(),\n rid: uuidv7,\n amt: z.number().int().nonnegative(),\n cur: iso4217,\n payment: NormalizedPayment,\n subject: Subject.optional(),\n ext: Extensions.optional(),\n // Purpose claims (v0.9.24+)\n // purpose_declared: string[] - preserves unknown tokens for forward-compat\n purpose_declared: z.array(z.string()).optional(),\n // purpose_enforced: CanonicalPurpose - must be one with enforcement semantics\n purpose_enforced: z.enum(CanonicalPurposeValues).optional(),\n // purpose_reason: PurposeReason - audit spine for enforcement decisions\n purpose_reason: z.enum(PurposeReasonValues).optional(),\n })\n .strict();\n\n/**\n * Schema-derived receipt claims type (v0.9.30+)\n *\n * This is the canonical type for receipt claims - derived from the Zod schema.\n * Use this type instead of manually-defined interfaces to ensure type/schema parity.\n */\nexport type ReceiptClaimsType = z.infer<typeof ReceiptClaimsSchema>;\n\n/**\n * @deprecated Use ReceiptClaimsSchema instead. Renamed in v0.9.30.\n */\nexport const ReceiptClaims = ReceiptClaimsSchema;\n\nexport const VerifyRequest = z\n .object({\n receipt_jws: z.string().min(16),\n })\n .strict();\n\n// -----------------------------------------------------------------------------\n// Control Abstraction Layer (CAL) Validators (v0.9.16+)\n// -----------------------------------------------------------------------------\n\n/**\n * Control purpose - what the access is for\n *\n * v0.9.17+: Added ai_input, search for RSL alignment\n * v0.9.18+: Added ai_index (RSL 1.0 canonical token). Removed ai_search.\n * v0.9.24+: Added user_action for agent-on-behalf-of-user scenarios.\n *\n * @see https://rslstandard.org/rsl for RSL 1.0 specification\n */\nexport const ControlPurposeSchema = z.enum([\n 'crawl',\n 'index',\n 'train',\n 'inference',\n 'user_action',\n 'ai_input',\n 'ai_index',\n 'search',\n]);\n\n/**\n * Control licensing mode - how access is licensed\n */\nexport const ControlLicensingModeSchema = z.enum([\n 'subscription',\n 'pay_per_crawl',\n 'pay_per_inference',\n]);\n\n/**\n * Control decision type\n */\nexport const ControlDecisionSchema = z.enum(['allow', 'deny', 'review']);\n\n/**\n * Single control step in governance chain\n */\nexport const ControlStepSchema = z.object({\n engine: z.string().min(1),\n version: z.string().optional(),\n policy_id: z.string().optional(),\n result: ControlDecisionSchema,\n reason: z.string().optional(),\n purpose: ControlPurposeSchema.optional(),\n licensing_mode: ControlLicensingModeSchema.optional(),\n scope: z.union([z.string(), z.array(z.string())]).optional(),\n limits_snapshot: z.unknown().optional(),\n evidence_ref: z.string().optional(),\n});\n\n/**\n * Composable control block - multi-party governance\n */\nexport const ControlBlockSchema = z\n .object({\n chain: z.array(ControlStepSchema).min(1),\n decision: ControlDecisionSchema,\n combinator: z.literal('any_can_veto').optional(),\n })\n .refine(\n (data) => {\n // Validate decision consistency with chain\n const hasAnyDeny = data.chain.some((step) => step.result === 'deny');\n const allAllow = data.chain.every((step) => step.result === 'allow');\n const hasReview = data.chain.some((step) => step.result === 'review');\n\n if (hasAnyDeny && data.decision !== 'deny') {\n return false;\n }\n if (allAllow && data.decision !== 'allow') {\n return false;\n }\n // If has review but no deny, decision can be review or allow\n if (hasReview && !hasAnyDeny && data.decision === 'deny') {\n return false;\n }\n return true;\n },\n {\n message: 'Control block decision must be consistent with chain results',\n }\n );\n\n// -----------------------------------------------------------------------------\n// Purpose Type Validators (v0.9.24+)\n// -----------------------------------------------------------------------------\n\n/**\n * Purpose token validator\n *\n * PurposeToken is a string that matches the purpose grammar:\n * - Lowercase letters, digits, underscores\n * - Optional vendor prefix with colon (e.g., \"cf:ai_crawler\")\n * - Max 64 characters\n *\n * Uses string type (not enum) to preserve unknown tokens for forward-compat.\n */\nexport const PurposeTokenSchema = z\n .string()\n .min(1)\n .max(MAX_PURPOSE_TOKEN_LENGTH)\n .refine((token) => PURPOSE_TOKEN_REGEX.test(token), {\n message:\n 'Invalid purpose token format. Must be lowercase, alphanumeric with underscores, optional vendor prefix.',\n });\n\n/**\n * Canonical purpose validator\n *\n * CanonicalPurpose is one of PEAC's normative purpose tokens.\n * Only these tokens have enforcement semantics.\n */\nexport const CanonicalPurposeSchema = z.enum([\n 'train',\n 'search',\n 'user_action',\n 'inference',\n 'index',\n]);\n\n/**\n * Purpose reason validator\n *\n * PurposeReason is the audit spine explaining enforcement decisions.\n */\nexport const PurposeReasonSchema = z.enum([\n 'allowed',\n 'constrained',\n 'denied',\n 'downgraded',\n 'undeclared_default',\n 'unknown_preserved',\n]);\n\n// -----------------------------------------------------------------------------\n// Payment Evidence Validators (v0.9.16+)\n// -----------------------------------------------------------------------------\n\n/**\n * Payment split schema\n *\n * Invariants:\n * - party is required (non-empty string)\n * - amount if present must be >= 0\n * - share if present must be in [0,1]\n * - At least one of amount or share must be specified\n */\nexport const PaymentSplitSchema = z\n .object({\n party: z.string().min(1),\n amount: z.number().int().nonnegative().optional(),\n currency: iso4217.optional(),\n share: z.number().min(0).max(1).optional(),\n rail: z.string().min(1).optional(),\n account_ref: z.string().min(1).optional(),\n metadata: JsonObjectSchema.optional(),\n })\n .strict()\n .refine((data) => data.amount !== undefined || data.share !== undefined, {\n message: 'At least one of amount or share must be specified',\n });\n\n/**\n * Payment routing mode schema (rail-agnostic)\n *\n * Describes how the payment is routed between payer, aggregator, and merchant.\n * This is a generic hint - specific rails populate it from their native formats.\n *\n * Values:\n * - \"direct\": Direct payment to merchant (no intermediary)\n * - \"callback\": Routed via callback URL / payment service\n * - \"role\": Role-based routing (e.g., \"publisher\", \"platform\")\n *\n * Examples of producers:\n * - x402 v2 `payTo.mode` -> routing\n * - Stripe Connect `destination` -> routing = 'direct' or 'callback'\n * - UPI `pa` (payee address) -> routing = 'direct'\n */\nexport const PaymentRoutingSchema = z.enum(['direct', 'callback', 'role']);\n\n/**\n * Payment evidence schema\n *\n * Full schema for PaymentEvidence including aggregator/splits support.\n */\nexport const PaymentEvidenceSchema = z\n .object({\n rail: z.string().min(1),\n reference: z.string().min(1),\n amount: z.number().int().nonnegative(),\n currency: iso4217,\n asset: z.string().min(1),\n env: z.enum(['live', 'test']),\n network: z.string().min(1).optional(),\n facilitator_ref: z.string().min(1).optional(),\n evidence: JsonValueSchema,\n aggregator: z.string().min(1).optional(),\n splits: z.array(PaymentSplitSchema).optional(),\n routing: PaymentRoutingSchema.optional(),\n })\n .strict();\n\n// -----------------------------------------------------------------------------\n// Subject Profile Validators (v0.9.16+)\n// -----------------------------------------------------------------------------\n\n/**\n * Subject type schema\n */\nexport const SubjectTypeSchema = z.enum(['human', 'org', 'agent']);\n\n/**\n * Subject profile schema\n *\n * Invariants:\n * - id is required (non-empty string)\n * - type is required (human, org, or agent)\n * - labels if present must be non-empty strings\n */\nexport const SubjectProfileSchema = z\n .object({\n id: z.string().min(1),\n type: SubjectTypeSchema,\n labels: z.array(z.string().min(1)).optional(),\n metadata: JsonObjectSchema.optional(),\n })\n .strict();\n\n/**\n * Subject profile snapshot schema\n *\n * Invariants:\n * - subject is required (valid SubjectProfile)\n * - captured_at is required (non-empty string)\n * MUST be RFC 3339 / ISO 8601 UTC; format not enforced in schema for v0.9.16\n */\nexport const SubjectProfileSnapshotSchema = z\n .object({\n subject: SubjectProfileSchema,\n captured_at: z.string().min(1),\n source: z.string().min(1).optional(),\n version: z.string().min(1).optional(),\n })\n .strict();\n\n// -----------------------------------------------------------------------------\n// Attestation Validators (v0.9.21+)\n// -----------------------------------------------------------------------------\n\n/**\n * Namespaced extensions schema\n *\n * Keys must be namespaced (e.g., \"com.example/field\", \"io.vendor/data\").\n * This provides a forward-compatible extension mechanism.\n */\nexport const ExtensionsSchema = z.record(\n z.string().regex(/^[a-z0-9_.-]+\\/[a-z0-9_.-]+$/),\n JsonValueSchema\n);\n\n/**\n * Generic attestation schema\n *\n * Invariants:\n * - issuer, type, issued_at, evidence are required\n * - issued_at and expires_at must be RFC 3339 date-time\n * - ref if present must be a valid URI\n */\nexport const AttestationSchema = z\n .object({\n issuer: z.string().min(1),\n type: z.string().min(1),\n issued_at: z.string().datetime(),\n expires_at: z.string().datetime().optional(),\n ref: z.string().url().optional(),\n evidence: JsonValueSchema,\n })\n .strict();\n\n// -----------------------------------------------------------------------------\n// Subject Snapshot Validation Helper (v0.9.17+)\n// -----------------------------------------------------------------------------\n\n// Module-level set for PII warning deduplication\nconst warnedSubjectIds = new Set<string>();\n\n/**\n * Heuristic check if a subject ID looks like PII (email/phone)\n */\nfunction looksLikePII(id: string): boolean {\n // Email pattern\n if (/^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(id)) {\n return true;\n }\n // Phone pattern (starts with + followed by digits)\n if (/^\\+?\\d{10,15}$/.test(id.replace(/[\\s\\-()]/g, ''))) {\n return true;\n }\n return false;\n}\n\n/**\n * Validate a subject snapshot (if present)\n *\n * - Returns validated snapshot or null if absent\n * - Throws ZodError for malformed data\n * - Logs advisory warning if id looks like PII (deduplicated)\n */\nexport function validateSubjectSnapshot(\n snapshot: unknown\n): z.infer<typeof SubjectProfileSnapshotSchema> | null {\n if (snapshot === undefined || snapshot === null) {\n return null;\n }\n\n // Validate against schema (throws on malformed data)\n const validated = SubjectProfileSnapshotSchema.parse(snapshot);\n\n // Advisory PII warning (deduplicated)\n const subjectId = validated.subject.id;\n if (looksLikePII(subjectId) && !warnedSubjectIds.has(subjectId)) {\n warnedSubjectIds.add(subjectId);\n console.warn(\n `[peac:subject] Advisory: subject.id \"${subjectId}\" looks like PII. ` +\n 'Prefer opaque identifiers (e.g., \"user:abc123\").'\n );\n }\n\n return validated;\n}\n\n// -----------------------------------------------------------------------------\n// Evidence Validation (v0.9.21+)\n// -----------------------------------------------------------------------------\n\n/**\n * Result type for evidence validation\n */\nexport type EvidenceValidationResult =\n | { ok: true; value: unknown }\n | { ok: false; error: PEACError };\n\n/**\n * Validate payment evidence for JSON safety\n *\n * Uses iterative validation (no recursion) to prevent stack overflow on\n * deeply nested structures. Enforces limits on depth, array length,\n * object keys, and string length.\n *\n * @param evidence - Evidence value to validate\n * @param limits - Optional limits (internal, not part of public API)\n * @returns Result indicating success with validated value, or failure with PEACError\n *\n * @example\n * ```ts\n * const result = validateEvidence({ txId: '123', amount: 100 });\n * if (!result.ok) {\n * console.error(result.error.code, result.error.remediation);\n * }\n * ```\n */\nexport function validateEvidence(\n evidence: unknown,\n limits?: JsonEvidenceLimits\n): EvidenceValidationResult {\n const result = assertJsonSafeIterative(evidence, limits);\n\n if (!result.ok) {\n return {\n ok: false,\n error: createEvidenceNotJsonError(result.error, result.path),\n };\n }\n\n return { ok: true, value: evidence };\n}\n","/**\n * PEAC Attestation Receipt Types (v0.10.8+)\n *\n * Attestation receipts are lightweight signed tokens that attest to API\n * interactions WITHOUT payment fields. This is a distinct profile from\n * full payment receipts (PEACReceiptClaims).\n *\n * Use cases:\n * - API interaction logging with evidentiary value\n * - Middleware-issued receipts for non-payment flows\n * - Audit trails for agent/tool interactions\n *\n * Claims structure:\n * - Core JWT claims: iss, aud, iat, exp\n * - PEAC claims: rid (UUIDv7 receipt ID)\n * - Optional: sub, ext (extensions including interaction binding)\n *\n * @see docs/specs/ATTESTATION-RECEIPTS.md\n */\n\nimport { z } from 'zod';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/**\n * Attestation receipt type constant\n */\nexport const ATTESTATION_RECEIPT_TYPE = 'peac/attestation-receipt' as const;\n\n/**\n * Extension key for minimal interaction binding (middleware profile)\n *\n * This is a simplified binding used by middleware packages. For full\n * interaction evidence, use INTERACTION_EXTENSION_KEY from ./interaction.ts\n */\nexport const MIDDLEWARE_INTERACTION_KEY = 'org.peacprotocol/middleware-interaction@0.1';\n\n/**\n * Limits for attestation receipt fields (DoS protection)\n */\nexport const ATTESTATION_LIMITS = {\n /** Maximum issuer URL length */\n maxIssuerLength: 2048,\n /** Maximum audience URL length */\n maxAudienceLength: 2048,\n /** Maximum subject length */\n maxSubjectLength: 256,\n /** Maximum path length in interaction binding */\n maxPathLength: 2048,\n /** Maximum method length */\n maxMethodLength: 16,\n /** Maximum HTTP status code */\n maxStatusCode: 599,\n /** Minimum HTTP status code */\n minStatusCode: 100,\n} as const;\n\n// ============================================================================\n// Zod Schemas\n// ============================================================================\n\n/**\n * HTTPS URL validation (reused from validators.ts pattern)\n */\nconst httpsUrl = z\n .string()\n .url()\n .max(ATTESTATION_LIMITS.maxIssuerLength)\n .refine((url) => url.startsWith('https://'), 'Must be HTTPS URL');\n\n/**\n * UUIDv7 format validation\n */\nconst uuidv7 = z\n .string()\n .regex(\n /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,\n 'Must be UUIDv7 format'\n );\n\n/**\n * Minimal interaction binding schema (for middleware use)\n *\n * This is a simplified version of full interaction evidence.\n * Contains only: method, path, status.\n *\n * Privacy note: Query strings are excluded by default to avoid\n * leaking sensitive data (API keys, tokens, PII in parameters).\n */\nexport const MinimalInteractionBindingSchema = z\n .object({\n /** HTTP method (uppercase, e.g., GET, POST) */\n method: z\n .string()\n .min(1)\n .max(ATTESTATION_LIMITS.maxMethodLength)\n .transform((m) => m.toUpperCase()),\n /** Request path (no query string by default) */\n path: z.string().min(1).max(ATTESTATION_LIMITS.maxPathLength),\n /** HTTP response status code */\n status: z\n .number()\n .int()\n .min(ATTESTATION_LIMITS.minStatusCode)\n .max(ATTESTATION_LIMITS.maxStatusCode),\n })\n .strict();\n\n/**\n * Attestation receipt extensions schema\n *\n * Allows interaction binding and other namespaced extensions.\n */\nexport const AttestationExtensionsSchema = z.record(z.string(), z.unknown());\n\n/**\n * PEAC Attestation Receipt Claims schema\n *\n * This is the claims structure for attestation receipts - lightweight\n * receipts without payment fields. For full payment receipts, use\n * ReceiptClaimsSchema from ./validators.ts\n */\nexport const AttestationReceiptClaimsSchema = z\n .object({\n /** Issuer URL (normalized, no trailing slash) */\n iss: httpsUrl,\n /** Audience URL */\n aud: httpsUrl,\n /** Issued at (Unix seconds) */\n iat: z.number().int().nonnegative(),\n /** Expiration (Unix seconds) */\n exp: z.number().int().nonnegative(),\n /** Receipt ID (UUIDv7) */\n rid: uuidv7,\n /** Subject identifier (optional) */\n sub: z.string().max(ATTESTATION_LIMITS.maxSubjectLength).optional(),\n /** Extensions (optional) */\n ext: AttestationExtensionsSchema.optional(),\n })\n .strict();\n\n// ============================================================================\n// TypeScript Types (inferred from Zod schemas)\n// ============================================================================\n\nexport type MinimalInteractionBinding = z.infer<typeof MinimalInteractionBindingSchema>;\nexport type AttestationExtensions = z.infer<typeof AttestationExtensionsSchema>;\nexport type AttestationReceiptClaims = z.infer<typeof AttestationReceiptClaimsSchema>;\n\n// ============================================================================\n// Validation Helpers\n// ============================================================================\n\n/**\n * Validation result type\n */\nexport interface AttestationValidationResult {\n valid: boolean;\n error_code?: string;\n error_message?: string;\n}\n\n/**\n * Validate attestation receipt claims\n *\n * @param input - Raw input to validate\n * @returns Validation result\n */\nexport function validateAttestationReceiptClaims(input: unknown): AttestationValidationResult {\n const result = AttestationReceiptClaimsSchema.safeParse(input);\n if (result.success) {\n return { valid: true };\n }\n const firstIssue = result.error.issues[0];\n return {\n valid: false,\n error_code: 'E_ATTESTATION_INVALID_CLAIMS',\n error_message: firstIssue?.message || 'Invalid attestation receipt claims',\n };\n}\n\n/**\n * Check if an object is valid attestation receipt claims (non-throwing)\n *\n * @param claims - Object to check\n * @returns True if valid AttestationReceiptClaims\n */\nexport function isAttestationReceiptClaims(claims: unknown): claims is AttestationReceiptClaims {\n return AttestationReceiptClaimsSchema.safeParse(claims).success;\n}\n\n/**\n * Validate minimal interaction binding\n *\n * @param input - Raw input to validate\n * @returns Validation result\n */\nexport function validateMinimalInteractionBinding(input: unknown): AttestationValidationResult {\n const result = MinimalInteractionBindingSchema.safeParse(input);\n if (result.success) {\n return { valid: true };\n }\n const firstIssue = result.error.issues[0];\n return {\n valid: false,\n error_code: 'E_ATTESTATION_INVALID_INTERACTION',\n error_message: firstIssue?.message || 'Invalid interaction binding',\n };\n}\n\n/**\n * Check if an object is valid minimal interaction binding (non-throwing)\n *\n * @param binding - Object to check\n * @returns True if valid MinimalInteractionBinding\n */\nexport function isMinimalInteractionBinding(\n binding: unknown\n): binding is MinimalInteractionBinding {\n return MinimalInteractionBindingSchema.safeParse(binding).success;\n}\n\n// ============================================================================\n// Factory Functions\n// ============================================================================\n\n/**\n * Parameters for creating attestation receipt claims\n */\nexport interface CreateAttestationReceiptParams {\n /** Issuer URL (will be normalized) */\n issuer: string;\n /** Audience URL */\n audience: string;\n /** Receipt ID (UUIDv7) */\n rid: string;\n /** Subject identifier (optional) */\n sub?: string;\n /** Interaction binding (optional) */\n interaction?: MinimalInteractionBinding;\n /** Additional extensions (optional) */\n extensions?: Record<string, unknown>;\n /** Expiration in seconds from now (default: 300) */\n expiresIn?: number;\n}\n\n/**\n * Create validated attestation receipt claims\n *\n * @param params - Attestation receipt parameters\n * @returns Validated AttestationReceiptClaims\n * @throws ZodError if validation fails\n */\nexport function createAttestationReceiptClaims(\n params: CreateAttestationReceiptParams\n): AttestationReceiptClaims {\n const now = Math.floor(Date.now() / 1000);\n const expiresIn = params.expiresIn ?? 300;\n\n // Normalize issuer (remove trailing slashes)\n // Using explicit loop instead of regex to avoid ReDoS with quantifiers\n let normalizedIssuer = params.issuer;\n while (normalizedIssuer.endsWith('/')) {\n normalizedIssuer = normalizedIssuer.slice(0, -1);\n }\n\n // Build extensions\n const ext: Record<string, unknown> = { ...params.extensions };\n if (params.interaction) {\n ext[MIDDLEWARE_INTERACTION_KEY] = params.interaction;\n }\n\n const claims: AttestationReceiptClaims = {\n iss: normalizedIssuer,\n aud: params.audience,\n iat: now,\n exp: now + expiresIn,\n rid: params.rid,\n ...(params.sub && { sub: params.sub }),\n ...(Object.keys(ext).length > 0 && { ext }),\n };\n\n return AttestationReceiptClaimsSchema.parse(claims);\n}\n\n// ============================================================================\n// Type Guard for Receipt Profile Discrimination\n// ============================================================================\n\n/**\n * Check if claims are attestation-only (no payment fields)\n *\n * This helps discriminate between attestation receipts and\n * full payment receipts at runtime.\n *\n * @param claims - Receipt claims to check\n * @returns True if claims lack payment fields (amt, cur, payment)\n */\nexport function isAttestationOnly(claims: Record<string, unknown>): boolean {\n return !('amt' in claims) && !('cur' in claims) && !('payment' in claims);\n}\n\n/**\n * Check if claims are payment receipt (has payment fields)\n *\n * @param claims - Receipt claims to check\n * @returns True if claims have payment fields\n */\nexport function isPaymentReceipt(claims: Record<string, unknown>): boolean {\n return 'amt' in claims && 'cur' in claims && 'payment' in claims;\n}\n","/**\n * Unified Receipt Parser\n *\n * Single entry point for classifying and validating receipt claims.\n * Supports both commerce (payment) and attestation receipt profiles.\n *\n * Classification uses key presence ('amt' in obj), NOT truthy values.\n * If any of amt|cur|payment are present, the receipt is classified as commerce.\n * If commerce validation fails, it returns a commerce error -- never falls\n * through to attestation.\n */\n\nimport { ZodError } from 'zod';\nimport { ReceiptClaimsSchema, type ReceiptClaimsType } from './validators.js';\nimport {\n AttestationReceiptClaimsSchema,\n type AttestationReceiptClaims,\n} from './attestation-receipt.js';\n\n/**\n * Receipt variant discriminator\n */\nexport type ReceiptVariant = 'commerce' | 'attestation';\n\n/**\n * Parse error with canonical error code\n */\nexport interface PEACParseError {\n /** Canonical error code from specs/kernel/errors.json */\n code: string;\n /** Human-readable message */\n message: string;\n /** Zod issues (if schema validation failed) */\n issues?: ZodError['issues'];\n}\n\n/**\n * Successful parse result\n */\nexport interface ParseSuccess {\n ok: true;\n variant: ReceiptVariant;\n claims: ReceiptClaimsType | AttestationReceiptClaims;\n}\n\n/**\n * Failed parse result\n */\nexport interface ParseFailure {\n ok: false;\n error: PEACParseError;\n}\n\n/**\n * Parse result type\n */\nexport type ParseReceiptResult = ParseSuccess | ParseFailure;\n\n/**\n * Options for parseReceiptClaims (extensible for future wire versions)\n */\nexport interface ParseReceiptOptions {\n /** Reserved for future use (wire version discrimination in v0.12.0+) */\n wireVersion?: string;\n}\n\n/**\n * Classify a claims object as commerce or attestation.\n *\n * Uses key presence (not truthiness). If ANY of amt, cur, payment\n * are present as keys, the receipt is classified as commerce.\n */\nfunction classifyReceipt(obj: Record<string, unknown>): ReceiptVariant {\n if ('amt' in obj || 'cur' in obj || 'payment' in obj) {\n return 'commerce';\n }\n return 'attestation';\n}\n\n/**\n * Parse and validate receipt claims.\n *\n * Unified entry point for both commerce and attestation receipt validation.\n * Classification is strict: if any commerce key (amt, cur, payment) is present,\n * the receipt MUST validate as commerce. There is no fallback to attestation.\n *\n * @param input - Raw claims object (typically decoded from JWS payload)\n * @param _opts - Reserved for future use\n * @returns Parse result with variant discrimination and validated claims, or error\n */\nexport function parseReceiptClaims(\n input: unknown,\n _opts?: ParseReceiptOptions\n): ParseReceiptResult {\n // Guard: input must be a non-null object\n if (input === null || input === undefined || typeof input !== 'object' || Array.isArray(input)) {\n return {\n ok: false,\n error: {\n code: 'E_PARSE_INVALID_INPUT',\n message: 'Input must be a non-null object',\n },\n };\n }\n\n const obj = input as Record<string, unknown>;\n const variant = classifyReceipt(obj);\n\n if (variant === 'commerce') {\n const result = ReceiptClaimsSchema.safeParse(obj);\n if (!result.success) {\n return {\n ok: false,\n error: {\n code: 'E_PARSE_COMMERCE_INVALID',\n message: `Commerce receipt validation failed: ${result.error.issues.map((i) => i.message).join('; ')}`,\n issues: result.error.issues,\n },\n };\n }\n return {\n ok: true,\n variant: 'commerce',\n claims: result.data,\n };\n }\n\n // Attestation path\n const result = AttestationReceiptClaimsSchema.safeParse(obj);\n if (!result.success) {\n return {\n ok: false,\n error: {\n code: 'E_PARSE_ATTESTATION_INVALID',\n message: `Attestation receipt validation failed: ${result.error.issues.map((i) => i.message).join('; ')}`,\n issues: result.error.issues,\n },\n };\n }\n return {\n ok: true,\n variant: 'attestation',\n claims: result.data,\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peac/schema",
3
- "version": "0.10.13",
3
+ "version": "0.10.14",
4
4
  "description": "PEAC Protocol JSON schemas, OpenAPI specs, and TypeScript types",
5
5
  "main": "dist/index.cjs",
6
6
  "types": "dist/index.d.ts",
@@ -63,10 +63,10 @@
63
63
  },
64
64
  "dependencies": {
65
65
  "zod": "^3.22.4",
66
- "@peac/kernel": "0.10.13"
66
+ "@peac/kernel": "0.10.14"
67
67
  },
68
68
  "devDependencies": {
69
- "@types/node": "^20.19.33",
69
+ "@types/node": "^22.19.11",
70
70
  "typescript": "^5.3.3",
71
71
  "vitest": "^4.0.0",
72
72
  "tsup": "^8.0.0"