@peac/schema 0.10.8 → 0.10.10
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.
- package/LICENSE +1 -1
- package/dist/attestation-receipt.cjs +127 -0
- package/dist/attestation-receipt.cjs.map +1 -0
- package/dist/attestation-receipt.mjs +113 -0
- package/dist/attestation-receipt.mjs.map +1 -0
- package/dist/attribution.cjs +249 -0
- package/dist/attribution.cjs.map +1 -0
- package/dist/attribution.mjs +227 -0
- package/dist/attribution.mjs.map +1 -0
- package/dist/dispute.d.ts.map +1 -1
- package/dist/index.cjs +2818 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +2577 -0
- package/dist/index.mjs.map +1 -0
- package/dist/interaction.cjs +619 -0
- package/dist/interaction.cjs.map +1 -0
- package/dist/interaction.mjs +583 -0
- package/dist/interaction.mjs.map +1 -0
- package/dist/normalize.cjs +84 -0
- package/dist/normalize.cjs.map +1 -0
- package/dist/normalize.d.ts +21 -15
- package/dist/normalize.d.ts.map +1 -1
- package/dist/normalize.mjs +82 -0
- package/dist/normalize.mjs.map +1 -0
- package/dist/receipt-parser.cjs +333 -0
- package/dist/receipt-parser.cjs.map +1 -0
- package/dist/receipt-parser.d.ts +68 -0
- package/dist/receipt-parser.d.ts.map +1 -0
- package/dist/receipt-parser.mjs +331 -0
- package/dist/receipt-parser.mjs.map +1 -0
- package/dist/workflow.cjs +321 -0
- package/dist/workflow.cjs.map +1 -0
- package/dist/workflow.mjs +292 -0
- package/dist/workflow.mjs.map +1 -0
- package/package.json +50 -6
- package/dist/agent-identity.js +0 -357
- package/dist/agent-identity.js.map +0 -1
- package/dist/attestation-receipt.js +0 -249
- package/dist/attestation-receipt.js.map +0 -1
- package/dist/attribution.js +0 -444
- package/dist/attribution.js.map +0 -1
- package/dist/constants.js +0 -73
- package/dist/constants.js.map +0 -1
- package/dist/control.js +0 -9
- package/dist/control.js.map +0 -1
- package/dist/dispute.js +0 -832
- package/dist/dispute.js.map +0 -1
- package/dist/envelope.js +0 -9
- package/dist/envelope.js.map +0 -1
- package/dist/errors.js +0 -116
- package/dist/errors.js.map +0 -1
- package/dist/evidence.js +0 -8
- package/dist/evidence.js.map +0 -1
- package/dist/index.js +0 -280
- package/dist/index.js.map +0 -1
- package/dist/interaction.js +0 -918
- package/dist/interaction.js.map +0 -1
- package/dist/json.js +0 -267
- package/dist/json.js.map +0 -1
- package/dist/normalize.js +0 -102
- package/dist/normalize.js.map +0 -1
- package/dist/obligations.js +0 -337
- package/dist/obligations.js.map +0 -1
- package/dist/purpose.js +0 -296
- package/dist/purpose.js.map +0 -1
- package/dist/schemas.js +0 -7
- package/dist/schemas.js.map +0 -1
- package/dist/subject.js +0 -9
- package/dist/subject.js.map +0 -1
- package/dist/types.js +0 -6
- package/dist/types.js.map +0 -1
- package/dist/validators.js +0 -421
- package/dist/validators.js.map +0 -1
- package/dist/version.js +0 -7
- package/dist/version.js.map +0 -1
- package/dist/workflow.js +0 -523
- package/dist/workflow.js.map +0 -1
package/LICENSE
CHANGED
|
@@ -175,7 +175,7 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
|
175
175
|
|
|
176
176
|
END OF TERMS AND CONDITIONS
|
|
177
177
|
|
|
178
|
-
Copyright 2025 PEAC Protocol Contributors
|
|
178
|
+
Copyright 2025-2026 PEAC Protocol Contributors
|
|
179
179
|
|
|
180
180
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
181
181
|
you may not use this file except in compliance with the License.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var zod = require('zod');
|
|
4
|
+
|
|
5
|
+
// src/attestation-receipt.ts
|
|
6
|
+
var ATTESTATION_RECEIPT_TYPE = "peac/attestation-receipt";
|
|
7
|
+
var MIDDLEWARE_INTERACTION_KEY = "org.peacprotocol/middleware-interaction@0.1";
|
|
8
|
+
var ATTESTATION_LIMITS = {
|
|
9
|
+
/** Maximum issuer URL length */
|
|
10
|
+
maxIssuerLength: 2048,
|
|
11
|
+
/** Maximum audience URL length */
|
|
12
|
+
maxAudienceLength: 2048,
|
|
13
|
+
/** Maximum subject length */
|
|
14
|
+
maxSubjectLength: 256,
|
|
15
|
+
/** Maximum path length in interaction binding */
|
|
16
|
+
maxPathLength: 2048,
|
|
17
|
+
/** Maximum method length */
|
|
18
|
+
maxMethodLength: 16,
|
|
19
|
+
/** Maximum HTTP status code */
|
|
20
|
+
maxStatusCode: 599,
|
|
21
|
+
/** Minimum HTTP status code */
|
|
22
|
+
minStatusCode: 100
|
|
23
|
+
};
|
|
24
|
+
var httpsUrl = zod.z.string().url().max(ATTESTATION_LIMITS.maxIssuerLength).refine((url) => url.startsWith("https://"), "Must be HTTPS URL");
|
|
25
|
+
var uuidv7 = zod.z.string().regex(
|
|
26
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
|
27
|
+
"Must be UUIDv7 format"
|
|
28
|
+
);
|
|
29
|
+
var MinimalInteractionBindingSchema = zod.z.object({
|
|
30
|
+
/** HTTP method (uppercase, e.g., GET, POST) */
|
|
31
|
+
method: zod.z.string().min(1).max(ATTESTATION_LIMITS.maxMethodLength).transform((m) => m.toUpperCase()),
|
|
32
|
+
/** Request path (no query string by default) */
|
|
33
|
+
path: zod.z.string().min(1).max(ATTESTATION_LIMITS.maxPathLength),
|
|
34
|
+
/** HTTP response status code */
|
|
35
|
+
status: zod.z.number().int().min(ATTESTATION_LIMITS.minStatusCode).max(ATTESTATION_LIMITS.maxStatusCode)
|
|
36
|
+
}).strict();
|
|
37
|
+
var AttestationExtensionsSchema = zod.z.record(zod.z.string(), zod.z.unknown());
|
|
38
|
+
var AttestationReceiptClaimsSchema = zod.z.object({
|
|
39
|
+
/** Issuer URL (normalized, no trailing slash) */
|
|
40
|
+
iss: httpsUrl,
|
|
41
|
+
/** Audience URL */
|
|
42
|
+
aud: httpsUrl,
|
|
43
|
+
/** Issued at (Unix seconds) */
|
|
44
|
+
iat: zod.z.number().int().nonnegative(),
|
|
45
|
+
/** Expiration (Unix seconds) */
|
|
46
|
+
exp: zod.z.number().int().nonnegative(),
|
|
47
|
+
/** Receipt ID (UUIDv7) */
|
|
48
|
+
rid: uuidv7,
|
|
49
|
+
/** Subject identifier (optional) */
|
|
50
|
+
sub: zod.z.string().max(ATTESTATION_LIMITS.maxSubjectLength).optional(),
|
|
51
|
+
/** Extensions (optional) */
|
|
52
|
+
ext: AttestationExtensionsSchema.optional()
|
|
53
|
+
}).strict();
|
|
54
|
+
function validateAttestationReceiptClaims(input) {
|
|
55
|
+
const result = AttestationReceiptClaimsSchema.safeParse(input);
|
|
56
|
+
if (result.success) {
|
|
57
|
+
return { valid: true };
|
|
58
|
+
}
|
|
59
|
+
const firstIssue = result.error.issues[0];
|
|
60
|
+
return {
|
|
61
|
+
valid: false,
|
|
62
|
+
error_code: "E_ATTESTATION_INVALID_CLAIMS",
|
|
63
|
+
error_message: firstIssue?.message || "Invalid attestation receipt claims"
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function isAttestationReceiptClaims(claims) {
|
|
67
|
+
return AttestationReceiptClaimsSchema.safeParse(claims).success;
|
|
68
|
+
}
|
|
69
|
+
function validateMinimalInteractionBinding(input) {
|
|
70
|
+
const result = MinimalInteractionBindingSchema.safeParse(input);
|
|
71
|
+
if (result.success) {
|
|
72
|
+
return { valid: true };
|
|
73
|
+
}
|
|
74
|
+
const firstIssue = result.error.issues[0];
|
|
75
|
+
return {
|
|
76
|
+
valid: false,
|
|
77
|
+
error_code: "E_ATTESTATION_INVALID_INTERACTION",
|
|
78
|
+
error_message: firstIssue?.message || "Invalid interaction binding"
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function isMinimalInteractionBinding(binding) {
|
|
82
|
+
return MinimalInteractionBindingSchema.safeParse(binding).success;
|
|
83
|
+
}
|
|
84
|
+
function createAttestationReceiptClaims(params) {
|
|
85
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
86
|
+
const expiresIn = params.expiresIn ?? 300;
|
|
87
|
+
let normalizedIssuer = params.issuer;
|
|
88
|
+
while (normalizedIssuer.endsWith("/")) {
|
|
89
|
+
normalizedIssuer = normalizedIssuer.slice(0, -1);
|
|
90
|
+
}
|
|
91
|
+
const ext = { ...params.extensions };
|
|
92
|
+
if (params.interaction) {
|
|
93
|
+
ext[MIDDLEWARE_INTERACTION_KEY] = params.interaction;
|
|
94
|
+
}
|
|
95
|
+
const claims = {
|
|
96
|
+
iss: normalizedIssuer,
|
|
97
|
+
aud: params.audience,
|
|
98
|
+
iat: now,
|
|
99
|
+
exp: now + expiresIn,
|
|
100
|
+
rid: params.rid,
|
|
101
|
+
...params.sub && { sub: params.sub },
|
|
102
|
+
...Object.keys(ext).length > 0 && { ext }
|
|
103
|
+
};
|
|
104
|
+
return AttestationReceiptClaimsSchema.parse(claims);
|
|
105
|
+
}
|
|
106
|
+
function isAttestationOnly(claims) {
|
|
107
|
+
return !("amt" in claims) && !("cur" in claims) && !("payment" in claims);
|
|
108
|
+
}
|
|
109
|
+
function isPaymentReceipt(claims) {
|
|
110
|
+
return "amt" in claims && "cur" in claims && "payment" in claims;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
exports.ATTESTATION_LIMITS = ATTESTATION_LIMITS;
|
|
114
|
+
exports.ATTESTATION_RECEIPT_TYPE = ATTESTATION_RECEIPT_TYPE;
|
|
115
|
+
exports.AttestationExtensionsSchema = AttestationExtensionsSchema;
|
|
116
|
+
exports.AttestationReceiptClaimsSchema = AttestationReceiptClaimsSchema;
|
|
117
|
+
exports.MIDDLEWARE_INTERACTION_KEY = MIDDLEWARE_INTERACTION_KEY;
|
|
118
|
+
exports.MinimalInteractionBindingSchema = MinimalInteractionBindingSchema;
|
|
119
|
+
exports.createAttestationReceiptClaims = createAttestationReceiptClaims;
|
|
120
|
+
exports.isAttestationOnly = isAttestationOnly;
|
|
121
|
+
exports.isAttestationReceiptClaims = isAttestationReceiptClaims;
|
|
122
|
+
exports.isMinimalInteractionBinding = isMinimalInteractionBinding;
|
|
123
|
+
exports.isPaymentReceipt = isPaymentReceipt;
|
|
124
|
+
exports.validateAttestationReceiptClaims = validateAttestationReceiptClaims;
|
|
125
|
+
exports.validateMinimalInteractionBinding = validateMinimalInteractionBinding;
|
|
126
|
+
//# sourceMappingURL=attestation-receipt.cjs.map
|
|
127
|
+
//# sourceMappingURL=attestation-receipt.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/attestation-receipt.ts"],"names":["z"],"mappings":";;;;;AA6BO,IAAM,wBAAA,GAA2B;AAQjC,IAAM,0BAAA,GAA6B;AAKnC,IAAM,kBAAA,GAAqB;AAAA;AAAA,EAEhC,eAAA,EAAiB,IAAA;AAAA;AAAA,EAEjB,iBAAA,EAAmB,IAAA;AAAA;AAAA,EAEnB,gBAAA,EAAkB,GAAA;AAAA;AAAA,EAElB,aAAA,EAAe,IAAA;AAAA;AAAA,EAEf,eAAA,EAAiB,EAAA;AAAA;AAAA,EAEjB,aAAA,EAAe,GAAA;AAAA;AAAA,EAEf,aAAA,EAAe;AACjB;AASA,IAAM,WAAWA,KAAA,CACd,MAAA,EAAO,CACP,GAAA,GACA,GAAA,CAAI,kBAAA,CAAmB,eAAe,CAAA,CACtC,OAAO,CAAC,GAAA,KAAQ,IAAI,UAAA,CAAW,UAAU,GAAG,mBAAmB,CAAA;AAKlE,IAAM,MAAA,GAASA,KAAA,CACZ,MAAA,EAAO,CACP,KAAA;AAAA,EACC,wEAAA;AAAA,EACA;AACF,CAAA;AAWK,IAAM,+BAAA,GAAkCA,MAC5C,MAAA,CAAO;AAAA;AAAA,EAEN,QAAQA,KAAA,CACL,MAAA,EAAO,CACP,GAAA,CAAI,CAAC,CAAA,CACL,GAAA,CAAI,kBAAA,CAAmB,eAAe,EACtC,SAAA,CAAU,CAAC,CAAA,KAAM,CAAA,CAAE,aAAa,CAAA;AAAA;AAAA,EAEnC,IAAA,EAAMA,MAAE,MAAA,EAAO,CAAE,IAAI,CAAC,CAAA,CAAE,GAAA,CAAI,kBAAA,CAAmB,aAAa,CAAA;AAAA;AAAA,EAE5D,MAAA,EAAQA,KAAA,CACL,MAAA,EAAO,CACP,GAAA,EAAI,CACJ,GAAA,CAAI,kBAAA,CAAmB,aAAa,CAAA,CACpC,GAAA,CAAI,kBAAA,CAAmB,aAAa;AACzC,CAAC,EACA,MAAA;AAOI,IAAM,2BAAA,GAA8BA,MAAE,MAAA,CAAOA,KAAA,CAAE,QAAO,EAAGA,KAAA,CAAE,SAAS;AASpE,IAAM,8BAAA,GAAiCA,MAC3C,MAAA,CAAO;AAAA;AAAA,EAEN,GAAA,EAAK,QAAA;AAAA;AAAA,EAEL,GAAA,EAAK,QAAA;AAAA;AAAA,EAEL,KAAKA,KAAA,CAAE,MAAA,EAAO,CAAE,GAAA,GAAM,WAAA,EAAY;AAAA;AAAA,EAElC,KAAKA,KAAA,CAAE,MAAA,EAAO,CAAE,GAAA,GAAM,WAAA,EAAY;AAAA;AAAA,EAElC,GAAA,EAAK,MAAA;AAAA;AAAA,EAEL,GAAA,EAAKA,MAAE,MAAA,EAAO,CAAE,IAAI,kBAAA,CAAmB,gBAAgB,EAAE,QAAA,EAAS;AAAA;AAAA,EAElE,GAAA,EAAK,4BAA4B,QAAA;AACnC,CAAC,EACA,MAAA;AA6BI,SAAS,iCAAiC,KAAA,EAA6C;AAC5F,EAAA,MAAM,MAAA,GAAS,8BAAA,CAA+B,SAAA,CAAU,KAAK,CAAA;AAC7D,EAAA,IAAI,OAAO,OAAA,EAAS;AAClB,IAAA,OAAO,EAAE,OAAO,IAAA,EAAK;AAAA,EACvB;AACA,EAAA,MAAM,UAAA,GAAa,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA;AACxC,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,KAAA;AAAA,IACP,UAAA,EAAY,8BAAA;AAAA,IACZ,aAAA,EAAe,YAAY,OAAA,IAAW;AAAA,GACxC;AACF;AAQO,SAAS,2BAA2B,MAAA,EAAqD;AAC9F,EAAA,OAAO,8BAAA,CAA+B,SAAA,CAAU,MAAM,CAAA,CAAE,OAAA;AAC1D;AAQO,SAAS,kCAAkC,KAAA,EAA6C;AAC7F,EAAA,MAAM,MAAA,GAAS,+BAAA,CAAgC,SAAA,CAAU,KAAK,CAAA;AAC9D,EAAA,IAAI,OAAO,OAAA,EAAS;AAClB,IAAA,OAAO,EAAE,OAAO,IAAA,EAAK;AAAA,EACvB;AACA,EAAA,MAAM,UAAA,GAAa,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA;AACxC,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,KAAA;AAAA,IACP,UAAA,EAAY,mCAAA;AAAA,IACZ,aAAA,EAAe,YAAY,OAAA,IAAW;AAAA,GACxC;AACF;AAQO,SAAS,4BACd,OAAA,EACsC;AACtC,EAAA,OAAO,+BAAA,CAAgC,SAAA,CAAU,OAAO,CAAA,CAAE,OAAA;AAC5D;AAiCO,SAAS,+BACd,MAAA,EAC0B;AAC1B,EAAA,MAAM,MAAM,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,GAAA,KAAQ,GAAI,CAAA;AACxC,EAAA,MAAM,SAAA,GAAY,OAAO,SAAA,IAAa,GAAA;AAItC,EAAA,IAAI,mBAAmB,MAAA,CAAO,MAAA;AAC9B,EAAA,OAAO,gBAAA,CAAiB,QAAA,CAAS,GAAG,CAAA,EAAG;AACrC,IAAA,gBAAA,GAAmB,gBAAA,CAAiB,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA;AAAA,EACjD;AAGA,EAAA,MAAM,GAAA,GAA+B,EAAE,GAAG,MAAA,CAAO,UAAA,EAAW;AAC5D,EAAA,IAAI,OAAO,WAAA,EAAa;AACtB,IAAA,GAAA,CAAI,0BAA0B,IAAI,MAAA,CAAO,WAAA;AAAA,EAC3C;AAEA,EAAA,MAAM,MAAA,GAAmC;AAAA,IACvC,GAAA,EAAK,gBAAA;AAAA,IACL,KAAK,MAAA,CAAO,QAAA;AAAA,IACZ,GAAA,EAAK,GAAA;AAAA,IACL,KAAK,GAAA,GAAM,SAAA;AAAA,IACX,KAAK,MAAA,CAAO,GAAA;AAAA,IACZ,GAAI,MAAA,CAAO,GAAA,IAAO,EAAE,GAAA,EAAK,OAAO,GAAA,EAAI;AAAA,IACpC,GAAI,OAAO,IAAA,CAAK,GAAG,EAAE,MAAA,GAAS,CAAA,IAAK,EAAE,GAAA;AAAI,GAC3C;AAEA,EAAA,OAAO,8BAAA,CAA+B,MAAM,MAAM,CAAA;AACpD;AAeO,SAAS,kBAAkB,MAAA,EAA0C;AAC1E,EAAA,OAAO,EAAE,KAAA,IAAS,MAAA,CAAA,IAAW,EAAE,KAAA,IAAS,MAAA,CAAA,IAAW,EAAE,SAAA,IAAa,MAAA,CAAA;AACpE;AAQO,SAAS,iBAAiB,MAAA,EAA0C;AACzE,EAAA,OAAO,KAAA,IAAS,MAAA,IAAU,KAAA,IAAS,MAAA,IAAU,SAAA,IAAa,MAAA;AAC5D","file":"attestation-receipt.cjs","sourcesContent":["/**\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"]}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
// src/attestation-receipt.ts
|
|
4
|
+
var ATTESTATION_RECEIPT_TYPE = "peac/attestation-receipt";
|
|
5
|
+
var MIDDLEWARE_INTERACTION_KEY = "org.peacprotocol/middleware-interaction@0.1";
|
|
6
|
+
var ATTESTATION_LIMITS = {
|
|
7
|
+
/** Maximum issuer URL length */
|
|
8
|
+
maxIssuerLength: 2048,
|
|
9
|
+
/** Maximum audience URL length */
|
|
10
|
+
maxAudienceLength: 2048,
|
|
11
|
+
/** Maximum subject length */
|
|
12
|
+
maxSubjectLength: 256,
|
|
13
|
+
/** Maximum path length in interaction binding */
|
|
14
|
+
maxPathLength: 2048,
|
|
15
|
+
/** Maximum method length */
|
|
16
|
+
maxMethodLength: 16,
|
|
17
|
+
/** Maximum HTTP status code */
|
|
18
|
+
maxStatusCode: 599,
|
|
19
|
+
/** Minimum HTTP status code */
|
|
20
|
+
minStatusCode: 100
|
|
21
|
+
};
|
|
22
|
+
var httpsUrl = z.string().url().max(ATTESTATION_LIMITS.maxIssuerLength).refine((url) => url.startsWith("https://"), "Must be HTTPS URL");
|
|
23
|
+
var uuidv7 = z.string().regex(
|
|
24
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
|
25
|
+
"Must be UUIDv7 format"
|
|
26
|
+
);
|
|
27
|
+
var MinimalInteractionBindingSchema = z.object({
|
|
28
|
+
/** HTTP method (uppercase, e.g., GET, POST) */
|
|
29
|
+
method: z.string().min(1).max(ATTESTATION_LIMITS.maxMethodLength).transform((m) => m.toUpperCase()),
|
|
30
|
+
/** Request path (no query string by default) */
|
|
31
|
+
path: z.string().min(1).max(ATTESTATION_LIMITS.maxPathLength),
|
|
32
|
+
/** HTTP response status code */
|
|
33
|
+
status: z.number().int().min(ATTESTATION_LIMITS.minStatusCode).max(ATTESTATION_LIMITS.maxStatusCode)
|
|
34
|
+
}).strict();
|
|
35
|
+
var AttestationExtensionsSchema = z.record(z.string(), z.unknown());
|
|
36
|
+
var AttestationReceiptClaimsSchema = z.object({
|
|
37
|
+
/** Issuer URL (normalized, no trailing slash) */
|
|
38
|
+
iss: httpsUrl,
|
|
39
|
+
/** Audience URL */
|
|
40
|
+
aud: httpsUrl,
|
|
41
|
+
/** Issued at (Unix seconds) */
|
|
42
|
+
iat: z.number().int().nonnegative(),
|
|
43
|
+
/** Expiration (Unix seconds) */
|
|
44
|
+
exp: z.number().int().nonnegative(),
|
|
45
|
+
/** Receipt ID (UUIDv7) */
|
|
46
|
+
rid: uuidv7,
|
|
47
|
+
/** Subject identifier (optional) */
|
|
48
|
+
sub: z.string().max(ATTESTATION_LIMITS.maxSubjectLength).optional(),
|
|
49
|
+
/** Extensions (optional) */
|
|
50
|
+
ext: AttestationExtensionsSchema.optional()
|
|
51
|
+
}).strict();
|
|
52
|
+
function validateAttestationReceiptClaims(input) {
|
|
53
|
+
const result = AttestationReceiptClaimsSchema.safeParse(input);
|
|
54
|
+
if (result.success) {
|
|
55
|
+
return { valid: true };
|
|
56
|
+
}
|
|
57
|
+
const firstIssue = result.error.issues[0];
|
|
58
|
+
return {
|
|
59
|
+
valid: false,
|
|
60
|
+
error_code: "E_ATTESTATION_INVALID_CLAIMS",
|
|
61
|
+
error_message: firstIssue?.message || "Invalid attestation receipt claims"
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function isAttestationReceiptClaims(claims) {
|
|
65
|
+
return AttestationReceiptClaimsSchema.safeParse(claims).success;
|
|
66
|
+
}
|
|
67
|
+
function validateMinimalInteractionBinding(input) {
|
|
68
|
+
const result = MinimalInteractionBindingSchema.safeParse(input);
|
|
69
|
+
if (result.success) {
|
|
70
|
+
return { valid: true };
|
|
71
|
+
}
|
|
72
|
+
const firstIssue = result.error.issues[0];
|
|
73
|
+
return {
|
|
74
|
+
valid: false,
|
|
75
|
+
error_code: "E_ATTESTATION_INVALID_INTERACTION",
|
|
76
|
+
error_message: firstIssue?.message || "Invalid interaction binding"
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function isMinimalInteractionBinding(binding) {
|
|
80
|
+
return MinimalInteractionBindingSchema.safeParse(binding).success;
|
|
81
|
+
}
|
|
82
|
+
function createAttestationReceiptClaims(params) {
|
|
83
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
84
|
+
const expiresIn = params.expiresIn ?? 300;
|
|
85
|
+
let normalizedIssuer = params.issuer;
|
|
86
|
+
while (normalizedIssuer.endsWith("/")) {
|
|
87
|
+
normalizedIssuer = normalizedIssuer.slice(0, -1);
|
|
88
|
+
}
|
|
89
|
+
const ext = { ...params.extensions };
|
|
90
|
+
if (params.interaction) {
|
|
91
|
+
ext[MIDDLEWARE_INTERACTION_KEY] = params.interaction;
|
|
92
|
+
}
|
|
93
|
+
const claims = {
|
|
94
|
+
iss: normalizedIssuer,
|
|
95
|
+
aud: params.audience,
|
|
96
|
+
iat: now,
|
|
97
|
+
exp: now + expiresIn,
|
|
98
|
+
rid: params.rid,
|
|
99
|
+
...params.sub && { sub: params.sub },
|
|
100
|
+
...Object.keys(ext).length > 0 && { ext }
|
|
101
|
+
};
|
|
102
|
+
return AttestationReceiptClaimsSchema.parse(claims);
|
|
103
|
+
}
|
|
104
|
+
function isAttestationOnly(claims) {
|
|
105
|
+
return !("amt" in claims) && !("cur" in claims) && !("payment" in claims);
|
|
106
|
+
}
|
|
107
|
+
function isPaymentReceipt(claims) {
|
|
108
|
+
return "amt" in claims && "cur" in claims && "payment" in claims;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export { ATTESTATION_LIMITS, ATTESTATION_RECEIPT_TYPE, AttestationExtensionsSchema, AttestationReceiptClaimsSchema, MIDDLEWARE_INTERACTION_KEY, MinimalInteractionBindingSchema, createAttestationReceiptClaims, isAttestationOnly, isAttestationReceiptClaims, isMinimalInteractionBinding, isPaymentReceipt, validateAttestationReceiptClaims, validateMinimalInteractionBinding };
|
|
112
|
+
//# sourceMappingURL=attestation-receipt.mjs.map
|
|
113
|
+
//# sourceMappingURL=attestation-receipt.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/attestation-receipt.ts"],"names":[],"mappings":"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","file":"attestation-receipt.mjs","sourcesContent":["/**\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"]}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var zod = require('zod');
|
|
4
|
+
|
|
5
|
+
// src/attribution.ts
|
|
6
|
+
function isPlainObject(value) {
|
|
7
|
+
if (value === null || typeof value !== "object") {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
const proto = Object.getPrototypeOf(value);
|
|
11
|
+
return proto === Object.prototype || proto === null;
|
|
12
|
+
}
|
|
13
|
+
var JsonNumberSchema = zod.z.number().finite();
|
|
14
|
+
var JsonPrimitiveSchema = zod.z.union([zod.z.string(), JsonNumberSchema, zod.z.boolean(), zod.z.null()]);
|
|
15
|
+
var PlainObjectSchema = zod.z.unknown().refine(isPlainObject, {
|
|
16
|
+
message: "Expected plain object, received non-plain object (Date, Map, Set, or class instance)"
|
|
17
|
+
});
|
|
18
|
+
var JsonValueSchema = zod.z.lazy(
|
|
19
|
+
() => zod.z.union([
|
|
20
|
+
JsonPrimitiveSchema,
|
|
21
|
+
zod.z.array(JsonValueSchema),
|
|
22
|
+
// Plain object check then record validation
|
|
23
|
+
PlainObjectSchema.transform((obj) => obj).pipe(
|
|
24
|
+
zod.z.record(JsonValueSchema)
|
|
25
|
+
)
|
|
26
|
+
])
|
|
27
|
+
);
|
|
28
|
+
PlainObjectSchema.transform(
|
|
29
|
+
(obj) => obj
|
|
30
|
+
).pipe(zod.z.record(JsonValueSchema));
|
|
31
|
+
zod.z.array(JsonValueSchema);
|
|
32
|
+
|
|
33
|
+
// src/attribution.ts
|
|
34
|
+
var ATTRIBUTION_LIMITS = {
|
|
35
|
+
/** Maximum sources per attestation */
|
|
36
|
+
maxSources: 100,
|
|
37
|
+
/** Maximum chain resolution depth */
|
|
38
|
+
maxDepth: 8,
|
|
39
|
+
/** Maximum attestation size in bytes (64KB) */
|
|
40
|
+
maxAttestationSize: 65536,
|
|
41
|
+
/** Per-hop resolution timeout in milliseconds */
|
|
42
|
+
resolutionTimeout: 5e3,
|
|
43
|
+
/** Maximum receipt reference length */
|
|
44
|
+
maxReceiptRefLength: 2048,
|
|
45
|
+
/** Maximum model ID length */
|
|
46
|
+
maxModelIdLength: 256
|
|
47
|
+
};
|
|
48
|
+
var HashAlgorithmSchema = zod.z.literal("sha-256");
|
|
49
|
+
var HashEncodingSchema = zod.z.literal("base64url");
|
|
50
|
+
var ContentHashSchema = zod.z.object({
|
|
51
|
+
/** Hash algorithm (REQUIRED, must be 'sha-256') */
|
|
52
|
+
alg: HashAlgorithmSchema,
|
|
53
|
+
/** Base64url-encoded hash value without padding (REQUIRED, 43 chars for SHA-256) */
|
|
54
|
+
value: zod.z.string().min(43).max(43).regex(/^[A-Za-z0-9_-]+$/, "Invalid base64url characters"),
|
|
55
|
+
/** Encoding format (REQUIRED, must be 'base64url') */
|
|
56
|
+
enc: HashEncodingSchema
|
|
57
|
+
}).strict();
|
|
58
|
+
var AttributionUsageSchema = zod.z.enum([
|
|
59
|
+
"training_input",
|
|
60
|
+
"rag_context",
|
|
61
|
+
"direct_reference",
|
|
62
|
+
"synthesis_source",
|
|
63
|
+
"embedding_source"
|
|
64
|
+
]);
|
|
65
|
+
var ATTRIBUTION_USAGES = [
|
|
66
|
+
"training_input",
|
|
67
|
+
"rag_context",
|
|
68
|
+
"direct_reference",
|
|
69
|
+
"synthesis_source",
|
|
70
|
+
"embedding_source"
|
|
71
|
+
];
|
|
72
|
+
var DerivationTypeSchema = zod.z.enum([
|
|
73
|
+
"training",
|
|
74
|
+
"inference",
|
|
75
|
+
"rag",
|
|
76
|
+
"synthesis",
|
|
77
|
+
"embedding"
|
|
78
|
+
]);
|
|
79
|
+
var DERIVATION_TYPES = ["training", "inference", "rag", "synthesis", "embedding"];
|
|
80
|
+
var ReceiptRefSchema = zod.z.string().min(1).max(ATTRIBUTION_LIMITS.maxReceiptRefLength).refine(
|
|
81
|
+
(ref) => {
|
|
82
|
+
if (ref.startsWith("jti:")) return true;
|
|
83
|
+
if (ref.startsWith("https://") || ref.startsWith("http://")) return true;
|
|
84
|
+
if (ref.startsWith("urn:peac:receipt:")) return true;
|
|
85
|
+
return false;
|
|
86
|
+
},
|
|
87
|
+
{ message: "Invalid receipt reference format. Must be jti:{id}, URL, or urn:peac:receipt:{id}" }
|
|
88
|
+
);
|
|
89
|
+
var AttributionSourceSchema = zod.z.object({
|
|
90
|
+
/** Reference to source PEAC receipt (REQUIRED) */
|
|
91
|
+
receipt_ref: ReceiptRefSchema,
|
|
92
|
+
/**
|
|
93
|
+
* Issuer of the referenced receipt (OPTIONAL but RECOMMENDED for jti: refs).
|
|
94
|
+
*
|
|
95
|
+
* Required for cross-issuer resolution when receipt_ref is jti:{id} format.
|
|
96
|
+
* Not needed for URL-based references which are self-resolvable.
|
|
97
|
+
* Used to construct resolution URL: {receipt_issuer}/.well-known/peac/receipts/{id}
|
|
98
|
+
*/
|
|
99
|
+
receipt_issuer: zod.z.string().url().max(2048).optional(),
|
|
100
|
+
/** Hash of source content (OPTIONAL) */
|
|
101
|
+
content_hash: ContentHashSchema.optional(),
|
|
102
|
+
/** Hash of used excerpt (OPTIONAL, content-minimizing, not privacy-preserving for short text) */
|
|
103
|
+
excerpt_hash: ContentHashSchema.optional(),
|
|
104
|
+
/** How the source was used (REQUIRED) */
|
|
105
|
+
usage: AttributionUsageSchema,
|
|
106
|
+
/** Relative contribution weight 0.0-1.0 (OPTIONAL) */
|
|
107
|
+
weight: zod.z.number().min(0).max(1).optional()
|
|
108
|
+
}).strict();
|
|
109
|
+
var AttributionEvidenceSchema = zod.z.object({
|
|
110
|
+
/** Array of attribution sources (REQUIRED, 1-100 sources) */
|
|
111
|
+
sources: zod.z.array(AttributionSourceSchema).min(1).max(ATTRIBUTION_LIMITS.maxSources),
|
|
112
|
+
/** Type of derivation (REQUIRED) */
|
|
113
|
+
derivation_type: DerivationTypeSchema,
|
|
114
|
+
/** Hash of derived output (OPTIONAL) */
|
|
115
|
+
output_hash: ContentHashSchema.optional(),
|
|
116
|
+
/** Model identifier (OPTIONAL) */
|
|
117
|
+
model_id: zod.z.string().max(ATTRIBUTION_LIMITS.maxModelIdLength).optional(),
|
|
118
|
+
/** Inference provider URL (OPTIONAL) */
|
|
119
|
+
inference_provider: zod.z.string().url().max(2048).optional(),
|
|
120
|
+
/** Session correlation ID (OPTIONAL) */
|
|
121
|
+
session_id: zod.z.string().max(256).optional(),
|
|
122
|
+
/** Additional type-specific metadata (OPTIONAL) */
|
|
123
|
+
metadata: zod.z.record(zod.z.string(), JsonValueSchema).optional()
|
|
124
|
+
}).strict();
|
|
125
|
+
var ATTRIBUTION_TYPE = "peac/attribution";
|
|
126
|
+
var AttributionAttestationSchema = zod.z.object({
|
|
127
|
+
/** Attestation type (MUST be 'peac/attribution') */
|
|
128
|
+
type: zod.z.literal(ATTRIBUTION_TYPE),
|
|
129
|
+
/** Issuer of the attestation (inference provider, platform) */
|
|
130
|
+
issuer: zod.z.string().min(1).max(2048),
|
|
131
|
+
/** When the attestation was issued (RFC 3339) */
|
|
132
|
+
issued_at: zod.z.string().datetime(),
|
|
133
|
+
/** When the attestation expires (RFC 3339, OPTIONAL) */
|
|
134
|
+
expires_at: zod.z.string().datetime().optional(),
|
|
135
|
+
/** Reference to external verification endpoint (OPTIONAL) */
|
|
136
|
+
ref: zod.z.string().url().max(2048).optional(),
|
|
137
|
+
/** Attribution evidence */
|
|
138
|
+
evidence: AttributionEvidenceSchema
|
|
139
|
+
}).strict();
|
|
140
|
+
function validateContentHash(data) {
|
|
141
|
+
const result = ContentHashSchema.safeParse(data);
|
|
142
|
+
if (result.success) {
|
|
143
|
+
return { ok: true, value: result.data };
|
|
144
|
+
}
|
|
145
|
+
return { ok: false, error: result.error.message };
|
|
146
|
+
}
|
|
147
|
+
function validateAttributionSource(data) {
|
|
148
|
+
const result = AttributionSourceSchema.safeParse(data);
|
|
149
|
+
if (result.success) {
|
|
150
|
+
return { ok: true, value: result.data };
|
|
151
|
+
}
|
|
152
|
+
return { ok: false, error: result.error.message };
|
|
153
|
+
}
|
|
154
|
+
function validateAttributionAttestation(data) {
|
|
155
|
+
const result = AttributionAttestationSchema.safeParse(data);
|
|
156
|
+
if (result.success) {
|
|
157
|
+
return { ok: true, value: result.data };
|
|
158
|
+
}
|
|
159
|
+
return { ok: false, error: result.error.message };
|
|
160
|
+
}
|
|
161
|
+
function isAttributionAttestation(attestation) {
|
|
162
|
+
return attestation.type === ATTRIBUTION_TYPE;
|
|
163
|
+
}
|
|
164
|
+
function createAttributionAttestation(params) {
|
|
165
|
+
const evidence = {
|
|
166
|
+
sources: params.sources,
|
|
167
|
+
derivation_type: params.derivation_type
|
|
168
|
+
};
|
|
169
|
+
if (params.output_hash) {
|
|
170
|
+
evidence.output_hash = params.output_hash;
|
|
171
|
+
}
|
|
172
|
+
if (params.model_id) {
|
|
173
|
+
evidence.model_id = params.model_id;
|
|
174
|
+
}
|
|
175
|
+
if (params.inference_provider) {
|
|
176
|
+
evidence.inference_provider = params.inference_provider;
|
|
177
|
+
}
|
|
178
|
+
if (params.session_id) {
|
|
179
|
+
evidence.session_id = params.session_id;
|
|
180
|
+
}
|
|
181
|
+
if (params.metadata) {
|
|
182
|
+
evidence.metadata = params.metadata;
|
|
183
|
+
}
|
|
184
|
+
const attestation = {
|
|
185
|
+
type: ATTRIBUTION_TYPE,
|
|
186
|
+
issuer: params.issuer,
|
|
187
|
+
issued_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
188
|
+
evidence
|
|
189
|
+
};
|
|
190
|
+
if (params.expires_at) {
|
|
191
|
+
attestation.expires_at = params.expires_at;
|
|
192
|
+
}
|
|
193
|
+
if (params.ref) {
|
|
194
|
+
attestation.ref = params.ref;
|
|
195
|
+
}
|
|
196
|
+
return attestation;
|
|
197
|
+
}
|
|
198
|
+
function isAttributionExpired(attestation, clockSkew = 3e4) {
|
|
199
|
+
if (!attestation.expires_at) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
const expiresAt = new Date(attestation.expires_at).getTime();
|
|
203
|
+
const now = Date.now();
|
|
204
|
+
return expiresAt < now - clockSkew;
|
|
205
|
+
}
|
|
206
|
+
function isAttributionNotYetValid(attestation, clockSkew = 3e4) {
|
|
207
|
+
const issuedAt = new Date(attestation.issued_at).getTime();
|
|
208
|
+
const now = Date.now();
|
|
209
|
+
return issuedAt > now + clockSkew;
|
|
210
|
+
}
|
|
211
|
+
function computeTotalWeight(sources) {
|
|
212
|
+
const weights = sources.filter((s) => s.weight !== void 0).map((s) => s.weight);
|
|
213
|
+
if (weights.length === 0) {
|
|
214
|
+
return void 0;
|
|
215
|
+
}
|
|
216
|
+
return weights.reduce((sum, w) => sum + w, 0);
|
|
217
|
+
}
|
|
218
|
+
function detectCycleInSources(sources, visited = /* @__PURE__ */ new Set()) {
|
|
219
|
+
for (const source of sources) {
|
|
220
|
+
if (visited.has(source.receipt_ref)) {
|
|
221
|
+
return source.receipt_ref;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return void 0;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
exports.ATTRIBUTION_LIMITS = ATTRIBUTION_LIMITS;
|
|
228
|
+
exports.ATTRIBUTION_TYPE = ATTRIBUTION_TYPE;
|
|
229
|
+
exports.ATTRIBUTION_USAGES = ATTRIBUTION_USAGES;
|
|
230
|
+
exports.AttributionAttestationSchema = AttributionAttestationSchema;
|
|
231
|
+
exports.AttributionEvidenceSchema = AttributionEvidenceSchema;
|
|
232
|
+
exports.AttributionSourceSchema = AttributionSourceSchema;
|
|
233
|
+
exports.AttributionUsageSchema = AttributionUsageSchema;
|
|
234
|
+
exports.ContentHashSchema = ContentHashSchema;
|
|
235
|
+
exports.DERIVATION_TYPES = DERIVATION_TYPES;
|
|
236
|
+
exports.DerivationTypeSchema = DerivationTypeSchema;
|
|
237
|
+
exports.HashAlgorithmSchema = HashAlgorithmSchema;
|
|
238
|
+
exports.HashEncodingSchema = HashEncodingSchema;
|
|
239
|
+
exports.computeTotalWeight = computeTotalWeight;
|
|
240
|
+
exports.createAttributionAttestation = createAttributionAttestation;
|
|
241
|
+
exports.detectCycleInSources = detectCycleInSources;
|
|
242
|
+
exports.isAttributionAttestation = isAttributionAttestation;
|
|
243
|
+
exports.isAttributionExpired = isAttributionExpired;
|
|
244
|
+
exports.isAttributionNotYetValid = isAttributionNotYetValid;
|
|
245
|
+
exports.validateAttributionAttestation = validateAttributionAttestation;
|
|
246
|
+
exports.validateAttributionSource = validateAttributionSource;
|
|
247
|
+
exports.validateContentHash = validateContentHash;
|
|
248
|
+
//# sourceMappingURL=attribution.cjs.map
|
|
249
|
+
//# sourceMappingURL=attribution.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/json.ts","../src/attribution.ts"],"names":["z"],"mappings":"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","file":"attribution.cjs","sourcesContent":["/**\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 * Attribution Attestation Types and Validators (v0.9.26+)\n *\n * Provides content derivation and usage proof for PEAC receipts,\n * enabling chain tracking and compliance artifacts.\n *\n * @see docs/specs/ATTRIBUTION.md for normative specification\n */\nimport { z } from 'zod';\nimport type { JsonValue } from '@peac/kernel';\nimport { JsonValueSchema } from './json';\n\n// =============================================================================\n// ATTRIBUTION LIMITS (v0.9.26+)\n// =============================================================================\n\n/**\n * Attribution limits for DoS protection and verification feasibility.\n *\n * These are implementation safety limits, not protocol constraints.\n */\nexport const ATTRIBUTION_LIMITS = {\n /** Maximum sources per attestation */\n maxSources: 100,\n /** Maximum chain resolution depth */\n maxDepth: 8,\n /** Maximum attestation size in bytes (64KB) */\n maxAttestationSize: 65536,\n /** Per-hop resolution timeout in milliseconds */\n resolutionTimeout: 5000,\n /** Maximum receipt reference length */\n maxReceiptRefLength: 2048,\n /** Maximum model ID length */\n maxModelIdLength: 256,\n} as const;\n\n// =============================================================================\n// CONTENT HASH (v0.9.26+)\n// =============================================================================\n\n/**\n * Supported hash algorithms for content hashing.\n * Only sha-256 is supported in v0.9.26.\n */\nexport const HashAlgorithmSchema = z.literal('sha-256');\nexport type HashAlgorithm = z.infer<typeof HashAlgorithmSchema>;\n\n/**\n * Supported encoding formats for hash values.\n */\nexport const HashEncodingSchema = z.literal('base64url');\nexport type HashEncoding = z.infer<typeof HashEncodingSchema>;\n\n/**\n * ContentHash - deterministic content identification.\n *\n * Provides cryptographic verification of content identity using SHA-256.\n * The hash value is base64url-encoded without padding (RFC 4648 Section 5).\n *\n * @example\n * ```typescript\n * const hash: ContentHash = {\n * alg: 'sha-256',\n * value: 'n4bQgYhMfWWaL28IoEbM8Qa8jG7x0QXJZJqL-w_zZdA',\n * enc: 'base64url',\n * };\n * ```\n */\nexport const ContentHashSchema = z\n .object({\n /** Hash algorithm (REQUIRED, must be 'sha-256') */\n alg: HashAlgorithmSchema,\n\n /** Base64url-encoded hash value without padding (REQUIRED, 43 chars for SHA-256) */\n value: z\n .string()\n .min(43)\n .max(43)\n .regex(/^[A-Za-z0-9_-]+$/, 'Invalid base64url characters'),\n\n /** Encoding format (REQUIRED, must be 'base64url') */\n enc: HashEncodingSchema,\n })\n .strict();\nexport type ContentHash = z.infer<typeof ContentHashSchema>;\n\n// =============================================================================\n// ATTRIBUTION USAGE (v0.9.26+)\n// =============================================================================\n\n/**\n * How source content was used in derivation.\n *\n * - 'training_input': Used to train a model\n * - 'rag_context': Retrieved for RAG context\n * - 'direct_reference': Directly quoted or referenced\n * - 'synthesis_source': Combined with other sources to create new content\n * - 'embedding_source': Used to create embeddings/vectors\n */\nexport const AttributionUsageSchema = z.enum([\n 'training_input',\n 'rag_context',\n 'direct_reference',\n 'synthesis_source',\n 'embedding_source',\n]);\nexport type AttributionUsage = z.infer<typeof AttributionUsageSchema>;\n\n/**\n * Array of valid attribution usage types for runtime checks.\n */\nexport const ATTRIBUTION_USAGES = [\n 'training_input',\n 'rag_context',\n 'direct_reference',\n 'synthesis_source',\n 'embedding_source',\n] as const;\n\n// =============================================================================\n// DERIVATION TYPE (v0.9.26+)\n// =============================================================================\n\n/**\n * Type of content derivation.\n *\n * - 'training': Model training or fine-tuning\n * - 'inference': Runtime inference with RAG/grounding\n * - 'rag': Retrieval-augmented generation\n * - 'synthesis': Multi-source content synthesis\n * - 'embedding': Vector embedding generation\n */\nexport const DerivationTypeSchema = z.enum([\n 'training',\n 'inference',\n 'rag',\n 'synthesis',\n 'embedding',\n]);\nexport type DerivationType = z.infer<typeof DerivationTypeSchema>;\n\n/**\n * Array of valid derivation types for runtime checks.\n */\nexport const DERIVATION_TYPES = ['training', 'inference', 'rag', 'synthesis', 'embedding'] as const;\n\n// =============================================================================\n// ATTRIBUTION SOURCE (v0.9.26+)\n// =============================================================================\n\n/**\n * Receipt reference format validation.\n *\n * Valid formats:\n * - jti:{receipt_id} - Direct receipt identifier\n * - https://... - Resolvable receipt URL\n * - urn:peac:receipt:{id} - URN-formatted identifier\n */\nconst ReceiptRefSchema = z\n .string()\n .min(1)\n .max(ATTRIBUTION_LIMITS.maxReceiptRefLength)\n .refine(\n (ref) => {\n // jti: prefix\n if (ref.startsWith('jti:')) return true;\n // URL\n if (ref.startsWith('https://') || ref.startsWith('http://')) return true;\n // URN\n if (ref.startsWith('urn:peac:receipt:')) return true;\n return false;\n },\n { message: 'Invalid receipt reference format. Must be jti:{id}, URL, or urn:peac:receipt:{id}' }\n );\n\n/**\n * AttributionSource - links to a source receipt and describes how content was used.\n *\n * For cross-issuer resolution, include `receipt_issuer` when using `jti:*` references.\n * URL-based references (`https://...`) are self-resolvable.\n *\n * @example\n * ```typescript\n * const source: AttributionSource = {\n * receipt_ref: 'jti:rec_abc123def456',\n * receipt_issuer: 'https://publisher.example.com',\n * content_hash: { alg: 'sha-256', value: '...', enc: 'base64url' },\n * usage: 'rag_context',\n * weight: 0.3,\n * };\n * ```\n */\nexport const AttributionSourceSchema = z\n .object({\n /** Reference to source PEAC receipt (REQUIRED) */\n receipt_ref: ReceiptRefSchema,\n\n /**\n * Issuer of the referenced receipt (OPTIONAL but RECOMMENDED for jti: refs).\n *\n * Required for cross-issuer resolution when receipt_ref is jti:{id} format.\n * Not needed for URL-based references which are self-resolvable.\n * Used to construct resolution URL: {receipt_issuer}/.well-known/peac/receipts/{id}\n */\n receipt_issuer: z.string().url().max(2048).optional(),\n\n /** Hash of source content (OPTIONAL) */\n content_hash: ContentHashSchema.optional(),\n\n /** Hash of used excerpt (OPTIONAL, content-minimizing, not privacy-preserving for short text) */\n excerpt_hash: ContentHashSchema.optional(),\n\n /** How the source was used (REQUIRED) */\n usage: AttributionUsageSchema,\n\n /** Relative contribution weight 0.0-1.0 (OPTIONAL) */\n weight: z.number().min(0).max(1).optional(),\n })\n .strict();\nexport type AttributionSource = z.infer<typeof AttributionSourceSchema>;\n\n// =============================================================================\n// ATTRIBUTION EVIDENCE (v0.9.26+)\n// =============================================================================\n\n/**\n * AttributionEvidence - the payload of an AttributionAttestation.\n *\n * Contains the sources, derivation type, and optional output metadata.\n */\nexport const AttributionEvidenceSchema = z\n .object({\n /** Array of attribution sources (REQUIRED, 1-100 sources) */\n sources: z.array(AttributionSourceSchema).min(1).max(ATTRIBUTION_LIMITS.maxSources),\n\n /** Type of derivation (REQUIRED) */\n derivation_type: DerivationTypeSchema,\n\n /** Hash of derived output (OPTIONAL) */\n output_hash: ContentHashSchema.optional(),\n\n /** Model identifier (OPTIONAL) */\n model_id: z.string().max(ATTRIBUTION_LIMITS.maxModelIdLength).optional(),\n\n /** Inference provider URL (OPTIONAL) */\n inference_provider: z.string().url().max(2048).optional(),\n\n /** Session correlation ID (OPTIONAL) */\n session_id: z.string().max(256).optional(),\n\n /** Additional type-specific metadata (OPTIONAL) */\n metadata: z.record(z.string(), JsonValueSchema).optional(),\n })\n .strict();\nexport type AttributionEvidence = z.infer<typeof AttributionEvidenceSchema>;\n\n// =============================================================================\n// ATTRIBUTION ATTESTATION (v0.9.26+)\n// =============================================================================\n\n/**\n * Attestation type literal for attribution\n */\nexport const ATTRIBUTION_TYPE = 'peac/attribution' as const;\n\n/**\n * AttributionAttestation - proves content derivation and usage.\n *\n * This attestation provides cryptographic evidence that content was derived\n * from specific sources, enabling chain tracking and compliance.\n *\n * @example\n * ```typescript\n * const attestation: AttributionAttestation = {\n * type: 'peac/attribution',\n * issuer: 'https://ai.example.com',\n * issued_at: '2026-01-04T12:00:00Z',\n * evidence: {\n * sources: [\n * { receipt_ref: 'jti:rec_abc123', usage: 'rag_context', weight: 0.5 },\n * { receipt_ref: 'jti:rec_def456', usage: 'rag_context', weight: 0.5 },\n * ],\n * derivation_type: 'rag',\n * model_id: 'gpt-4',\n * },\n * };\n * ```\n */\nexport const AttributionAttestationSchema = z\n .object({\n /** Attestation type (MUST be 'peac/attribution') */\n type: z.literal(ATTRIBUTION_TYPE),\n\n /** Issuer of the attestation (inference provider, platform) */\n issuer: z.string().min(1).max(2048),\n\n /** When the attestation was issued (RFC 3339) */\n issued_at: z.string().datetime(),\n\n /** When the attestation expires (RFC 3339, OPTIONAL) */\n expires_at: z.string().datetime().optional(),\n\n /** Reference to external verification endpoint (OPTIONAL) */\n ref: z.string().url().max(2048).optional(),\n\n /** Attribution evidence */\n evidence: AttributionEvidenceSchema,\n })\n .strict();\nexport type AttributionAttestation = z.infer<typeof AttributionAttestationSchema>;\n\n// =============================================================================\n// CHAIN VERIFICATION RESULT (v0.9.26+)\n// =============================================================================\n\n/**\n * Result of chain verification including depth and resolved sources.\n */\nexport interface ChainVerificationResult {\n /** Whether the chain is valid */\n valid: boolean;\n /** Maximum depth encountered in the chain */\n maxDepth: number;\n /** Total number of sources across the chain */\n totalSources: number;\n /** Any cycle detected in the chain */\n cycleDetected?: string;\n /** Error message if validation failed */\n error?: string;\n}\n\n// =============================================================================\n// VALIDATION HELPERS (v0.9.26+)\n// =============================================================================\n\n/**\n * Validate a ContentHash.\n *\n * @param data - Unknown data to validate\n * @returns Result with validated hash or error message\n */\nexport function validateContentHash(\n data: unknown\n): { ok: true; value: ContentHash } | { ok: false; error: string } {\n const result = ContentHashSchema.safeParse(data);\n if (result.success) {\n return { ok: true, value: result.data };\n }\n return { ok: false, error: result.error.message };\n}\n\n/**\n * Validate an AttributionSource.\n *\n * @param data - Unknown data to validate\n * @returns Result with validated source or error message\n */\nexport function validateAttributionSource(\n data: unknown\n): { ok: true; value: AttributionSource } | { ok: false; error: string } {\n const result = AttributionSourceSchema.safeParse(data);\n if (result.success) {\n return { ok: true, value: result.data };\n }\n return { ok: false, error: result.error.message };\n}\n\n/**\n * Validate an AttributionAttestation.\n *\n * @param data - Unknown data to validate\n * @returns Result with validated attestation or error message\n *\n * @example\n * ```typescript\n * const result = validateAttributionAttestation(data);\n * if (result.ok) {\n * console.log('Sources:', result.value.evidence.sources.length);\n * } else {\n * console.error('Validation error:', result.error);\n * }\n * ```\n */\nexport function validateAttributionAttestation(\n data: unknown\n): { ok: true; value: AttributionAttestation } | { ok: false; error: string } {\n const result = AttributionAttestationSchema.safeParse(data);\n if (result.success) {\n return { ok: true, value: result.data };\n }\n return { ok: false, error: result.error.message };\n}\n\n/**\n * Check if an object is an AttributionAttestation.\n *\n * @param attestation - Object with a type field\n * @returns True if the type is 'peac/attribution'\n */\nexport function isAttributionAttestation(attestation: {\n type: string;\n}): attestation is AttributionAttestation {\n return attestation.type === ATTRIBUTION_TYPE;\n}\n\n/**\n * Parameters for creating an AttributionAttestation.\n */\nexport interface CreateAttributionAttestationParams {\n /** Issuer of the attestation */\n issuer: string;\n /** Attribution sources */\n sources: AttributionSource[];\n /** Type of derivation */\n derivation_type: DerivationType;\n /** Hash of derived output (optional) */\n output_hash?: ContentHash;\n /** Model identifier (optional) */\n model_id?: string;\n /** Inference provider URL (optional) */\n inference_provider?: string;\n /** Session correlation ID (optional) */\n session_id?: string;\n /** When the attestation expires (optional) */\n expires_at?: string;\n /** External verification endpoint (optional) */\n ref?: string;\n /** Additional metadata (optional, must be JSON-safe) */\n metadata?: Record<string, JsonValue>;\n}\n\n/**\n * Create an AttributionAttestation with current timestamp.\n *\n * @param params - Attestation parameters\n * @returns A valid AttributionAttestation\n *\n * @example\n * ```typescript\n * const attestation = createAttributionAttestation({\n * issuer: 'https://ai.example.com',\n * sources: [\n * { receipt_ref: 'jti:rec_abc123', usage: 'rag_context' },\n * ],\n * derivation_type: 'rag',\n * model_id: 'gpt-4',\n * });\n * ```\n */\nexport function createAttributionAttestation(\n params: CreateAttributionAttestationParams\n): AttributionAttestation {\n const evidence: AttributionEvidence = {\n sources: params.sources,\n derivation_type: params.derivation_type,\n };\n\n if (params.output_hash) {\n evidence.output_hash = params.output_hash;\n }\n if (params.model_id) {\n evidence.model_id = params.model_id;\n }\n if (params.inference_provider) {\n evidence.inference_provider = params.inference_provider;\n }\n if (params.session_id) {\n evidence.session_id = params.session_id;\n }\n if (params.metadata) {\n evidence.metadata = params.metadata;\n }\n\n const attestation: AttributionAttestation = {\n type: ATTRIBUTION_TYPE,\n issuer: params.issuer,\n issued_at: new Date().toISOString(),\n evidence,\n };\n\n if (params.expires_at) {\n attestation.expires_at = params.expires_at;\n }\n if (params.ref) {\n attestation.ref = params.ref;\n }\n\n return attestation;\n}\n\n/**\n * Check if an attribution attestation is expired.\n *\n * @param attestation - The attestation to check\n * @param clockSkew - Optional clock skew tolerance in milliseconds (default: 30000)\n * @returns True if the attestation has expired\n */\nexport function isAttributionExpired(\n attestation: AttributionAttestation,\n clockSkew: number = 30000\n): boolean {\n if (!attestation.expires_at) {\n return false; // No expiry = never expires\n }\n const expiresAt = new Date(attestation.expires_at).getTime();\n const now = Date.now();\n return expiresAt < now - clockSkew;\n}\n\n/**\n * Check if an attribution attestation is not yet valid.\n *\n * @param attestation - The attestation to check\n * @param clockSkew - Optional clock skew tolerance in milliseconds (default: 30000)\n * @returns True if the attestation is not yet valid (issued_at in the future)\n */\nexport function isAttributionNotYetValid(\n attestation: AttributionAttestation,\n clockSkew: number = 30000\n): boolean {\n const issuedAt = new Date(attestation.issued_at).getTime();\n const now = Date.now();\n return issuedAt > now + clockSkew;\n}\n\n/**\n * Compute total weight of sources (for validation).\n *\n * @param sources - Array of attribution sources\n * @returns Total weight, or undefined if no weights specified\n */\nexport function computeTotalWeight(sources: AttributionSource[]): number | undefined {\n const weights = sources.filter((s) => s.weight !== undefined).map((s) => s.weight as number);\n if (weights.length === 0) {\n return undefined;\n }\n return weights.reduce((sum, w) => sum + w, 0);\n}\n\n/**\n * Detect cycles in attribution sources (for chain validation).\n *\n * @param sources - Array of attribution sources\n * @param visited - Set of visited receipt refs (for recursion)\n * @returns Receipt ref that caused cycle, or undefined if no cycle\n */\nexport function detectCycleInSources(\n sources: AttributionSource[],\n visited: Set<string> = new Set()\n): string | undefined {\n for (const source of sources) {\n if (visited.has(source.receipt_ref)) {\n return source.receipt_ref;\n }\n }\n return undefined;\n}\n"]}
|