@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/dist/index.mjs
ADDED
|
@@ -0,0 +1,2577 @@
|
|
|
1
|
+
import { ALGORITHMS, HEADERS, POLICY, ISSUER_CONFIG, DISCOVERY, WIRE_TYPE, ERROR_CATEGORIES } from '@peac/kernel';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
// src/errors.ts
|
|
5
|
+
var ERROR_CATEGORIES_CANONICAL = ERROR_CATEGORIES;
|
|
6
|
+
var ERROR_CODES = {
|
|
7
|
+
// Validation errors (400)
|
|
8
|
+
E_CONTROL_REQUIRED: "E_CONTROL_REQUIRED",
|
|
9
|
+
E_INVALID_ENVELOPE: "E_INVALID_ENVELOPE",
|
|
10
|
+
E_INVALID_CONTROL_CHAIN: "E_INVALID_CONTROL_CHAIN",
|
|
11
|
+
E_INVALID_PAYMENT: "E_INVALID_PAYMENT",
|
|
12
|
+
E_INVALID_POLICY_HASH: "E_INVALID_POLICY_HASH",
|
|
13
|
+
E_EXPIRED_RECEIPT: "E_EXPIRED_RECEIPT",
|
|
14
|
+
E_EVIDENCE_NOT_JSON: "E_EVIDENCE_NOT_JSON",
|
|
15
|
+
// Verification errors (401)
|
|
16
|
+
E_INVALID_SIGNATURE: "E_INVALID_SIGNATURE",
|
|
17
|
+
E_SSRF_BLOCKED: "E_SSRF_BLOCKED",
|
|
18
|
+
E_DPOP_REPLAY: "E_DPOP_REPLAY",
|
|
19
|
+
E_DPOP_INVALID: "E_DPOP_INVALID",
|
|
20
|
+
// Control errors (403)
|
|
21
|
+
E_CONTROL_DENIED: "E_CONTROL_DENIED",
|
|
22
|
+
// Infrastructure errors (502/503)
|
|
23
|
+
E_JWKS_FETCH_FAILED: "E_JWKS_FETCH_FAILED",
|
|
24
|
+
E_POLICY_FETCH_FAILED: "E_POLICY_FETCH_FAILED",
|
|
25
|
+
E_NETWORK_ERROR: "E_NETWORK_ERROR",
|
|
26
|
+
// Workflow errors (400)
|
|
27
|
+
E_WORKFLOW_CONTEXT_INVALID: "E_WORKFLOW_CONTEXT_INVALID",
|
|
28
|
+
E_WORKFLOW_DAG_INVALID: "E_WORKFLOW_DAG_INVALID",
|
|
29
|
+
E_WORKFLOW_LIMIT_EXCEEDED: "E_WORKFLOW_LIMIT_EXCEEDED",
|
|
30
|
+
E_WORKFLOW_ID_INVALID: "E_WORKFLOW_ID_INVALID",
|
|
31
|
+
E_WORKFLOW_STEP_ID_INVALID: "E_WORKFLOW_STEP_ID_INVALID",
|
|
32
|
+
E_WORKFLOW_PARENT_NOT_FOUND: "E_WORKFLOW_PARENT_NOT_FOUND",
|
|
33
|
+
E_WORKFLOW_SUMMARY_INVALID: "E_WORKFLOW_SUMMARY_INVALID",
|
|
34
|
+
E_WORKFLOW_CYCLE_DETECTED: "E_WORKFLOW_CYCLE_DETECTED"
|
|
35
|
+
};
|
|
36
|
+
function createPEACError(code, category, severity, retryable, options) {
|
|
37
|
+
return {
|
|
38
|
+
code,
|
|
39
|
+
category,
|
|
40
|
+
severity,
|
|
41
|
+
retryable,
|
|
42
|
+
...options
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function createEvidenceNotJsonError(message, path) {
|
|
46
|
+
return createPEACError(ERROR_CODES.E_EVIDENCE_NOT_JSON, "validation", "error", false, {
|
|
47
|
+
http_status: 400,
|
|
48
|
+
pointer: path ? "/" + path.join("/") : void 0,
|
|
49
|
+
remediation: "Ensure evidence contains only JSON-safe values (strings, finite numbers, booleans, null, arrays, plain objects)",
|
|
50
|
+
details: { message }
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function createWorkflowContextInvalidError(details) {
|
|
54
|
+
return createPEACError(ERROR_CODES.E_WORKFLOW_CONTEXT_INVALID, "validation", "error", false, {
|
|
55
|
+
http_status: 400,
|
|
56
|
+
pointer: "/ext/org.peacprotocol~1workflow",
|
|
57
|
+
remediation: "Ensure workflow_context conforms to WorkflowContextSchema",
|
|
58
|
+
details: { message: details ?? "Invalid workflow context" }
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function createWorkflowDagInvalidError(reason) {
|
|
62
|
+
const messages = {
|
|
63
|
+
self_parent: "Step cannot be its own parent",
|
|
64
|
+
duplicate_parent: "Parent step IDs must be unique",
|
|
65
|
+
cycle: "Workflow DAG contains a cycle"
|
|
66
|
+
};
|
|
67
|
+
return createPEACError(ERROR_CODES.E_WORKFLOW_DAG_INVALID, "validation", "error", false, {
|
|
68
|
+
http_status: 400,
|
|
69
|
+
pointer: "/ext/org.peacprotocol~1workflow/parent_step_ids",
|
|
70
|
+
remediation: "Ensure workflow forms a valid directed acyclic graph (DAG)",
|
|
71
|
+
details: { reason, message: messages[reason] }
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/normalize.ts
|
|
76
|
+
function normalizePayment(payment) {
|
|
77
|
+
const result = {
|
|
78
|
+
rail: payment.rail,
|
|
79
|
+
reference: payment.reference,
|
|
80
|
+
amount: payment.amount,
|
|
81
|
+
currency: payment.currency,
|
|
82
|
+
asset: payment.asset,
|
|
83
|
+
env: payment.env
|
|
84
|
+
};
|
|
85
|
+
if (payment.network !== void 0) {
|
|
86
|
+
result.network = payment.network;
|
|
87
|
+
}
|
|
88
|
+
if (payment.aggregator !== void 0) {
|
|
89
|
+
result.aggregator = payment.aggregator;
|
|
90
|
+
}
|
|
91
|
+
if (payment.routing !== void 0) {
|
|
92
|
+
result.routing = payment.routing;
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
function normalizeControlStep(step) {
|
|
97
|
+
return {
|
|
98
|
+
engine: step.engine,
|
|
99
|
+
result: step.result
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function normalizeControl(control) {
|
|
103
|
+
return {
|
|
104
|
+
chain: control.chain.map(normalizeControlStep)
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function toCoreClaims(input) {
|
|
108
|
+
if ("ok" in input && input.ok === true && "variant" in input) {
|
|
109
|
+
const parsed = input;
|
|
110
|
+
if (parsed.variant === "commerce") {
|
|
111
|
+
return commerceCoreClaims(parsed.claims);
|
|
112
|
+
}
|
|
113
|
+
return attestationCoreClaims(parsed.claims);
|
|
114
|
+
}
|
|
115
|
+
return commerceCoreClaims(input);
|
|
116
|
+
}
|
|
117
|
+
function commerceCoreClaims(claims) {
|
|
118
|
+
const result = {
|
|
119
|
+
iss: claims.iss,
|
|
120
|
+
aud: claims.aud,
|
|
121
|
+
rid: claims.rid,
|
|
122
|
+
iat: claims.iat,
|
|
123
|
+
...claims.amt !== void 0 && { amt: claims.amt },
|
|
124
|
+
...claims.cur !== void 0 && { cur: claims.cur },
|
|
125
|
+
...claims.payment !== void 0 && { payment: normalizePayment(claims.payment) }
|
|
126
|
+
};
|
|
127
|
+
if (claims.exp !== void 0) {
|
|
128
|
+
result.exp = claims.exp;
|
|
129
|
+
}
|
|
130
|
+
if (claims.subject !== void 0) {
|
|
131
|
+
result.subject = { uri: claims.subject.uri };
|
|
132
|
+
}
|
|
133
|
+
if (claims.ext?.control !== void 0) {
|
|
134
|
+
result.control = normalizeControl(claims.ext.control);
|
|
135
|
+
}
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
function attestationCoreClaims(claims) {
|
|
139
|
+
const result = {
|
|
140
|
+
iss: claims.iss,
|
|
141
|
+
aud: claims.aud,
|
|
142
|
+
rid: claims.rid,
|
|
143
|
+
iat: claims.iat
|
|
144
|
+
};
|
|
145
|
+
if (claims.exp !== void 0) {
|
|
146
|
+
result.exp = claims.exp;
|
|
147
|
+
}
|
|
148
|
+
if (claims.sub !== void 0) {
|
|
149
|
+
result.subject = { uri: claims.sub };
|
|
150
|
+
}
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/purpose.ts
|
|
155
|
+
var PURPOSE_TOKEN_REGEX = /^[a-z](?:[a-z0-9_-]*[a-z0-9])?(?::[a-z](?:[a-z0-9_-]*[a-z0-9])?)?$/;
|
|
156
|
+
var MAX_PURPOSE_TOKEN_LENGTH = 64;
|
|
157
|
+
var MAX_PURPOSE_TOKENS_PER_REQUEST = 10;
|
|
158
|
+
var CANONICAL_PURPOSES = [
|
|
159
|
+
"train",
|
|
160
|
+
"search",
|
|
161
|
+
"user_action",
|
|
162
|
+
"inference",
|
|
163
|
+
"index"
|
|
164
|
+
];
|
|
165
|
+
var PURPOSE_REASONS = [
|
|
166
|
+
"allowed",
|
|
167
|
+
"constrained",
|
|
168
|
+
"denied",
|
|
169
|
+
"downgraded",
|
|
170
|
+
"undeclared_default",
|
|
171
|
+
"unknown_preserved"
|
|
172
|
+
];
|
|
173
|
+
var INTERNAL_PURPOSE_UNDECLARED = "undeclared";
|
|
174
|
+
function isValidPurposeToken(token) {
|
|
175
|
+
if (typeof token !== "string") return false;
|
|
176
|
+
if (token.length === 0 || token.length > MAX_PURPOSE_TOKEN_LENGTH) return false;
|
|
177
|
+
return PURPOSE_TOKEN_REGEX.test(token);
|
|
178
|
+
}
|
|
179
|
+
function isCanonicalPurpose(token) {
|
|
180
|
+
return CANONICAL_PURPOSES.includes(token);
|
|
181
|
+
}
|
|
182
|
+
function isLegacyPurpose(token) {
|
|
183
|
+
return token === "crawl" || token === "ai_input" || token === "ai_index";
|
|
184
|
+
}
|
|
185
|
+
function isValidPurposeReason(reason) {
|
|
186
|
+
return PURPOSE_REASONS.includes(reason);
|
|
187
|
+
}
|
|
188
|
+
function isUndeclaredPurpose(token) {
|
|
189
|
+
return token === INTERNAL_PURPOSE_UNDECLARED;
|
|
190
|
+
}
|
|
191
|
+
function normalizePurposeToken(token) {
|
|
192
|
+
return token.trim().toLowerCase();
|
|
193
|
+
}
|
|
194
|
+
function parsePurposeHeader(headerValue) {
|
|
195
|
+
if (!headerValue || typeof headerValue !== "string") {
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
const seen = /* @__PURE__ */ new Set();
|
|
199
|
+
const tokens = [];
|
|
200
|
+
for (const part of headerValue.split(",")) {
|
|
201
|
+
const normalized = normalizePurposeToken(part);
|
|
202
|
+
if (normalized.length > 0 && !seen.has(normalized)) {
|
|
203
|
+
seen.add(normalized);
|
|
204
|
+
tokens.push(normalized);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return tokens;
|
|
208
|
+
}
|
|
209
|
+
function validatePurposeTokens(tokens) {
|
|
210
|
+
const invalidTokens = [];
|
|
211
|
+
let undeclaredPresent = false;
|
|
212
|
+
for (const token of tokens) {
|
|
213
|
+
if (isUndeclaredPurpose(token)) {
|
|
214
|
+
undeclaredPresent = true;
|
|
215
|
+
}
|
|
216
|
+
if (!isValidPurposeToken(token)) {
|
|
217
|
+
invalidTokens.push(token);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
valid: invalidTokens.length === 0 && !undeclaredPresent,
|
|
222
|
+
tokens,
|
|
223
|
+
invalidTokens,
|
|
224
|
+
undeclaredPresent
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function deriveKnownPurposes(declared) {
|
|
228
|
+
return declared.filter(isCanonicalPurpose);
|
|
229
|
+
}
|
|
230
|
+
var LEGACY_TO_CANONICAL = {
|
|
231
|
+
crawl: "index",
|
|
232
|
+
// Crawl implies indexing
|
|
233
|
+
ai_input: "inference",
|
|
234
|
+
// RAG/grounding -> inference context
|
|
235
|
+
ai_index: "index"
|
|
236
|
+
// AI-powered indexing -> index
|
|
237
|
+
};
|
|
238
|
+
function mapLegacyToCanonical(legacy) {
|
|
239
|
+
const canonical = LEGACY_TO_CANONICAL[legacy];
|
|
240
|
+
return {
|
|
241
|
+
canonical,
|
|
242
|
+
mapping_note: `Mapped legacy '${legacy}' to canonical '${canonical}'`
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function normalizeToCanonicalOrPreserve(token) {
|
|
246
|
+
if (isCanonicalPurpose(token)) {
|
|
247
|
+
return { purpose: token, mapped: false };
|
|
248
|
+
}
|
|
249
|
+
if (isLegacyPurpose(token)) {
|
|
250
|
+
return { purpose: LEGACY_TO_CANONICAL[token], mapped: true, from: token };
|
|
251
|
+
}
|
|
252
|
+
return { purpose: token, mapped: false, unknown: true };
|
|
253
|
+
}
|
|
254
|
+
function determinePurposeReason(context) {
|
|
255
|
+
if (!context.declared) {
|
|
256
|
+
return "undeclared_default";
|
|
257
|
+
}
|
|
258
|
+
if (context.hasUnknownTokens) {
|
|
259
|
+
return "unknown_preserved";
|
|
260
|
+
}
|
|
261
|
+
const decision = context.decision ?? "allowed";
|
|
262
|
+
switch (decision) {
|
|
263
|
+
case "allowed":
|
|
264
|
+
return "allowed";
|
|
265
|
+
case "constrained":
|
|
266
|
+
return "constrained";
|
|
267
|
+
case "denied":
|
|
268
|
+
return "denied";
|
|
269
|
+
case "downgraded":
|
|
270
|
+
return "downgraded";
|
|
271
|
+
default:
|
|
272
|
+
return "allowed";
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function hasUnknownPurposeTokens(tokens) {
|
|
276
|
+
return tokens.some((token) => !isCanonicalPurpose(token));
|
|
277
|
+
}
|
|
278
|
+
function isPlainObject(value) {
|
|
279
|
+
if (value === null || typeof value !== "object") {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
const proto = Object.getPrototypeOf(value);
|
|
283
|
+
return proto === Object.prototype || proto === null;
|
|
284
|
+
}
|
|
285
|
+
var JsonNumberSchema = z.number().finite();
|
|
286
|
+
var JsonPrimitiveSchema = z.union([z.string(), JsonNumberSchema, z.boolean(), z.null()]);
|
|
287
|
+
var PlainObjectSchema = z.unknown().refine(isPlainObject, {
|
|
288
|
+
message: "Expected plain object, received non-plain object (Date, Map, Set, or class instance)"
|
|
289
|
+
});
|
|
290
|
+
var JsonValueSchema = z.lazy(
|
|
291
|
+
() => z.union([
|
|
292
|
+
JsonPrimitiveSchema,
|
|
293
|
+
z.array(JsonValueSchema),
|
|
294
|
+
// Plain object check then record validation
|
|
295
|
+
PlainObjectSchema.transform((obj) => obj).pipe(
|
|
296
|
+
z.record(JsonValueSchema)
|
|
297
|
+
)
|
|
298
|
+
])
|
|
299
|
+
);
|
|
300
|
+
var JsonObjectSchema = PlainObjectSchema.transform(
|
|
301
|
+
(obj) => obj
|
|
302
|
+
).pipe(z.record(JsonValueSchema));
|
|
303
|
+
var JsonArraySchema = z.array(JsonValueSchema);
|
|
304
|
+
var JSON_EVIDENCE_LIMITS = {
|
|
305
|
+
/** Maximum nesting depth (default: 32) */
|
|
306
|
+
maxDepth: 32,
|
|
307
|
+
/** Maximum array length (default: 10,000) */
|
|
308
|
+
maxArrayLength: 1e4,
|
|
309
|
+
/** Maximum object keys (default: 1,000) */
|
|
310
|
+
maxObjectKeys: 1e3,
|
|
311
|
+
/** Maximum string length in bytes (default: 65,536 = 64KB) */
|
|
312
|
+
maxStringLength: 65536,
|
|
313
|
+
/** Maximum total nodes to visit (default: 100,000) */
|
|
314
|
+
maxTotalNodes: 1e5
|
|
315
|
+
};
|
|
316
|
+
function assertJsonSafeIterative(value, limits = {}) {
|
|
317
|
+
const maxDepth = limits.maxDepth ?? JSON_EVIDENCE_LIMITS.maxDepth;
|
|
318
|
+
const maxArrayLength = limits.maxArrayLength ?? JSON_EVIDENCE_LIMITS.maxArrayLength;
|
|
319
|
+
const maxObjectKeys = limits.maxObjectKeys ?? JSON_EVIDENCE_LIMITS.maxObjectKeys;
|
|
320
|
+
const maxStringLength = limits.maxStringLength ?? JSON_EVIDENCE_LIMITS.maxStringLength;
|
|
321
|
+
const maxTotalNodes = limits.maxTotalNodes ?? JSON_EVIDENCE_LIMITS.maxTotalNodes;
|
|
322
|
+
const pathSet = /* @__PURE__ */ new WeakSet();
|
|
323
|
+
let nodeCount = 0;
|
|
324
|
+
const stack = [{ type: "enter", value, path: [], depth: 0 }];
|
|
325
|
+
while (stack.length > 0) {
|
|
326
|
+
const entry = stack.pop();
|
|
327
|
+
if (entry.type === "exit") {
|
|
328
|
+
pathSet.delete(entry.obj);
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
const { value: current, path, depth } = entry;
|
|
332
|
+
nodeCount++;
|
|
333
|
+
if (nodeCount > maxTotalNodes) {
|
|
334
|
+
return {
|
|
335
|
+
ok: false,
|
|
336
|
+
error: `Maximum total nodes exceeded (limit: ${maxTotalNodes})`,
|
|
337
|
+
path
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
if (depth > maxDepth) {
|
|
341
|
+
return {
|
|
342
|
+
ok: false,
|
|
343
|
+
error: `Maximum depth exceeded (limit: ${maxDepth})`,
|
|
344
|
+
path
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
if (current === null) {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
const type = typeof current;
|
|
351
|
+
if (type === "string") {
|
|
352
|
+
if (current.length > maxStringLength) {
|
|
353
|
+
return {
|
|
354
|
+
ok: false,
|
|
355
|
+
error: `String exceeds maximum length (limit: ${maxStringLength})`,
|
|
356
|
+
path
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
if (type === "number") {
|
|
362
|
+
if (!Number.isFinite(current)) {
|
|
363
|
+
return {
|
|
364
|
+
ok: false,
|
|
365
|
+
error: `Non-finite number: ${current}`,
|
|
366
|
+
path
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
if (type === "boolean") {
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
if (type === "undefined") {
|
|
375
|
+
return { ok: false, error: "undefined is not valid JSON", path };
|
|
376
|
+
}
|
|
377
|
+
if (type === "bigint") {
|
|
378
|
+
return { ok: false, error: "BigInt is not valid JSON", path };
|
|
379
|
+
}
|
|
380
|
+
if (type === "function") {
|
|
381
|
+
return { ok: false, error: "Function is not valid JSON", path };
|
|
382
|
+
}
|
|
383
|
+
if (type === "symbol") {
|
|
384
|
+
return { ok: false, error: "Symbol is not valid JSON", path };
|
|
385
|
+
}
|
|
386
|
+
if (type === "object") {
|
|
387
|
+
const obj = current;
|
|
388
|
+
if (pathSet.has(obj)) {
|
|
389
|
+
return { ok: false, error: "Cycle detected in object graph", path };
|
|
390
|
+
}
|
|
391
|
+
pathSet.add(obj);
|
|
392
|
+
stack.push({ type: "exit", obj });
|
|
393
|
+
if (Array.isArray(obj)) {
|
|
394
|
+
if (obj.length > maxArrayLength) {
|
|
395
|
+
return {
|
|
396
|
+
ok: false,
|
|
397
|
+
error: `Array exceeds maximum length (limit: ${maxArrayLength})`,
|
|
398
|
+
path
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
for (let i = obj.length - 1; i >= 0; i--) {
|
|
402
|
+
stack.push({ type: "enter", value: obj[i], path: [...path, i], depth: depth + 1 });
|
|
403
|
+
}
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
const proto = Object.getPrototypeOf(obj);
|
|
407
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
408
|
+
const constructorName = obj.constructor?.name ?? "unknown";
|
|
409
|
+
return {
|
|
410
|
+
ok: false,
|
|
411
|
+
error: `Non-plain object (${constructorName}) is not valid JSON`,
|
|
412
|
+
path
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
const keys = Object.keys(obj);
|
|
416
|
+
if (keys.length > maxObjectKeys) {
|
|
417
|
+
return {
|
|
418
|
+
ok: false,
|
|
419
|
+
error: `Object exceeds maximum key count (limit: ${maxObjectKeys})`,
|
|
420
|
+
path
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
for (let i = keys.length - 1; i >= 0; i--) {
|
|
424
|
+
const key = keys[i];
|
|
425
|
+
stack.push({
|
|
426
|
+
type: "enter",
|
|
427
|
+
value: obj[key],
|
|
428
|
+
path: [...path, key],
|
|
429
|
+
depth: depth + 1
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
return { ok: false, error: `Unknown type: ${type}`, path };
|
|
435
|
+
}
|
|
436
|
+
return { ok: true };
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// src/agent-identity.ts
|
|
440
|
+
var ControlTypeSchema = z.enum(["operator", "user-delegated"]);
|
|
441
|
+
var CONTROL_TYPES = ["operator", "user-delegated"];
|
|
442
|
+
var ProofMethodSchema = z.enum([
|
|
443
|
+
"http-message-signature",
|
|
444
|
+
"dpop",
|
|
445
|
+
"mtls",
|
|
446
|
+
"jwk-thumbprint"
|
|
447
|
+
]);
|
|
448
|
+
var PROOF_METHODS = ["http-message-signature", "dpop", "mtls", "jwk-thumbprint"];
|
|
449
|
+
var BindingDetailsSchema = z.object({
|
|
450
|
+
/** HTTP method (uppercase: GET, POST, etc.) */
|
|
451
|
+
method: z.string().min(1).max(16),
|
|
452
|
+
/** Target URI of the request */
|
|
453
|
+
target: z.string().min(1).max(2048),
|
|
454
|
+
/** Headers included in the signature (lowercase) */
|
|
455
|
+
headers_included: z.array(z.string().max(64)).max(32),
|
|
456
|
+
/** SHA-256 hash of request body (base64url), empty string if no body */
|
|
457
|
+
body_hash: z.string().max(64).optional(),
|
|
458
|
+
/** When the binding was signed (RFC 3339) */
|
|
459
|
+
signed_at: z.string().datetime()
|
|
460
|
+
}).strict();
|
|
461
|
+
var AgentProofSchema = z.object({
|
|
462
|
+
/** Proof method used */
|
|
463
|
+
method: ProofMethodSchema,
|
|
464
|
+
/** Key ID (matches kid in JWS header or JWKS) */
|
|
465
|
+
key_id: z.string().min(1).max(256),
|
|
466
|
+
/** Algorithm used (default: EdDSA for Ed25519) */
|
|
467
|
+
alg: z.string().max(32).default("EdDSA"),
|
|
468
|
+
/** Signature over binding message (base64url, for http-message-signature) */
|
|
469
|
+
signature: z.string().max(512).optional(),
|
|
470
|
+
/** DPoP proof JWT (for dpop method) */
|
|
471
|
+
dpop_proof: z.string().max(4096).optional(),
|
|
472
|
+
/** Certificate fingerprint (for mtls method, SHA-256 base64url) */
|
|
473
|
+
cert_thumbprint: z.string().max(64).optional(),
|
|
474
|
+
/** Binding details for http-message-signature */
|
|
475
|
+
binding: BindingDetailsSchema.optional()
|
|
476
|
+
}).strict();
|
|
477
|
+
var AgentIdentityEvidenceSchema = z.object({
|
|
478
|
+
/** Stable agent identifier (opaque string, REQUIRED) */
|
|
479
|
+
agent_id: z.string().min(1).max(256),
|
|
480
|
+
/** Control type: operator-verified or user-delegated (REQUIRED) */
|
|
481
|
+
control_type: ControlTypeSchema,
|
|
482
|
+
/** Agent capabilities/scopes (optional, for fine-grained access) */
|
|
483
|
+
capabilities: z.array(z.string().max(64)).max(32).optional(),
|
|
484
|
+
/** Delegation chain for user-delegated agents (optional) */
|
|
485
|
+
delegation_chain: z.array(z.string().max(256)).max(8).optional(),
|
|
486
|
+
/** Cryptographic proof of key control (optional) */
|
|
487
|
+
proof: AgentProofSchema.optional(),
|
|
488
|
+
/** Key directory URL for public key discovery (optional) */
|
|
489
|
+
key_directory_url: z.string().url().max(2048).optional(),
|
|
490
|
+
/** Agent operator/organization (optional, for operator type) */
|
|
491
|
+
operator: z.string().max(256).optional(),
|
|
492
|
+
/** User identifier (optional, for user-delegated type, should be opaque) */
|
|
493
|
+
user_id: z.string().max(256).optional(),
|
|
494
|
+
/** Additional type-specific metadata (optional) */
|
|
495
|
+
metadata: z.record(z.string(), JsonValueSchema).optional()
|
|
496
|
+
}).strict();
|
|
497
|
+
var AGENT_IDENTITY_TYPE = "peac/agent-identity";
|
|
498
|
+
var AgentIdentityAttestationSchema = z.object({
|
|
499
|
+
/** Attestation type (MUST be 'peac/agent-identity') */
|
|
500
|
+
type: z.literal(AGENT_IDENTITY_TYPE),
|
|
501
|
+
/** Issuer of the attestation (agent operator, IdP, or platform) */
|
|
502
|
+
issuer: z.string().min(1).max(2048),
|
|
503
|
+
/** When the attestation was issued (RFC 3339) */
|
|
504
|
+
issued_at: z.string().datetime(),
|
|
505
|
+
/** When the attestation expires (RFC 3339, optional) */
|
|
506
|
+
expires_at: z.string().datetime().optional(),
|
|
507
|
+
/** Reference to external verification endpoint (optional) */
|
|
508
|
+
ref: z.string().url().max(2048).optional(),
|
|
509
|
+
/** Agent identity evidence */
|
|
510
|
+
evidence: AgentIdentityEvidenceSchema
|
|
511
|
+
}).strict();
|
|
512
|
+
var IdentityBindingSchema = z.object({
|
|
513
|
+
/** SHA-256 hash of the canonical binding message (base64url) */
|
|
514
|
+
binding_message_hash: z.string().min(1).max(64),
|
|
515
|
+
/** Ed25519 signature over binding message (base64url) */
|
|
516
|
+
signature: z.string().min(1).max(512),
|
|
517
|
+
/** Key ID used for signing */
|
|
518
|
+
key_id: z.string().min(1).max(256),
|
|
519
|
+
/** When the binding was created (RFC 3339) */
|
|
520
|
+
signed_at: z.string().datetime()
|
|
521
|
+
}).strict();
|
|
522
|
+
var AgentIdentityVerifiedSchema = z.object({
|
|
523
|
+
/** Agent ID from the verified attestation */
|
|
524
|
+
agent_id: z.string().min(1).max(256),
|
|
525
|
+
/** Control type from the verified attestation */
|
|
526
|
+
control_type: ControlTypeSchema,
|
|
527
|
+
/** When the publisher verified the identity (RFC 3339) */
|
|
528
|
+
verified_at: z.string().datetime(),
|
|
529
|
+
/** Key ID that was used for verification */
|
|
530
|
+
key_id: z.string().min(1).max(256),
|
|
531
|
+
/** SHA-256 hash of the binding message (base64url) */
|
|
532
|
+
binding_hash: z.string().min(1).max(64)
|
|
533
|
+
}).strict();
|
|
534
|
+
function validateAgentIdentityAttestation(data) {
|
|
535
|
+
const result = AgentIdentityAttestationSchema.safeParse(data);
|
|
536
|
+
if (result.success) {
|
|
537
|
+
return { ok: true, value: result.data };
|
|
538
|
+
}
|
|
539
|
+
return { ok: false, error: result.error.message };
|
|
540
|
+
}
|
|
541
|
+
function isAgentIdentityAttestation(attestation) {
|
|
542
|
+
return attestation.type === AGENT_IDENTITY_TYPE;
|
|
543
|
+
}
|
|
544
|
+
function createAgentIdentityAttestation(params) {
|
|
545
|
+
const evidence = {
|
|
546
|
+
agent_id: params.agent_id,
|
|
547
|
+
control_type: params.control_type
|
|
548
|
+
};
|
|
549
|
+
if (params.capabilities) {
|
|
550
|
+
evidence.capabilities = params.capabilities;
|
|
551
|
+
}
|
|
552
|
+
if (params.delegation_chain) {
|
|
553
|
+
evidence.delegation_chain = params.delegation_chain;
|
|
554
|
+
}
|
|
555
|
+
if (params.proof) {
|
|
556
|
+
evidence.proof = params.proof;
|
|
557
|
+
}
|
|
558
|
+
if (params.key_directory_url) {
|
|
559
|
+
evidence.key_directory_url = params.key_directory_url;
|
|
560
|
+
}
|
|
561
|
+
if (params.operator) {
|
|
562
|
+
evidence.operator = params.operator;
|
|
563
|
+
}
|
|
564
|
+
if (params.user_id) {
|
|
565
|
+
evidence.user_id = params.user_id;
|
|
566
|
+
}
|
|
567
|
+
if (params.metadata) {
|
|
568
|
+
evidence.metadata = params.metadata;
|
|
569
|
+
}
|
|
570
|
+
const attestation = {
|
|
571
|
+
type: AGENT_IDENTITY_TYPE,
|
|
572
|
+
issuer: params.issuer,
|
|
573
|
+
issued_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
574
|
+
evidence
|
|
575
|
+
};
|
|
576
|
+
if (params.expires_at) {
|
|
577
|
+
attestation.expires_at = params.expires_at;
|
|
578
|
+
}
|
|
579
|
+
if (params.ref) {
|
|
580
|
+
attestation.ref = params.ref;
|
|
581
|
+
}
|
|
582
|
+
return attestation;
|
|
583
|
+
}
|
|
584
|
+
function validateIdentityBinding(data) {
|
|
585
|
+
const result = IdentityBindingSchema.safeParse(data);
|
|
586
|
+
if (result.success) {
|
|
587
|
+
return { ok: true, value: result.data };
|
|
588
|
+
}
|
|
589
|
+
return { ok: false, error: result.error.message };
|
|
590
|
+
}
|
|
591
|
+
function isAttestationExpired(attestation, clockSkew = 3e4) {
|
|
592
|
+
if (!attestation.expires_at) {
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
const expiresAt = new Date(attestation.expires_at).getTime();
|
|
596
|
+
const now = Date.now();
|
|
597
|
+
return expiresAt < now - clockSkew;
|
|
598
|
+
}
|
|
599
|
+
function isAttestationNotYetValid(attestation, clockSkew = 3e4) {
|
|
600
|
+
const issuedAt = new Date(attestation.issued_at).getTime();
|
|
601
|
+
const now = Date.now();
|
|
602
|
+
return issuedAt > now + clockSkew;
|
|
603
|
+
}
|
|
604
|
+
var ATTRIBUTION_LIMITS = {
|
|
605
|
+
/** Maximum sources per attestation */
|
|
606
|
+
maxSources: 100,
|
|
607
|
+
/** Maximum chain resolution depth */
|
|
608
|
+
maxDepth: 8,
|
|
609
|
+
/** Maximum attestation size in bytes (64KB) */
|
|
610
|
+
maxAttestationSize: 65536,
|
|
611
|
+
/** Per-hop resolution timeout in milliseconds */
|
|
612
|
+
resolutionTimeout: 5e3,
|
|
613
|
+
/** Maximum receipt reference length */
|
|
614
|
+
maxReceiptRefLength: 2048,
|
|
615
|
+
/** Maximum model ID length */
|
|
616
|
+
maxModelIdLength: 256
|
|
617
|
+
};
|
|
618
|
+
var HashAlgorithmSchema = z.literal("sha-256");
|
|
619
|
+
var HashEncodingSchema = z.literal("base64url");
|
|
620
|
+
var ContentHashSchema = z.object({
|
|
621
|
+
/** Hash algorithm (REQUIRED, must be 'sha-256') */
|
|
622
|
+
alg: HashAlgorithmSchema,
|
|
623
|
+
/** Base64url-encoded hash value without padding (REQUIRED, 43 chars for SHA-256) */
|
|
624
|
+
value: z.string().min(43).max(43).regex(/^[A-Za-z0-9_-]+$/, "Invalid base64url characters"),
|
|
625
|
+
/** Encoding format (REQUIRED, must be 'base64url') */
|
|
626
|
+
enc: HashEncodingSchema
|
|
627
|
+
}).strict();
|
|
628
|
+
var AttributionUsageSchema = z.enum([
|
|
629
|
+
"training_input",
|
|
630
|
+
"rag_context",
|
|
631
|
+
"direct_reference",
|
|
632
|
+
"synthesis_source",
|
|
633
|
+
"embedding_source"
|
|
634
|
+
]);
|
|
635
|
+
var ATTRIBUTION_USAGES = [
|
|
636
|
+
"training_input",
|
|
637
|
+
"rag_context",
|
|
638
|
+
"direct_reference",
|
|
639
|
+
"synthesis_source",
|
|
640
|
+
"embedding_source"
|
|
641
|
+
];
|
|
642
|
+
var DerivationTypeSchema = z.enum([
|
|
643
|
+
"training",
|
|
644
|
+
"inference",
|
|
645
|
+
"rag",
|
|
646
|
+
"synthesis",
|
|
647
|
+
"embedding"
|
|
648
|
+
]);
|
|
649
|
+
var DERIVATION_TYPES = ["training", "inference", "rag", "synthesis", "embedding"];
|
|
650
|
+
var ReceiptRefSchema = z.string().min(1).max(ATTRIBUTION_LIMITS.maxReceiptRefLength).refine(
|
|
651
|
+
(ref) => {
|
|
652
|
+
if (ref.startsWith("jti:")) return true;
|
|
653
|
+
if (ref.startsWith("https://") || ref.startsWith("http://")) return true;
|
|
654
|
+
if (ref.startsWith("urn:peac:receipt:")) return true;
|
|
655
|
+
return false;
|
|
656
|
+
},
|
|
657
|
+
{ message: "Invalid receipt reference format. Must be jti:{id}, URL, or urn:peac:receipt:{id}" }
|
|
658
|
+
);
|
|
659
|
+
var AttributionSourceSchema = z.object({
|
|
660
|
+
/** Reference to source PEAC receipt (REQUIRED) */
|
|
661
|
+
receipt_ref: ReceiptRefSchema,
|
|
662
|
+
/**
|
|
663
|
+
* Issuer of the referenced receipt (OPTIONAL but RECOMMENDED for jti: refs).
|
|
664
|
+
*
|
|
665
|
+
* Required for cross-issuer resolution when receipt_ref is jti:{id} format.
|
|
666
|
+
* Not needed for URL-based references which are self-resolvable.
|
|
667
|
+
* Used to construct resolution URL: {receipt_issuer}/.well-known/peac/receipts/{id}
|
|
668
|
+
*/
|
|
669
|
+
receipt_issuer: z.string().url().max(2048).optional(),
|
|
670
|
+
/** Hash of source content (OPTIONAL) */
|
|
671
|
+
content_hash: ContentHashSchema.optional(),
|
|
672
|
+
/** Hash of used excerpt (OPTIONAL, content-minimizing, not privacy-preserving for short text) */
|
|
673
|
+
excerpt_hash: ContentHashSchema.optional(),
|
|
674
|
+
/** How the source was used (REQUIRED) */
|
|
675
|
+
usage: AttributionUsageSchema,
|
|
676
|
+
/** Relative contribution weight 0.0-1.0 (OPTIONAL) */
|
|
677
|
+
weight: z.number().min(0).max(1).optional()
|
|
678
|
+
}).strict();
|
|
679
|
+
var AttributionEvidenceSchema = z.object({
|
|
680
|
+
/** Array of attribution sources (REQUIRED, 1-100 sources) */
|
|
681
|
+
sources: z.array(AttributionSourceSchema).min(1).max(ATTRIBUTION_LIMITS.maxSources),
|
|
682
|
+
/** Type of derivation (REQUIRED) */
|
|
683
|
+
derivation_type: DerivationTypeSchema,
|
|
684
|
+
/** Hash of derived output (OPTIONAL) */
|
|
685
|
+
output_hash: ContentHashSchema.optional(),
|
|
686
|
+
/** Model identifier (OPTIONAL) */
|
|
687
|
+
model_id: z.string().max(ATTRIBUTION_LIMITS.maxModelIdLength).optional(),
|
|
688
|
+
/** Inference provider URL (OPTIONAL) */
|
|
689
|
+
inference_provider: z.string().url().max(2048).optional(),
|
|
690
|
+
/** Session correlation ID (OPTIONAL) */
|
|
691
|
+
session_id: z.string().max(256).optional(),
|
|
692
|
+
/** Additional type-specific metadata (OPTIONAL) */
|
|
693
|
+
metadata: z.record(z.string(), JsonValueSchema).optional()
|
|
694
|
+
}).strict();
|
|
695
|
+
var ATTRIBUTION_TYPE = "peac/attribution";
|
|
696
|
+
var AttributionAttestationSchema = z.object({
|
|
697
|
+
/** Attestation type (MUST be 'peac/attribution') */
|
|
698
|
+
type: z.literal(ATTRIBUTION_TYPE),
|
|
699
|
+
/** Issuer of the attestation (inference provider, platform) */
|
|
700
|
+
issuer: z.string().min(1).max(2048),
|
|
701
|
+
/** When the attestation was issued (RFC 3339) */
|
|
702
|
+
issued_at: z.string().datetime(),
|
|
703
|
+
/** When the attestation expires (RFC 3339, OPTIONAL) */
|
|
704
|
+
expires_at: z.string().datetime().optional(),
|
|
705
|
+
/** Reference to external verification endpoint (OPTIONAL) */
|
|
706
|
+
ref: z.string().url().max(2048).optional(),
|
|
707
|
+
/** Attribution evidence */
|
|
708
|
+
evidence: AttributionEvidenceSchema
|
|
709
|
+
}).strict();
|
|
710
|
+
function validateContentHash(data) {
|
|
711
|
+
const result = ContentHashSchema.safeParse(data);
|
|
712
|
+
if (result.success) {
|
|
713
|
+
return { ok: true, value: result.data };
|
|
714
|
+
}
|
|
715
|
+
return { ok: false, error: result.error.message };
|
|
716
|
+
}
|
|
717
|
+
function validateAttributionSource(data) {
|
|
718
|
+
const result = AttributionSourceSchema.safeParse(data);
|
|
719
|
+
if (result.success) {
|
|
720
|
+
return { ok: true, value: result.data };
|
|
721
|
+
}
|
|
722
|
+
return { ok: false, error: result.error.message };
|
|
723
|
+
}
|
|
724
|
+
function validateAttributionAttestation(data) {
|
|
725
|
+
const result = AttributionAttestationSchema.safeParse(data);
|
|
726
|
+
if (result.success) {
|
|
727
|
+
return { ok: true, value: result.data };
|
|
728
|
+
}
|
|
729
|
+
return { ok: false, error: result.error.message };
|
|
730
|
+
}
|
|
731
|
+
function isAttributionAttestation(attestation) {
|
|
732
|
+
return attestation.type === ATTRIBUTION_TYPE;
|
|
733
|
+
}
|
|
734
|
+
function createAttributionAttestation(params) {
|
|
735
|
+
const evidence = {
|
|
736
|
+
sources: params.sources,
|
|
737
|
+
derivation_type: params.derivation_type
|
|
738
|
+
};
|
|
739
|
+
if (params.output_hash) {
|
|
740
|
+
evidence.output_hash = params.output_hash;
|
|
741
|
+
}
|
|
742
|
+
if (params.model_id) {
|
|
743
|
+
evidence.model_id = params.model_id;
|
|
744
|
+
}
|
|
745
|
+
if (params.inference_provider) {
|
|
746
|
+
evidence.inference_provider = params.inference_provider;
|
|
747
|
+
}
|
|
748
|
+
if (params.session_id) {
|
|
749
|
+
evidence.session_id = params.session_id;
|
|
750
|
+
}
|
|
751
|
+
if (params.metadata) {
|
|
752
|
+
evidence.metadata = params.metadata;
|
|
753
|
+
}
|
|
754
|
+
const attestation = {
|
|
755
|
+
type: ATTRIBUTION_TYPE,
|
|
756
|
+
issuer: params.issuer,
|
|
757
|
+
issued_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
758
|
+
evidence
|
|
759
|
+
};
|
|
760
|
+
if (params.expires_at) {
|
|
761
|
+
attestation.expires_at = params.expires_at;
|
|
762
|
+
}
|
|
763
|
+
if (params.ref) {
|
|
764
|
+
attestation.ref = params.ref;
|
|
765
|
+
}
|
|
766
|
+
return attestation;
|
|
767
|
+
}
|
|
768
|
+
function isAttributionExpired(attestation, clockSkew = 3e4) {
|
|
769
|
+
if (!attestation.expires_at) {
|
|
770
|
+
return false;
|
|
771
|
+
}
|
|
772
|
+
const expiresAt = new Date(attestation.expires_at).getTime();
|
|
773
|
+
const now = Date.now();
|
|
774
|
+
return expiresAt < now - clockSkew;
|
|
775
|
+
}
|
|
776
|
+
function isAttributionNotYetValid(attestation, clockSkew = 3e4) {
|
|
777
|
+
const issuedAt = new Date(attestation.issued_at).getTime();
|
|
778
|
+
const now = Date.now();
|
|
779
|
+
return issuedAt > now + clockSkew;
|
|
780
|
+
}
|
|
781
|
+
function computeTotalWeight(sources) {
|
|
782
|
+
const weights = sources.filter((s) => s.weight !== void 0).map((s) => s.weight);
|
|
783
|
+
if (weights.length === 0) {
|
|
784
|
+
return void 0;
|
|
785
|
+
}
|
|
786
|
+
return weights.reduce((sum, w) => sum + w, 0);
|
|
787
|
+
}
|
|
788
|
+
function detectCycleInSources(sources, visited = /* @__PURE__ */ new Set()) {
|
|
789
|
+
for (const source of sources) {
|
|
790
|
+
if (visited.has(source.receipt_ref)) {
|
|
791
|
+
return source.receipt_ref;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return void 0;
|
|
795
|
+
}
|
|
796
|
+
var PEAC_WIRE_TYP = WIRE_TYPE;
|
|
797
|
+
var PEAC_ALG = ALGORITHMS.default;
|
|
798
|
+
var PEAC_RECEIPT_HEADER = HEADERS.receipt;
|
|
799
|
+
var PEAC_PURPOSE_HEADER = HEADERS.purpose;
|
|
800
|
+
var PEAC_PURPOSE_APPLIED_HEADER = HEADERS.purposeApplied;
|
|
801
|
+
var PEAC_PURPOSE_REASON_HEADER = HEADERS.purposeReason;
|
|
802
|
+
var PEAC_POLICY_PATH = POLICY.manifestPath;
|
|
803
|
+
var PEAC_POLICY_FALLBACK_PATH = POLICY.fallbackPath;
|
|
804
|
+
var PEAC_POLICY_MAX_BYTES = POLICY.maxBytes;
|
|
805
|
+
var PEAC_ISSUER_CONFIG_PATH = ISSUER_CONFIG.configPath;
|
|
806
|
+
var PEAC_ISSUER_CONFIG_VERSION = ISSUER_CONFIG.configVersion;
|
|
807
|
+
var PEAC_ISSUER_CONFIG_MAX_BYTES = ISSUER_CONFIG.maxBytes;
|
|
808
|
+
var PEAC_DISCOVERY_PATH = DISCOVERY.manifestPath;
|
|
809
|
+
var PEAC_DISCOVERY_MAX_BYTES = 2e3;
|
|
810
|
+
var PEAC_RECEIPT_SCHEMA_URL = "https://www.peacprotocol.org/schemas/wire/0.1/peac-receipt.0.1.schema.json";
|
|
811
|
+
var httpsUrl = z.string().url().refine((u) => u.startsWith("https://"), "must be https://");
|
|
812
|
+
var iso4217 = z.string().regex(/^[A-Z]{3}$/);
|
|
813
|
+
var uuidv7 = z.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
|
|
814
|
+
var NormalizedPayment = z.object({
|
|
815
|
+
rail: z.string().min(1),
|
|
816
|
+
reference: z.string().min(1),
|
|
817
|
+
amount: z.number().int().nonnegative(),
|
|
818
|
+
currency: iso4217,
|
|
819
|
+
asset: z.string().optional(),
|
|
820
|
+
env: z.string().optional(),
|
|
821
|
+
evidence: JsonValueSchema.optional(),
|
|
822
|
+
metadata: JsonObjectSchema.optional()
|
|
823
|
+
}).strict();
|
|
824
|
+
var Subject = z.object({ uri: httpsUrl }).strict();
|
|
825
|
+
var AIPREFSnapshot = z.object({
|
|
826
|
+
url: httpsUrl,
|
|
827
|
+
hash: z.string().min(8)
|
|
828
|
+
}).strict();
|
|
829
|
+
var Extensions = z.object({
|
|
830
|
+
aipref_snapshot: AIPREFSnapshot.optional()
|
|
831
|
+
// control block validated via ControlBlockSchema when present
|
|
832
|
+
}).catchall(z.unknown());
|
|
833
|
+
var JWSHeader = z.object({
|
|
834
|
+
typ: z.literal(PEAC_WIRE_TYP),
|
|
835
|
+
alg: z.literal(PEAC_ALG),
|
|
836
|
+
kid: z.string().min(8)
|
|
837
|
+
}).strict();
|
|
838
|
+
var CanonicalPurposeValues = ["train", "search", "user_action", "inference", "index"];
|
|
839
|
+
var PurposeReasonValues = [
|
|
840
|
+
"allowed",
|
|
841
|
+
"constrained",
|
|
842
|
+
"denied",
|
|
843
|
+
"downgraded",
|
|
844
|
+
"undeclared_default",
|
|
845
|
+
"unknown_preserved"
|
|
846
|
+
];
|
|
847
|
+
var ReceiptClaimsSchema = z.object({
|
|
848
|
+
iss: httpsUrl,
|
|
849
|
+
aud: httpsUrl,
|
|
850
|
+
iat: z.number().int().nonnegative(),
|
|
851
|
+
exp: z.number().int().optional(),
|
|
852
|
+
rid: uuidv7,
|
|
853
|
+
amt: z.number().int().nonnegative(),
|
|
854
|
+
cur: iso4217,
|
|
855
|
+
payment: NormalizedPayment,
|
|
856
|
+
subject: Subject.optional(),
|
|
857
|
+
ext: Extensions.optional(),
|
|
858
|
+
// Purpose claims (v0.9.24+)
|
|
859
|
+
// purpose_declared: string[] - preserves unknown tokens for forward-compat
|
|
860
|
+
purpose_declared: z.array(z.string()).optional(),
|
|
861
|
+
// purpose_enforced: CanonicalPurpose - must be one with enforcement semantics
|
|
862
|
+
purpose_enforced: z.enum(CanonicalPurposeValues).optional(),
|
|
863
|
+
// purpose_reason: PurposeReason - audit spine for enforcement decisions
|
|
864
|
+
purpose_reason: z.enum(PurposeReasonValues).optional()
|
|
865
|
+
}).strict();
|
|
866
|
+
var ReceiptClaims = ReceiptClaimsSchema;
|
|
867
|
+
var VerifyRequest = z.object({
|
|
868
|
+
receipt_jws: z.string().min(16)
|
|
869
|
+
}).strict();
|
|
870
|
+
var ControlPurposeSchema = z.enum([
|
|
871
|
+
"crawl",
|
|
872
|
+
"index",
|
|
873
|
+
"train",
|
|
874
|
+
"inference",
|
|
875
|
+
"user_action",
|
|
876
|
+
"ai_input",
|
|
877
|
+
"ai_index",
|
|
878
|
+
"search"
|
|
879
|
+
]);
|
|
880
|
+
var ControlLicensingModeSchema = z.enum([
|
|
881
|
+
"subscription",
|
|
882
|
+
"pay_per_crawl",
|
|
883
|
+
"pay_per_inference"
|
|
884
|
+
]);
|
|
885
|
+
var ControlDecisionSchema = z.enum(["allow", "deny", "review"]);
|
|
886
|
+
var ControlStepSchema = z.object({
|
|
887
|
+
engine: z.string().min(1),
|
|
888
|
+
version: z.string().optional(),
|
|
889
|
+
policy_id: z.string().optional(),
|
|
890
|
+
result: ControlDecisionSchema,
|
|
891
|
+
reason: z.string().optional(),
|
|
892
|
+
purpose: ControlPurposeSchema.optional(),
|
|
893
|
+
licensing_mode: ControlLicensingModeSchema.optional(),
|
|
894
|
+
scope: z.union([z.string(), z.array(z.string())]).optional(),
|
|
895
|
+
limits_snapshot: z.unknown().optional(),
|
|
896
|
+
evidence_ref: z.string().optional()
|
|
897
|
+
});
|
|
898
|
+
var ControlBlockSchema = z.object({
|
|
899
|
+
chain: z.array(ControlStepSchema).min(1),
|
|
900
|
+
decision: ControlDecisionSchema,
|
|
901
|
+
combinator: z.literal("any_can_veto").optional()
|
|
902
|
+
}).refine(
|
|
903
|
+
(data) => {
|
|
904
|
+
const hasAnyDeny = data.chain.some((step) => step.result === "deny");
|
|
905
|
+
const allAllow = data.chain.every((step) => step.result === "allow");
|
|
906
|
+
const hasReview = data.chain.some((step) => step.result === "review");
|
|
907
|
+
if (hasAnyDeny && data.decision !== "deny") {
|
|
908
|
+
return false;
|
|
909
|
+
}
|
|
910
|
+
if (allAllow && data.decision !== "allow") {
|
|
911
|
+
return false;
|
|
912
|
+
}
|
|
913
|
+
if (hasReview && !hasAnyDeny && data.decision === "deny") {
|
|
914
|
+
return false;
|
|
915
|
+
}
|
|
916
|
+
return true;
|
|
917
|
+
},
|
|
918
|
+
{
|
|
919
|
+
message: "Control block decision must be consistent with chain results"
|
|
920
|
+
}
|
|
921
|
+
);
|
|
922
|
+
var PurposeTokenSchema = z.string().min(1).max(MAX_PURPOSE_TOKEN_LENGTH).refine((token) => PURPOSE_TOKEN_REGEX.test(token), {
|
|
923
|
+
message: "Invalid purpose token format. Must be lowercase, alphanumeric with underscores, optional vendor prefix."
|
|
924
|
+
});
|
|
925
|
+
var CanonicalPurposeSchema = z.enum([
|
|
926
|
+
"train",
|
|
927
|
+
"search",
|
|
928
|
+
"user_action",
|
|
929
|
+
"inference",
|
|
930
|
+
"index"
|
|
931
|
+
]);
|
|
932
|
+
var PurposeReasonSchema = z.enum([
|
|
933
|
+
"allowed",
|
|
934
|
+
"constrained",
|
|
935
|
+
"denied",
|
|
936
|
+
"downgraded",
|
|
937
|
+
"undeclared_default",
|
|
938
|
+
"unknown_preserved"
|
|
939
|
+
]);
|
|
940
|
+
var PaymentSplitSchema = z.object({
|
|
941
|
+
party: z.string().min(1),
|
|
942
|
+
amount: z.number().int().nonnegative().optional(),
|
|
943
|
+
currency: iso4217.optional(),
|
|
944
|
+
share: z.number().min(0).max(1).optional(),
|
|
945
|
+
rail: z.string().min(1).optional(),
|
|
946
|
+
account_ref: z.string().min(1).optional(),
|
|
947
|
+
metadata: JsonObjectSchema.optional()
|
|
948
|
+
}).strict().refine((data) => data.amount !== void 0 || data.share !== void 0, {
|
|
949
|
+
message: "At least one of amount or share must be specified"
|
|
950
|
+
});
|
|
951
|
+
var PaymentRoutingSchema = z.enum(["direct", "callback", "role"]);
|
|
952
|
+
var PaymentEvidenceSchema = z.object({
|
|
953
|
+
rail: z.string().min(1),
|
|
954
|
+
reference: z.string().min(1),
|
|
955
|
+
amount: z.number().int().nonnegative(),
|
|
956
|
+
currency: iso4217,
|
|
957
|
+
asset: z.string().min(1),
|
|
958
|
+
env: z.enum(["live", "test"]),
|
|
959
|
+
network: z.string().min(1).optional(),
|
|
960
|
+
facilitator_ref: z.string().min(1).optional(),
|
|
961
|
+
evidence: JsonValueSchema,
|
|
962
|
+
aggregator: z.string().min(1).optional(),
|
|
963
|
+
splits: z.array(PaymentSplitSchema).optional(),
|
|
964
|
+
routing: PaymentRoutingSchema.optional()
|
|
965
|
+
}).strict();
|
|
966
|
+
var SubjectTypeSchema = z.enum(["human", "org", "agent"]);
|
|
967
|
+
var SubjectProfileSchema = z.object({
|
|
968
|
+
id: z.string().min(1),
|
|
969
|
+
type: SubjectTypeSchema,
|
|
970
|
+
labels: z.array(z.string().min(1)).optional(),
|
|
971
|
+
metadata: JsonObjectSchema.optional()
|
|
972
|
+
}).strict();
|
|
973
|
+
var SubjectProfileSnapshotSchema = z.object({
|
|
974
|
+
subject: SubjectProfileSchema,
|
|
975
|
+
captured_at: z.string().min(1),
|
|
976
|
+
source: z.string().min(1).optional(),
|
|
977
|
+
version: z.string().min(1).optional()
|
|
978
|
+
}).strict();
|
|
979
|
+
var ExtensionsSchema = z.record(
|
|
980
|
+
z.string().regex(/^[a-z0-9_.-]+\/[a-z0-9_.-]+$/),
|
|
981
|
+
JsonValueSchema
|
|
982
|
+
);
|
|
983
|
+
var AttestationSchema = z.object({
|
|
984
|
+
issuer: z.string().min(1),
|
|
985
|
+
type: z.string().min(1),
|
|
986
|
+
issued_at: z.string().datetime(),
|
|
987
|
+
expires_at: z.string().datetime().optional(),
|
|
988
|
+
ref: z.string().url().optional(),
|
|
989
|
+
evidence: JsonValueSchema
|
|
990
|
+
}).strict();
|
|
991
|
+
var warnedSubjectIds = /* @__PURE__ */ new Set();
|
|
992
|
+
function looksLikePII(id) {
|
|
993
|
+
if (/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(id)) {
|
|
994
|
+
return true;
|
|
995
|
+
}
|
|
996
|
+
if (/^\+?\d{10,15}$/.test(id.replace(/[\s\-()]/g, ""))) {
|
|
997
|
+
return true;
|
|
998
|
+
}
|
|
999
|
+
return false;
|
|
1000
|
+
}
|
|
1001
|
+
function validateSubjectSnapshot(snapshot) {
|
|
1002
|
+
if (snapshot === void 0 || snapshot === null) {
|
|
1003
|
+
return null;
|
|
1004
|
+
}
|
|
1005
|
+
const validated = SubjectProfileSnapshotSchema.parse(snapshot);
|
|
1006
|
+
const subjectId = validated.subject.id;
|
|
1007
|
+
if (looksLikePII(subjectId) && !warnedSubjectIds.has(subjectId)) {
|
|
1008
|
+
warnedSubjectIds.add(subjectId);
|
|
1009
|
+
console.warn(
|
|
1010
|
+
`[peac:subject] Advisory: subject.id "${subjectId}" looks like PII. Prefer opaque identifiers (e.g., "user:abc123").`
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
return validated;
|
|
1014
|
+
}
|
|
1015
|
+
function validateEvidence(evidence, limits) {
|
|
1016
|
+
const result = assertJsonSafeIterative(evidence, limits);
|
|
1017
|
+
if (!result.ok) {
|
|
1018
|
+
return {
|
|
1019
|
+
ok: false,
|
|
1020
|
+
error: createEvidenceNotJsonError(result.error, result.path)
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
return { ok: true, value: evidence };
|
|
1024
|
+
}
|
|
1025
|
+
var DISPUTE_LIMITS = {
|
|
1026
|
+
/** Maximum grounds per dispute */
|
|
1027
|
+
maxGrounds: 10,
|
|
1028
|
+
/** Maximum supporting receipts */
|
|
1029
|
+
maxSupportingReceipts: 50,
|
|
1030
|
+
/** Maximum supporting attributions */
|
|
1031
|
+
maxSupportingAttributions: 50,
|
|
1032
|
+
/** Maximum supporting documents */
|
|
1033
|
+
maxSupportingDocuments: 20,
|
|
1034
|
+
/** Maximum description length in chars */
|
|
1035
|
+
maxDescriptionLength: 4e3,
|
|
1036
|
+
/** Maximum details length per ground in chars */
|
|
1037
|
+
maxGroundDetailsLength: 1e3,
|
|
1038
|
+
/** Maximum rationale length in chars */
|
|
1039
|
+
maxRationaleLength: 4e3,
|
|
1040
|
+
/** Maximum remediation details length in chars */
|
|
1041
|
+
maxRemediationDetailsLength: 4e3,
|
|
1042
|
+
/** Minimum description for 'other' dispute type */
|
|
1043
|
+
minOtherDescriptionLength: 50
|
|
1044
|
+
};
|
|
1045
|
+
var ULID_REGEX = /^[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
1046
|
+
var DisputeIdSchema = z.string().regex(ULID_REGEX, "Invalid ULID format");
|
|
1047
|
+
var DisputeTypeSchema = z.enum([
|
|
1048
|
+
"unauthorized_access",
|
|
1049
|
+
"attribution_missing",
|
|
1050
|
+
"attribution_incorrect",
|
|
1051
|
+
"receipt_invalid",
|
|
1052
|
+
"identity_spoofed",
|
|
1053
|
+
"purpose_mismatch",
|
|
1054
|
+
"policy_violation",
|
|
1055
|
+
"other"
|
|
1056
|
+
]);
|
|
1057
|
+
var DISPUTE_TYPES = [
|
|
1058
|
+
"unauthorized_access",
|
|
1059
|
+
"attribution_missing",
|
|
1060
|
+
"attribution_incorrect",
|
|
1061
|
+
"receipt_invalid",
|
|
1062
|
+
"identity_spoofed",
|
|
1063
|
+
"purpose_mismatch",
|
|
1064
|
+
"policy_violation",
|
|
1065
|
+
"other"
|
|
1066
|
+
];
|
|
1067
|
+
var DisputeTargetTypeSchema = z.enum(["receipt", "attribution", "identity", "policy"]);
|
|
1068
|
+
var DISPUTE_TARGET_TYPES = ["receipt", "attribution", "identity", "policy"];
|
|
1069
|
+
var DisputeGroundsCodeSchema = z.enum([
|
|
1070
|
+
// Evidence-based
|
|
1071
|
+
"missing_receipt",
|
|
1072
|
+
"expired_receipt",
|
|
1073
|
+
"forged_receipt",
|
|
1074
|
+
"receipt_not_applicable",
|
|
1075
|
+
// Attribution-based
|
|
1076
|
+
"content_not_used",
|
|
1077
|
+
"source_misidentified",
|
|
1078
|
+
"usage_type_wrong",
|
|
1079
|
+
"weight_inaccurate",
|
|
1080
|
+
// Identity-based
|
|
1081
|
+
"agent_impersonation",
|
|
1082
|
+
"key_compromise",
|
|
1083
|
+
"delegation_invalid",
|
|
1084
|
+
// Policy-based
|
|
1085
|
+
"purpose_exceeded",
|
|
1086
|
+
"terms_violated",
|
|
1087
|
+
"rate_limit_exceeded"
|
|
1088
|
+
]);
|
|
1089
|
+
var DISPUTE_GROUNDS_CODES = [
|
|
1090
|
+
"missing_receipt",
|
|
1091
|
+
"expired_receipt",
|
|
1092
|
+
"forged_receipt",
|
|
1093
|
+
"receipt_not_applicable",
|
|
1094
|
+
"content_not_used",
|
|
1095
|
+
"source_misidentified",
|
|
1096
|
+
"usage_type_wrong",
|
|
1097
|
+
"weight_inaccurate",
|
|
1098
|
+
"agent_impersonation",
|
|
1099
|
+
"key_compromise",
|
|
1100
|
+
"delegation_invalid",
|
|
1101
|
+
"purpose_exceeded",
|
|
1102
|
+
"terms_violated",
|
|
1103
|
+
"rate_limit_exceeded"
|
|
1104
|
+
];
|
|
1105
|
+
var DisputeGroundsSchema = z.object({
|
|
1106
|
+
/** Specific code for this ground (REQUIRED) */
|
|
1107
|
+
code: DisputeGroundsCodeSchema,
|
|
1108
|
+
/** Reference to supporting evidence (OPTIONAL) */
|
|
1109
|
+
evidence_ref: z.string().max(2048).optional(),
|
|
1110
|
+
/** Additional context for this ground (OPTIONAL) */
|
|
1111
|
+
details: z.string().max(DISPUTE_LIMITS.maxGroundDetailsLength).optional()
|
|
1112
|
+
}).strict();
|
|
1113
|
+
var DisputeStateSchema = z.enum([
|
|
1114
|
+
"filed",
|
|
1115
|
+
"acknowledged",
|
|
1116
|
+
"under_review",
|
|
1117
|
+
"escalated",
|
|
1118
|
+
"resolved",
|
|
1119
|
+
"rejected",
|
|
1120
|
+
"appealed",
|
|
1121
|
+
"final"
|
|
1122
|
+
]);
|
|
1123
|
+
var DISPUTE_STATES = [
|
|
1124
|
+
"filed",
|
|
1125
|
+
"acknowledged",
|
|
1126
|
+
"under_review",
|
|
1127
|
+
"escalated",
|
|
1128
|
+
"resolved",
|
|
1129
|
+
"rejected",
|
|
1130
|
+
"appealed",
|
|
1131
|
+
"final"
|
|
1132
|
+
];
|
|
1133
|
+
var TERMINAL_STATES = ["resolved", "rejected", "final"];
|
|
1134
|
+
var DISPUTE_TRANSITIONS = {
|
|
1135
|
+
filed: ["acknowledged", "rejected"],
|
|
1136
|
+
acknowledged: ["under_review", "rejected"],
|
|
1137
|
+
under_review: ["resolved", "escalated"],
|
|
1138
|
+
escalated: ["resolved"],
|
|
1139
|
+
resolved: ["appealed", "final"],
|
|
1140
|
+
rejected: ["appealed", "final"],
|
|
1141
|
+
appealed: ["under_review", "final"],
|
|
1142
|
+
final: []
|
|
1143
|
+
// Terminal - no transitions out
|
|
1144
|
+
};
|
|
1145
|
+
function canTransitionTo(current, next) {
|
|
1146
|
+
return DISPUTE_TRANSITIONS[current].includes(next);
|
|
1147
|
+
}
|
|
1148
|
+
function isTerminalState(state) {
|
|
1149
|
+
return TERMINAL_STATES.includes(state);
|
|
1150
|
+
}
|
|
1151
|
+
function getValidTransitions(current) {
|
|
1152
|
+
return DISPUTE_TRANSITIONS[current];
|
|
1153
|
+
}
|
|
1154
|
+
var DisputeOutcomeSchema = z.enum(["upheld", "dismissed", "partially_upheld", "settled"]);
|
|
1155
|
+
var DISPUTE_OUTCOMES = ["upheld", "dismissed", "partially_upheld", "settled"];
|
|
1156
|
+
var RemediationTypeSchema = z.enum([
|
|
1157
|
+
"attribution_corrected",
|
|
1158
|
+
"receipt_revoked",
|
|
1159
|
+
"access_restored",
|
|
1160
|
+
"compensation",
|
|
1161
|
+
"policy_updated",
|
|
1162
|
+
"no_action",
|
|
1163
|
+
"other"
|
|
1164
|
+
]);
|
|
1165
|
+
var REMEDIATION_TYPES = [
|
|
1166
|
+
"attribution_corrected",
|
|
1167
|
+
"receipt_revoked",
|
|
1168
|
+
"access_restored",
|
|
1169
|
+
"compensation",
|
|
1170
|
+
"policy_updated",
|
|
1171
|
+
"no_action",
|
|
1172
|
+
"other"
|
|
1173
|
+
];
|
|
1174
|
+
var RemediationSchema = z.object({
|
|
1175
|
+
/** Type of remediation (REQUIRED) */
|
|
1176
|
+
type: RemediationTypeSchema,
|
|
1177
|
+
/** Details of the remediation action (REQUIRED) */
|
|
1178
|
+
details: z.string().min(1).max(DISPUTE_LIMITS.maxRemediationDetailsLength),
|
|
1179
|
+
/** Deadline for completing remediation (OPTIONAL) */
|
|
1180
|
+
deadline: z.string().datetime().optional()
|
|
1181
|
+
}).strict();
|
|
1182
|
+
var DisputeResolutionSchema = z.object({
|
|
1183
|
+
/** Outcome of the dispute (REQUIRED) */
|
|
1184
|
+
outcome: DisputeOutcomeSchema,
|
|
1185
|
+
/** When the decision was made (REQUIRED) */
|
|
1186
|
+
decided_at: z.string().datetime(),
|
|
1187
|
+
/** Who made the decision (REQUIRED) */
|
|
1188
|
+
decided_by: z.string().min(1).max(2048),
|
|
1189
|
+
/** Explanation of the decision (REQUIRED) */
|
|
1190
|
+
rationale: z.string().min(1).max(DISPUTE_LIMITS.maxRationaleLength),
|
|
1191
|
+
/** Remediation action if applicable (OPTIONAL) */
|
|
1192
|
+
remediation: RemediationSchema.optional()
|
|
1193
|
+
}).strict();
|
|
1194
|
+
var ContactMethodSchema = z.enum(["email", "url", "did"]);
|
|
1195
|
+
var DisputeContactSchema = z.object({
|
|
1196
|
+
/** Contact method (REQUIRED) */
|
|
1197
|
+
method: ContactMethodSchema,
|
|
1198
|
+
/** Contact value (REQUIRED) */
|
|
1199
|
+
value: z.string().min(1).max(2048)
|
|
1200
|
+
}).strict().superRefine((contact, ctx) => {
|
|
1201
|
+
if (contact.method === "email") {
|
|
1202
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1203
|
+
if (!emailRegex.test(contact.value)) {
|
|
1204
|
+
ctx.addIssue({
|
|
1205
|
+
code: z.ZodIssueCode.custom,
|
|
1206
|
+
message: "Invalid email format",
|
|
1207
|
+
path: ["value"]
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
} else if (contact.method === "did") {
|
|
1211
|
+
if (!contact.value.startsWith("did:")) {
|
|
1212
|
+
ctx.addIssue({
|
|
1213
|
+
code: z.ZodIssueCode.custom,
|
|
1214
|
+
message: 'DID must start with "did:"',
|
|
1215
|
+
path: ["value"]
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
} else if (contact.method === "url") {
|
|
1219
|
+
try {
|
|
1220
|
+
new URL(contact.value);
|
|
1221
|
+
} catch {
|
|
1222
|
+
ctx.addIssue({
|
|
1223
|
+
code: z.ZodIssueCode.custom,
|
|
1224
|
+
message: "Invalid URL format",
|
|
1225
|
+
path: ["value"]
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
});
|
|
1230
|
+
var DocumentRefSchema = z.object({
|
|
1231
|
+
/** URI of the document (REQUIRED) */
|
|
1232
|
+
uri: z.string().url().max(2048),
|
|
1233
|
+
/** Content hash for integrity verification (OPTIONAL) */
|
|
1234
|
+
content_hash: ContentHashSchema.optional(),
|
|
1235
|
+
/** Brief description of the document (OPTIONAL) */
|
|
1236
|
+
description: z.string().max(500).optional()
|
|
1237
|
+
}).strict();
|
|
1238
|
+
var DisputeEvidenceBaseSchema = z.object({
|
|
1239
|
+
/** Type of dispute (REQUIRED) */
|
|
1240
|
+
dispute_type: DisputeTypeSchema,
|
|
1241
|
+
/** Reference to disputed target: jti:{id}, URL, or URN (REQUIRED) */
|
|
1242
|
+
target_ref: z.string().min(1).max(2048),
|
|
1243
|
+
/** Type of target being disputed (REQUIRED) */
|
|
1244
|
+
target_type: DisputeTargetTypeSchema,
|
|
1245
|
+
/** Grounds for the dispute (REQUIRED, at least 1) */
|
|
1246
|
+
grounds: z.array(DisputeGroundsSchema).min(1).max(DISPUTE_LIMITS.maxGrounds),
|
|
1247
|
+
/** Human-readable description (REQUIRED) */
|
|
1248
|
+
description: z.string().min(1).max(DISPUTE_LIMITS.maxDescriptionLength),
|
|
1249
|
+
/** Receipt references supporting the claim (OPTIONAL) */
|
|
1250
|
+
supporting_receipts: z.array(z.string().max(2048)).max(DISPUTE_LIMITS.maxSupportingReceipts).optional(),
|
|
1251
|
+
/** Attribution references supporting the claim (OPTIONAL) */
|
|
1252
|
+
supporting_attributions: z.array(z.string().max(2048)).max(DISPUTE_LIMITS.maxSupportingAttributions).optional(),
|
|
1253
|
+
/** External document references (OPTIONAL) */
|
|
1254
|
+
supporting_documents: z.array(DocumentRefSchema).max(DISPUTE_LIMITS.maxSupportingDocuments).optional(),
|
|
1255
|
+
/** Contact for dispute resolution (OPTIONAL) */
|
|
1256
|
+
contact: DisputeContactSchema.optional(),
|
|
1257
|
+
/** Current lifecycle state (REQUIRED) */
|
|
1258
|
+
state: DisputeStateSchema,
|
|
1259
|
+
/** When state was last changed (OPTIONAL) */
|
|
1260
|
+
state_changed_at: z.string().datetime().optional(),
|
|
1261
|
+
/** Reason for state change (OPTIONAL) */
|
|
1262
|
+
state_reason: z.string().max(1e3).optional(),
|
|
1263
|
+
/** Resolution details (REQUIRED for terminal states) */
|
|
1264
|
+
resolution: DisputeResolutionSchema.optional(),
|
|
1265
|
+
/** Advisory: filing window used by issuer in days (OPTIONAL, informative only) */
|
|
1266
|
+
window_hint_days: z.number().int().positive().max(365).optional()
|
|
1267
|
+
}).strict();
|
|
1268
|
+
var DisputeEvidenceSchema = DisputeEvidenceBaseSchema.superRefine((evidence, ctx) => {
|
|
1269
|
+
const terminalStates = ["resolved", "rejected", "final"];
|
|
1270
|
+
if (terminalStates.includes(evidence.state) && !evidence.resolution) {
|
|
1271
|
+
ctx.addIssue({
|
|
1272
|
+
code: z.ZodIssueCode.custom,
|
|
1273
|
+
message: `Resolution is required when state is "${evidence.state}"`,
|
|
1274
|
+
path: ["resolution"]
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
if (evidence.resolution && !terminalStates.includes(evidence.state)) {
|
|
1278
|
+
ctx.addIssue({
|
|
1279
|
+
code: z.ZodIssueCode.custom,
|
|
1280
|
+
message: `Resolution is only valid for terminal states (resolved, rejected, final), not "${evidence.state}"`,
|
|
1281
|
+
path: ["state"]
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
if (evidence.dispute_type === "other" && evidence.description.length < DISPUTE_LIMITS.minOtherDescriptionLength) {
|
|
1285
|
+
ctx.addIssue({
|
|
1286
|
+
code: z.ZodIssueCode.custom,
|
|
1287
|
+
message: `Dispute type "other" requires description of at least ${DISPUTE_LIMITS.minOtherDescriptionLength} characters`,
|
|
1288
|
+
path: ["description"]
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
});
|
|
1292
|
+
var DISPUTE_TYPE = "peac/dispute";
|
|
1293
|
+
var DisputeAttestationSchema = z.object({
|
|
1294
|
+
/** Attestation type (MUST be 'peac/dispute') */
|
|
1295
|
+
type: z.literal(DISPUTE_TYPE),
|
|
1296
|
+
/** Party filing the dispute (REQUIRED) */
|
|
1297
|
+
issuer: z.string().min(1).max(2048),
|
|
1298
|
+
/** When the dispute was filed (REQUIRED) */
|
|
1299
|
+
issued_at: z.string().datetime(),
|
|
1300
|
+
/** When the dispute expires (OPTIONAL) */
|
|
1301
|
+
expires_at: z.string().datetime().optional(),
|
|
1302
|
+
/** Unique dispute reference in ULID format (REQUIRED) */
|
|
1303
|
+
ref: DisputeIdSchema,
|
|
1304
|
+
/** Dispute evidence and state */
|
|
1305
|
+
evidence: DisputeEvidenceSchema
|
|
1306
|
+
}).strict();
|
|
1307
|
+
function validateDisputeAttestation(data) {
|
|
1308
|
+
const result = DisputeAttestationSchema.safeParse(data);
|
|
1309
|
+
if (result.success) {
|
|
1310
|
+
return { ok: true, value: result.data };
|
|
1311
|
+
}
|
|
1312
|
+
return { ok: false, error: result.error.message };
|
|
1313
|
+
}
|
|
1314
|
+
function isValidDisputeAttestation(data) {
|
|
1315
|
+
return DisputeAttestationSchema.safeParse(data).success;
|
|
1316
|
+
}
|
|
1317
|
+
function isDisputeAttestation(attestation) {
|
|
1318
|
+
return attestation.type === DISPUTE_TYPE;
|
|
1319
|
+
}
|
|
1320
|
+
function validateDisputeResolution(data) {
|
|
1321
|
+
const result = DisputeResolutionSchema.safeParse(data);
|
|
1322
|
+
if (result.success) {
|
|
1323
|
+
return { ok: true, value: result.data };
|
|
1324
|
+
}
|
|
1325
|
+
return { ok: false, error: result.error.message };
|
|
1326
|
+
}
|
|
1327
|
+
function validateDisputeContact(data) {
|
|
1328
|
+
const result = DisputeContactSchema.safeParse(data);
|
|
1329
|
+
if (result.success) {
|
|
1330
|
+
return { ok: true, value: result.data };
|
|
1331
|
+
}
|
|
1332
|
+
return { ok: false, error: result.error.message };
|
|
1333
|
+
}
|
|
1334
|
+
function createDisputeAttestation(params) {
|
|
1335
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1336
|
+
const evidence = {
|
|
1337
|
+
dispute_type: params.dispute_type,
|
|
1338
|
+
target_ref: params.target_ref,
|
|
1339
|
+
target_type: params.target_type,
|
|
1340
|
+
grounds: params.grounds,
|
|
1341
|
+
description: params.description,
|
|
1342
|
+
state: "filed"
|
|
1343
|
+
};
|
|
1344
|
+
if (params.contact) {
|
|
1345
|
+
evidence.contact = params.contact;
|
|
1346
|
+
}
|
|
1347
|
+
if (params.supporting_receipts) {
|
|
1348
|
+
evidence.supporting_receipts = params.supporting_receipts;
|
|
1349
|
+
}
|
|
1350
|
+
if (params.supporting_attributions) {
|
|
1351
|
+
evidence.supporting_attributions = params.supporting_attributions;
|
|
1352
|
+
}
|
|
1353
|
+
if (params.supporting_documents) {
|
|
1354
|
+
evidence.supporting_documents = params.supporting_documents;
|
|
1355
|
+
}
|
|
1356
|
+
if (params.window_hint_days !== void 0) {
|
|
1357
|
+
evidence.window_hint_days = params.window_hint_days;
|
|
1358
|
+
}
|
|
1359
|
+
const attestation = {
|
|
1360
|
+
type: DISPUTE_TYPE,
|
|
1361
|
+
issuer: params.issuer,
|
|
1362
|
+
issued_at: now,
|
|
1363
|
+
ref: params.ref,
|
|
1364
|
+
evidence
|
|
1365
|
+
};
|
|
1366
|
+
if (params.expires_at) {
|
|
1367
|
+
attestation.expires_at = params.expires_at;
|
|
1368
|
+
}
|
|
1369
|
+
return attestation;
|
|
1370
|
+
}
|
|
1371
|
+
function transitionDisputeState(dispute, newState, reason, resolution) {
|
|
1372
|
+
const currentState = dispute.evidence.state;
|
|
1373
|
+
if (!canTransitionTo(currentState, newState)) {
|
|
1374
|
+
return {
|
|
1375
|
+
ok: false,
|
|
1376
|
+
error: `Invalid transition from "${currentState}" to "${newState}". Valid transitions: ${DISPUTE_TRANSITIONS[currentState].join(", ") || "none"}`,
|
|
1377
|
+
code: "INVALID_TRANSITION"
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
const isTargetTerminal = isTerminalState(newState);
|
|
1381
|
+
if (isTargetTerminal && !resolution) {
|
|
1382
|
+
return {
|
|
1383
|
+
ok: false,
|
|
1384
|
+
error: `Resolution is required when transitioning to terminal state "${newState}"`,
|
|
1385
|
+
code: "RESOLUTION_REQUIRED"
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
if (!isTargetTerminal && resolution) {
|
|
1389
|
+
return {
|
|
1390
|
+
ok: false,
|
|
1391
|
+
error: `Resolution is not allowed for non-terminal state "${newState}"`,
|
|
1392
|
+
code: "RESOLUTION_NOT_ALLOWED"
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1396
|
+
const { resolution: _existingResolution, ...evidenceWithoutResolution } = dispute.evidence;
|
|
1397
|
+
const updatedEvidence = {
|
|
1398
|
+
...evidenceWithoutResolution,
|
|
1399
|
+
state: newState,
|
|
1400
|
+
state_changed_at: now
|
|
1401
|
+
};
|
|
1402
|
+
if (reason) {
|
|
1403
|
+
updatedEvidence.state_reason = reason;
|
|
1404
|
+
}
|
|
1405
|
+
if (isTargetTerminal && resolution) {
|
|
1406
|
+
updatedEvidence.resolution = resolution;
|
|
1407
|
+
}
|
|
1408
|
+
return {
|
|
1409
|
+
ok: true,
|
|
1410
|
+
value: {
|
|
1411
|
+
...dispute,
|
|
1412
|
+
evidence: updatedEvidence
|
|
1413
|
+
}
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
function isDisputeExpired(attestation, clockSkew = 3e4) {
|
|
1417
|
+
if (!attestation.expires_at) {
|
|
1418
|
+
return false;
|
|
1419
|
+
}
|
|
1420
|
+
const expiresAt = new Date(attestation.expires_at).getTime();
|
|
1421
|
+
const now = Date.now();
|
|
1422
|
+
return expiresAt < now - clockSkew;
|
|
1423
|
+
}
|
|
1424
|
+
function isDisputeNotYetValid(attestation, clockSkew = 3e4) {
|
|
1425
|
+
const issuedAt = new Date(attestation.issued_at).getTime();
|
|
1426
|
+
const now = Date.now();
|
|
1427
|
+
return issuedAt > now + clockSkew;
|
|
1428
|
+
}
|
|
1429
|
+
var WORKFLOW_EXTENSION_KEY = "org.peacprotocol/workflow";
|
|
1430
|
+
var WORKFLOW_SUMMARY_TYPE = "peac/workflow-summary";
|
|
1431
|
+
var WORKFLOW_STATUSES = ["in_progress", "completed", "failed", "cancelled"];
|
|
1432
|
+
var WELL_KNOWN_FRAMEWORKS = [
|
|
1433
|
+
"mcp",
|
|
1434
|
+
"a2a",
|
|
1435
|
+
"crewai",
|
|
1436
|
+
"langgraph",
|
|
1437
|
+
"autogen",
|
|
1438
|
+
"custom"
|
|
1439
|
+
];
|
|
1440
|
+
var ORCHESTRATION_FRAMEWORKS = WELL_KNOWN_FRAMEWORKS;
|
|
1441
|
+
var FRAMEWORK_ID_PATTERN = /^[a-z][a-z0-9_-]*$/;
|
|
1442
|
+
var WORKFLOW_LIMITS = {
|
|
1443
|
+
/** Maximum parent steps per step (DAG fan-in) */
|
|
1444
|
+
maxParentSteps: 16,
|
|
1445
|
+
/** Maximum workflow ID length */
|
|
1446
|
+
maxWorkflowIdLength: 128,
|
|
1447
|
+
/** Maximum step ID length */
|
|
1448
|
+
maxStepIdLength: 128,
|
|
1449
|
+
/** Maximum tool name length */
|
|
1450
|
+
maxToolNameLength: 256,
|
|
1451
|
+
/** Maximum framework identifier length */
|
|
1452
|
+
maxFrameworkLength: 64,
|
|
1453
|
+
/** Maximum agents in a workflow summary */
|
|
1454
|
+
maxAgentsInvolved: 100,
|
|
1455
|
+
/** Maximum receipt refs in a workflow summary */
|
|
1456
|
+
maxReceiptRefs: 1e4,
|
|
1457
|
+
/** Maximum error message length */
|
|
1458
|
+
maxErrorMessageLength: 1024
|
|
1459
|
+
};
|
|
1460
|
+
var WORKFLOW_ID_PATTERN = /^wf_[a-zA-Z0-9_-]{20,48}$/;
|
|
1461
|
+
var STEP_ID_PATTERN = /^step_[a-zA-Z0-9_-]{20,48}$/;
|
|
1462
|
+
var WorkflowIdSchema = z.string().regex(WORKFLOW_ID_PATTERN, "Invalid workflow ID format (expected wf_{ulid|uuid})").max(WORKFLOW_LIMITS.maxWorkflowIdLength);
|
|
1463
|
+
var StepIdSchema = z.string().regex(STEP_ID_PATTERN, "Invalid step ID format (expected step_{ulid|uuid})").max(WORKFLOW_LIMITS.maxStepIdLength);
|
|
1464
|
+
var WorkflowStatusSchema = z.enum(WORKFLOW_STATUSES);
|
|
1465
|
+
var OrchestrationFrameworkSchema = z.string().regex(
|
|
1466
|
+
FRAMEWORK_ID_PATTERN,
|
|
1467
|
+
"Invalid framework identifier (must be lowercase alphanumeric with hyphens/underscores, starting with a letter)"
|
|
1468
|
+
).max(WORKFLOW_LIMITS.maxFrameworkLength);
|
|
1469
|
+
var WorkflowContextSchema = z.object({
|
|
1470
|
+
// Correlation
|
|
1471
|
+
/** Globally unique workflow/run ID */
|
|
1472
|
+
workflow_id: WorkflowIdSchema,
|
|
1473
|
+
/** This step's unique ID */
|
|
1474
|
+
step_id: StepIdSchema,
|
|
1475
|
+
/** DAG parent step IDs (empty array for root step) */
|
|
1476
|
+
parent_step_ids: z.array(StepIdSchema).max(WORKFLOW_LIMITS.maxParentSteps).default([]),
|
|
1477
|
+
// Orchestration identity
|
|
1478
|
+
/** Agent identity ref of the orchestrator (optional) */
|
|
1479
|
+
orchestrator_id: z.string().max(256).optional(),
|
|
1480
|
+
/** Receipt ID that initiated this workflow (optional) */
|
|
1481
|
+
orchestrator_receipt_ref: z.string().max(256).optional(),
|
|
1482
|
+
// Sequencing (for linear workflows)
|
|
1483
|
+
/** 0-based position in sequential runs (optional) */
|
|
1484
|
+
step_index: z.number().int().nonnegative().optional(),
|
|
1485
|
+
/** Total steps if known upfront (optional) */
|
|
1486
|
+
step_total: z.number().int().positive().optional(),
|
|
1487
|
+
// Metadata
|
|
1488
|
+
/** Tool or skill name (MCP tool, A2A skill, etc.) */
|
|
1489
|
+
tool_name: z.string().max(WORKFLOW_LIMITS.maxToolNameLength).optional(),
|
|
1490
|
+
/** Orchestration framework */
|
|
1491
|
+
framework: OrchestrationFrameworkSchema.optional(),
|
|
1492
|
+
// Hash chain (for streaming/progressive receipts)
|
|
1493
|
+
/** SHA-256 hash of previous receipt in chain (for ordering) */
|
|
1494
|
+
prev_receipt_hash: z.string().regex(/^sha256:[a-f0-9]{64}$/).optional()
|
|
1495
|
+
}).strict();
|
|
1496
|
+
var WorkflowErrorContextSchema = z.object({
|
|
1497
|
+
/** Step ID where failure occurred */
|
|
1498
|
+
failed_step_id: StepIdSchema,
|
|
1499
|
+
/** Error code (E_* format preferred) */
|
|
1500
|
+
error_code: z.string().max(64),
|
|
1501
|
+
/** Human-readable error message */
|
|
1502
|
+
error_message: z.string().max(WORKFLOW_LIMITS.maxErrorMessageLength)
|
|
1503
|
+
}).strict();
|
|
1504
|
+
var WorkflowSummaryEvidenceSchema = z.object({
|
|
1505
|
+
/** Workflow ID this summary describes */
|
|
1506
|
+
workflow_id: WorkflowIdSchema,
|
|
1507
|
+
/** Workflow status */
|
|
1508
|
+
status: WorkflowStatusSchema,
|
|
1509
|
+
/** When the workflow started (ISO 8601) */
|
|
1510
|
+
started_at: z.string().datetime(),
|
|
1511
|
+
/** When the workflow completed (ISO 8601, undefined if in_progress) */
|
|
1512
|
+
completed_at: z.string().datetime().optional(),
|
|
1513
|
+
// Receipt commitment
|
|
1514
|
+
/** Ordered list of receipt IDs (for small workflows) */
|
|
1515
|
+
receipt_refs: z.array(z.string().max(256)).max(WORKFLOW_LIMITS.maxReceiptRefs).optional(),
|
|
1516
|
+
/** Merkle root of receipt digests (for large workflows, RFC 6962 style) */
|
|
1517
|
+
receipt_merkle_root: z.string().regex(/^sha256:[a-f0-9]{64}$/).optional(),
|
|
1518
|
+
/** Total receipt count (required if using Merkle root) */
|
|
1519
|
+
receipt_count: z.number().int().nonnegative().optional(),
|
|
1520
|
+
// Orchestration
|
|
1521
|
+
/** Agent identity ref of the orchestrator */
|
|
1522
|
+
orchestrator_id: z.string().max(256),
|
|
1523
|
+
/** List of agent IDs involved in the workflow */
|
|
1524
|
+
agents_involved: z.array(z.string().max(256)).max(WORKFLOW_LIMITS.maxAgentsInvolved),
|
|
1525
|
+
// Outcome
|
|
1526
|
+
/** SHA-256 hash of final output artifact (optional) */
|
|
1527
|
+
final_result_hash: z.string().regex(/^sha256:[a-f0-9]{64}$/).optional(),
|
|
1528
|
+
/** Error context if workflow failed */
|
|
1529
|
+
error_context: WorkflowErrorContextSchema.optional()
|
|
1530
|
+
}).strict().refine(
|
|
1531
|
+
(data) => {
|
|
1532
|
+
return data.receipt_refs !== void 0 || data.receipt_merkle_root !== void 0;
|
|
1533
|
+
},
|
|
1534
|
+
{
|
|
1535
|
+
message: "Workflow summary must include receipt_refs or receipt_merkle_root"
|
|
1536
|
+
}
|
|
1537
|
+
).refine(
|
|
1538
|
+
(data) => {
|
|
1539
|
+
if (data.receipt_merkle_root !== void 0 && data.receipt_count === void 0) {
|
|
1540
|
+
return false;
|
|
1541
|
+
}
|
|
1542
|
+
return true;
|
|
1543
|
+
},
|
|
1544
|
+
{
|
|
1545
|
+
message: "receipt_count is required when using receipt_merkle_root"
|
|
1546
|
+
}
|
|
1547
|
+
);
|
|
1548
|
+
var WorkflowSummaryAttestationSchema = z.object({
|
|
1549
|
+
/** Attestation type (must be 'peac/workflow-summary') */
|
|
1550
|
+
type: z.literal(WORKFLOW_SUMMARY_TYPE),
|
|
1551
|
+
/** Who issued this attestation (HTTPS URL) */
|
|
1552
|
+
issuer: z.string().url().startsWith("https://"),
|
|
1553
|
+
/** When this attestation was issued (ISO 8601) */
|
|
1554
|
+
issued_at: z.string().datetime(),
|
|
1555
|
+
/** When this attestation expires (ISO 8601, optional) */
|
|
1556
|
+
expires_at: z.string().datetime().optional(),
|
|
1557
|
+
/** The workflow summary evidence */
|
|
1558
|
+
evidence: WorkflowSummaryEvidenceSchema
|
|
1559
|
+
}).strict();
|
|
1560
|
+
function validateWorkflowContextOrdered(input) {
|
|
1561
|
+
if (typeof input !== "object" || input === null) {
|
|
1562
|
+
return {
|
|
1563
|
+
valid: false,
|
|
1564
|
+
error_code: "E_WORKFLOW_CONTEXT_INVALID",
|
|
1565
|
+
error: "Input must be an object"
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
const obj = input;
|
|
1569
|
+
if (typeof obj.workflow_id !== "string" || !WORKFLOW_ID_PATTERN.test(obj.workflow_id) || obj.workflow_id.length > WORKFLOW_LIMITS.maxWorkflowIdLength) {
|
|
1570
|
+
return {
|
|
1571
|
+
valid: false,
|
|
1572
|
+
error_code: "E_WORKFLOW_ID_INVALID",
|
|
1573
|
+
error: "Invalid workflow ID format"
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1576
|
+
if (typeof obj.step_id !== "string" || !STEP_ID_PATTERN.test(obj.step_id) || obj.step_id.length > WORKFLOW_LIMITS.maxStepIdLength) {
|
|
1577
|
+
return {
|
|
1578
|
+
valid: false,
|
|
1579
|
+
error_code: "E_WORKFLOW_STEP_ID_INVALID",
|
|
1580
|
+
error: "Invalid step ID format"
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
const parentStepIds = obj.parent_step_ids;
|
|
1584
|
+
if (Array.isArray(parentStepIds) && parentStepIds.length > WORKFLOW_LIMITS.maxParentSteps) {
|
|
1585
|
+
return {
|
|
1586
|
+
valid: false,
|
|
1587
|
+
error_code: "E_WORKFLOW_LIMIT_EXCEEDED",
|
|
1588
|
+
error: "Exceeds maximum parent steps"
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
if (obj.framework !== void 0) {
|
|
1592
|
+
if (typeof obj.framework !== "string" || !FRAMEWORK_ID_PATTERN.test(obj.framework) || obj.framework.length > WORKFLOW_LIMITS.maxFrameworkLength) {
|
|
1593
|
+
return {
|
|
1594
|
+
valid: false,
|
|
1595
|
+
error_code: "E_WORKFLOW_CONTEXT_INVALID",
|
|
1596
|
+
error: "Invalid framework identifier grammar"
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
if (obj.prev_receipt_hash !== void 0) {
|
|
1601
|
+
if (typeof obj.prev_receipt_hash !== "string" || !/^sha256:[a-f0-9]{64}$/.test(obj.prev_receipt_hash)) {
|
|
1602
|
+
return {
|
|
1603
|
+
valid: false,
|
|
1604
|
+
error_code: "E_WORKFLOW_CONTEXT_INVALID",
|
|
1605
|
+
error: "Invalid hash format"
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
if (Array.isArray(parentStepIds)) {
|
|
1610
|
+
if (parentStepIds.includes(obj.step_id)) {
|
|
1611
|
+
return {
|
|
1612
|
+
valid: false,
|
|
1613
|
+
error_code: "E_WORKFLOW_DAG_INVALID",
|
|
1614
|
+
error: "Self-parent cycle detected"
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
const uniqueParents = new Set(parentStepIds);
|
|
1618
|
+
if (uniqueParents.size !== parentStepIds.length) {
|
|
1619
|
+
return {
|
|
1620
|
+
valid: false,
|
|
1621
|
+
error_code: "E_WORKFLOW_DAG_INVALID",
|
|
1622
|
+
error: "Duplicate parent step IDs"
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
const result = WorkflowContextSchema.safeParse(input);
|
|
1627
|
+
if (!result.success) {
|
|
1628
|
+
return {
|
|
1629
|
+
valid: false,
|
|
1630
|
+
error_code: "E_WORKFLOW_CONTEXT_INVALID",
|
|
1631
|
+
error: result.error.issues[0]?.message || "Schema validation failed"
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
return { valid: true, value: result.data };
|
|
1635
|
+
}
|
|
1636
|
+
function createWorkflowId(id) {
|
|
1637
|
+
const workflowId = `wf_${id}`;
|
|
1638
|
+
return WorkflowIdSchema.parse(workflowId);
|
|
1639
|
+
}
|
|
1640
|
+
function createStepId(id) {
|
|
1641
|
+
const stepId = `step_${id}`;
|
|
1642
|
+
return StepIdSchema.parse(stepId);
|
|
1643
|
+
}
|
|
1644
|
+
function validateWorkflowContext(context) {
|
|
1645
|
+
return WorkflowContextSchema.parse(context);
|
|
1646
|
+
}
|
|
1647
|
+
function isValidWorkflowContext(context) {
|
|
1648
|
+
return WorkflowContextSchema.safeParse(context).success;
|
|
1649
|
+
}
|
|
1650
|
+
function validateWorkflowSummaryAttestation(attestation) {
|
|
1651
|
+
return WorkflowSummaryAttestationSchema.parse(attestation);
|
|
1652
|
+
}
|
|
1653
|
+
function isWorkflowSummaryAttestation(attestation) {
|
|
1654
|
+
return WorkflowSummaryAttestationSchema.safeParse(attestation).success;
|
|
1655
|
+
}
|
|
1656
|
+
function isTerminalWorkflowStatus(status) {
|
|
1657
|
+
return status === "completed" || status === "failed" || status === "cancelled";
|
|
1658
|
+
}
|
|
1659
|
+
function hasValidDagSemantics(context) {
|
|
1660
|
+
if (context.parent_step_ids.includes(context.step_id)) {
|
|
1661
|
+
return false;
|
|
1662
|
+
}
|
|
1663
|
+
const uniqueParents = new Set(context.parent_step_ids);
|
|
1664
|
+
if (uniqueParents.size !== context.parent_step_ids.length) {
|
|
1665
|
+
return false;
|
|
1666
|
+
}
|
|
1667
|
+
return true;
|
|
1668
|
+
}
|
|
1669
|
+
function createWorkflowContext(params) {
|
|
1670
|
+
const context = {
|
|
1671
|
+
workflow_id: params.workflow_id,
|
|
1672
|
+
step_id: params.step_id,
|
|
1673
|
+
parent_step_ids: params.parent_step_ids ?? [],
|
|
1674
|
+
...params.orchestrator_id && { orchestrator_id: params.orchestrator_id },
|
|
1675
|
+
...params.orchestrator_receipt_ref && {
|
|
1676
|
+
orchestrator_receipt_ref: params.orchestrator_receipt_ref
|
|
1677
|
+
},
|
|
1678
|
+
...params.step_index !== void 0 && { step_index: params.step_index },
|
|
1679
|
+
...params.step_total !== void 0 && { step_total: params.step_total },
|
|
1680
|
+
...params.tool_name && { tool_name: params.tool_name },
|
|
1681
|
+
...params.framework && { framework: params.framework },
|
|
1682
|
+
...params.prev_receipt_hash && { prev_receipt_hash: params.prev_receipt_hash }
|
|
1683
|
+
};
|
|
1684
|
+
const validated = validateWorkflowContext(context);
|
|
1685
|
+
if (!hasValidDagSemantics(validated)) {
|
|
1686
|
+
throw new Error(
|
|
1687
|
+
"Invalid DAG semantics: step cannot be its own parent or have duplicate parents"
|
|
1688
|
+
);
|
|
1689
|
+
}
|
|
1690
|
+
return validated;
|
|
1691
|
+
}
|
|
1692
|
+
function createWorkflowSummaryAttestation(params) {
|
|
1693
|
+
const attestation = {
|
|
1694
|
+
type: WORKFLOW_SUMMARY_TYPE,
|
|
1695
|
+
issuer: params.issuer,
|
|
1696
|
+
issued_at: params.issued_at,
|
|
1697
|
+
...params.expires_at && { expires_at: params.expires_at },
|
|
1698
|
+
evidence: {
|
|
1699
|
+
workflow_id: params.workflow_id,
|
|
1700
|
+
status: params.status,
|
|
1701
|
+
started_at: params.started_at,
|
|
1702
|
+
...params.completed_at && { completed_at: params.completed_at },
|
|
1703
|
+
...params.receipt_refs && { receipt_refs: params.receipt_refs },
|
|
1704
|
+
...params.receipt_merkle_root && { receipt_merkle_root: params.receipt_merkle_root },
|
|
1705
|
+
...params.receipt_count !== void 0 && { receipt_count: params.receipt_count },
|
|
1706
|
+
orchestrator_id: params.orchestrator_id,
|
|
1707
|
+
agents_involved: params.agents_involved,
|
|
1708
|
+
...params.final_result_hash && { final_result_hash: params.final_result_hash },
|
|
1709
|
+
...params.error_context && { error_context: params.error_context }
|
|
1710
|
+
}
|
|
1711
|
+
};
|
|
1712
|
+
return validateWorkflowSummaryAttestation(attestation);
|
|
1713
|
+
}
|
|
1714
|
+
var INTERACTION_EXTENSION_KEY = "org.peacprotocol/interaction@0.1";
|
|
1715
|
+
var CANONICAL_DIGEST_ALGS = [
|
|
1716
|
+
"sha-256",
|
|
1717
|
+
// Full SHA-256
|
|
1718
|
+
"sha-256:trunc-64k",
|
|
1719
|
+
// First 64KB (65536 bytes)
|
|
1720
|
+
"sha-256:trunc-1m"
|
|
1721
|
+
// First 1MB (1048576 bytes)
|
|
1722
|
+
];
|
|
1723
|
+
var DIGEST_SIZE_CONSTANTS = {
|
|
1724
|
+
k: 1024,
|
|
1725
|
+
// 1 KB = 1024 bytes (binary)
|
|
1726
|
+
m: 1024 * 1024,
|
|
1727
|
+
// 1 MB = 1048576 bytes (binary)
|
|
1728
|
+
"trunc-64k": 65536,
|
|
1729
|
+
// 64 * 1024
|
|
1730
|
+
"trunc-1m": 1048576
|
|
1731
|
+
// 1024 * 1024
|
|
1732
|
+
};
|
|
1733
|
+
var RESULT_STATUSES = ["ok", "error", "timeout", "canceled"];
|
|
1734
|
+
var REDACTION_MODES = ["hash_only", "redacted", "plaintext_allowlisted"];
|
|
1735
|
+
var POLICY_DECISIONS = ["allow", "deny", "constrained"];
|
|
1736
|
+
var WELL_KNOWN_KINDS = [
|
|
1737
|
+
"tool.call",
|
|
1738
|
+
"http.request",
|
|
1739
|
+
"fs.read",
|
|
1740
|
+
"fs.write",
|
|
1741
|
+
"message"
|
|
1742
|
+
];
|
|
1743
|
+
var RESERVED_KIND_PREFIXES = ["peac.", "org.peacprotocol."];
|
|
1744
|
+
var INTERACTION_LIMITS = {
|
|
1745
|
+
/** Maximum interaction ID length */
|
|
1746
|
+
maxInteractionIdLength: 256,
|
|
1747
|
+
/** Maximum kind length */
|
|
1748
|
+
maxKindLength: 128,
|
|
1749
|
+
/** Maximum platform name length */
|
|
1750
|
+
maxPlatformLength: 64,
|
|
1751
|
+
/** Maximum version string length */
|
|
1752
|
+
maxVersionLength: 64,
|
|
1753
|
+
/** Maximum plugin ID length */
|
|
1754
|
+
maxPluginIdLength: 128,
|
|
1755
|
+
/** Maximum tool name length */
|
|
1756
|
+
maxToolNameLength: 256,
|
|
1757
|
+
/** Maximum provider length */
|
|
1758
|
+
maxProviderLength: 128,
|
|
1759
|
+
/** Maximum URI length */
|
|
1760
|
+
maxUriLength: 2048,
|
|
1761
|
+
/** Maximum method length */
|
|
1762
|
+
maxMethodLength: 16,
|
|
1763
|
+
/** Maximum error code length */
|
|
1764
|
+
maxErrorCodeLength: 128,
|
|
1765
|
+
/** Maximum payment reference length */
|
|
1766
|
+
maxPaymentReferenceLength: 256,
|
|
1767
|
+
/** Maximum receipt RID length */
|
|
1768
|
+
maxReceiptRidLength: 128
|
|
1769
|
+
};
|
|
1770
|
+
function requiresTool(kind) {
|
|
1771
|
+
return kind.startsWith("tool.");
|
|
1772
|
+
}
|
|
1773
|
+
function requiresResource(kind) {
|
|
1774
|
+
return kind.startsWith("http.") || kind.startsWith("fs.");
|
|
1775
|
+
}
|
|
1776
|
+
function usesReservedPrefix(kind) {
|
|
1777
|
+
return RESERVED_KIND_PREFIXES.some((prefix) => kind.startsWith(prefix));
|
|
1778
|
+
}
|
|
1779
|
+
function hasMeaningfulResource(resource) {
|
|
1780
|
+
if (!resource) return false;
|
|
1781
|
+
if (typeof resource.uri === "string" && resource.uri.length > 0) return true;
|
|
1782
|
+
return false;
|
|
1783
|
+
}
|
|
1784
|
+
var KIND_FORMAT_PATTERN = /^[a-z][a-z0-9._:-]{0,126}[a-z0-9]$/;
|
|
1785
|
+
var EXTENSION_KEY_PATTERN = /^([a-z0-9-]+\.)+[a-z0-9-]+\/[a-z][a-z0-9._:-]{0,126}[a-z0-9](?:@[0-9]+(?:\.[0-9]+)*)?$/;
|
|
1786
|
+
var DIGEST_VALUE_PATTERN = /^[a-f0-9]{64}$/;
|
|
1787
|
+
var DigestAlgSchema = z.enum(CANONICAL_DIGEST_ALGS);
|
|
1788
|
+
var DigestSchema = z.object({
|
|
1789
|
+
/** Algorithm: 'sha-256' (full) or 'sha-256:trunc-{size}' (truncated) */
|
|
1790
|
+
alg: DigestAlgSchema,
|
|
1791
|
+
/** 64 lowercase hex chars (SHA-256 output) */
|
|
1792
|
+
value: z.string().regex(DIGEST_VALUE_PATTERN, "Must be 64 lowercase hex chars"),
|
|
1793
|
+
/** Original byte length before any truncation (REQUIRED) */
|
|
1794
|
+
bytes: z.number().int().nonnegative()
|
|
1795
|
+
}).strict();
|
|
1796
|
+
var PayloadRefSchema = z.object({
|
|
1797
|
+
/** Content digest */
|
|
1798
|
+
digest: DigestSchema,
|
|
1799
|
+
/** Redaction mode */
|
|
1800
|
+
redaction: z.enum(REDACTION_MODES)
|
|
1801
|
+
}).strict();
|
|
1802
|
+
var ExecutorSchema = z.object({
|
|
1803
|
+
/** Platform identifier: 'openclaw', 'mcp', 'a2a', 'claude-code' */
|
|
1804
|
+
platform: z.string().min(1).max(INTERACTION_LIMITS.maxPlatformLength),
|
|
1805
|
+
/** Platform version */
|
|
1806
|
+
version: z.string().max(INTERACTION_LIMITS.maxVersionLength).optional(),
|
|
1807
|
+
/** Plugin that captured this */
|
|
1808
|
+
plugin_id: z.string().max(INTERACTION_LIMITS.maxPluginIdLength).optional(),
|
|
1809
|
+
/** Hash of plugin package (provenance) */
|
|
1810
|
+
plugin_digest: DigestSchema.optional()
|
|
1811
|
+
}).strict();
|
|
1812
|
+
var ToolTargetSchema = z.object({
|
|
1813
|
+
/** Tool name */
|
|
1814
|
+
name: z.string().min(1).max(INTERACTION_LIMITS.maxToolNameLength),
|
|
1815
|
+
/** Tool provider */
|
|
1816
|
+
provider: z.string().max(INTERACTION_LIMITS.maxProviderLength).optional(),
|
|
1817
|
+
/** Tool version */
|
|
1818
|
+
version: z.string().max(INTERACTION_LIMITS.maxVersionLength).optional()
|
|
1819
|
+
}).strict();
|
|
1820
|
+
var ResourceTargetSchema = z.object({
|
|
1821
|
+
/** Resource URI */
|
|
1822
|
+
uri: z.string().max(INTERACTION_LIMITS.maxUriLength).optional(),
|
|
1823
|
+
/** HTTP method or operation */
|
|
1824
|
+
method: z.string().max(INTERACTION_LIMITS.maxMethodLength).optional()
|
|
1825
|
+
}).strict();
|
|
1826
|
+
var ResultSchema = z.object({
|
|
1827
|
+
/** Result status */
|
|
1828
|
+
status: z.enum(RESULT_STATUSES),
|
|
1829
|
+
/** Error code (PEAC error code or namespaced) */
|
|
1830
|
+
error_code: z.string().max(INTERACTION_LIMITS.maxErrorCodeLength).optional(),
|
|
1831
|
+
/** Whether the operation can be retried */
|
|
1832
|
+
retryable: z.boolean().optional()
|
|
1833
|
+
}).strict();
|
|
1834
|
+
var PolicyContextSchema = z.object({
|
|
1835
|
+
/** Policy decision */
|
|
1836
|
+
decision: z.enum(POLICY_DECISIONS),
|
|
1837
|
+
/** Whether sandbox mode was enabled */
|
|
1838
|
+
sandbox_enabled: z.boolean().optional(),
|
|
1839
|
+
/** Whether elevated permissions were granted */
|
|
1840
|
+
elevated: z.boolean().optional(),
|
|
1841
|
+
/** Hash of effective policy document */
|
|
1842
|
+
effective_policy_digest: DigestSchema.optional()
|
|
1843
|
+
}).strict();
|
|
1844
|
+
var RefsSchema = z.object({
|
|
1845
|
+
/** Links to evidence.payment.reference */
|
|
1846
|
+
payment_reference: z.string().max(INTERACTION_LIMITS.maxPaymentReferenceLength).optional(),
|
|
1847
|
+
/** Correlation across receipts */
|
|
1848
|
+
related_receipt_rid: z.string().max(INTERACTION_LIMITS.maxReceiptRidLength).optional()
|
|
1849
|
+
}).strict();
|
|
1850
|
+
var KindSchema = z.string().min(2).max(INTERACTION_LIMITS.maxKindLength).regex(KIND_FORMAT_PATTERN, "Invalid kind format");
|
|
1851
|
+
var InteractionEvidenceV01BaseSchema = z.object({
|
|
1852
|
+
/** Stable ID for idempotency/dedupe (REQUIRED) */
|
|
1853
|
+
interaction_id: z.string().min(1).max(INTERACTION_LIMITS.maxInteractionIdLength),
|
|
1854
|
+
/** Event kind - open string, not closed enum (REQUIRED) */
|
|
1855
|
+
kind: KindSchema,
|
|
1856
|
+
/** Executor identity (REQUIRED) */
|
|
1857
|
+
executor: ExecutorSchema,
|
|
1858
|
+
/** Tool target (when kind is tool-related) */
|
|
1859
|
+
tool: ToolTargetSchema.optional(),
|
|
1860
|
+
/** Resource target (when kind is http/fs-related) */
|
|
1861
|
+
resource: ResourceTargetSchema.optional(),
|
|
1862
|
+
/** Input payload reference */
|
|
1863
|
+
input: PayloadRefSchema.optional(),
|
|
1864
|
+
/** Output payload reference */
|
|
1865
|
+
output: PayloadRefSchema.optional(),
|
|
1866
|
+
/** Start time (RFC 3339) (REQUIRED) */
|
|
1867
|
+
started_at: z.string().datetime(),
|
|
1868
|
+
/** Completion time (RFC 3339) */
|
|
1869
|
+
completed_at: z.string().datetime().optional(),
|
|
1870
|
+
/** Duration in milliseconds (OPTIONAL, non-normative) */
|
|
1871
|
+
duration_ms: z.number().int().nonnegative().optional(),
|
|
1872
|
+
/** Execution outcome */
|
|
1873
|
+
result: ResultSchema.optional(),
|
|
1874
|
+
/** Policy context at execution */
|
|
1875
|
+
policy: PolicyContextSchema.optional(),
|
|
1876
|
+
/** References to related evidence */
|
|
1877
|
+
refs: RefsSchema.optional(),
|
|
1878
|
+
/** Platform-specific extensions (MUST be namespaced) */
|
|
1879
|
+
extensions: z.record(z.unknown()).optional()
|
|
1880
|
+
}).strict();
|
|
1881
|
+
var InteractionEvidenceV01Schema = InteractionEvidenceV01BaseSchema.superRefine(
|
|
1882
|
+
(ev, ctx) => {
|
|
1883
|
+
if (ev.completed_at && ev.started_at) {
|
|
1884
|
+
const startedAt = new Date(ev.started_at).getTime();
|
|
1885
|
+
const completedAt = new Date(ev.completed_at).getTime();
|
|
1886
|
+
if (completedAt < startedAt) {
|
|
1887
|
+
ctx.addIssue({
|
|
1888
|
+
code: z.ZodIssueCode.custom,
|
|
1889
|
+
message: "completed_at must be >= started_at",
|
|
1890
|
+
path: ["completed_at"]
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
if (ev.output && !ev.result?.status) {
|
|
1895
|
+
ctx.addIssue({
|
|
1896
|
+
code: z.ZodIssueCode.custom,
|
|
1897
|
+
message: "result.status is required when output is present",
|
|
1898
|
+
path: ["result"]
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
if (ev.result?.status === "error") {
|
|
1902
|
+
const hasErrorCode = Boolean(ev.result.error_code);
|
|
1903
|
+
const hasNonEmptyExtensions = ev.extensions !== void 0 && Object.keys(ev.extensions).length > 0;
|
|
1904
|
+
if (!hasErrorCode && !hasNonEmptyExtensions) {
|
|
1905
|
+
ctx.addIssue({
|
|
1906
|
+
code: z.ZodIssueCode.custom,
|
|
1907
|
+
message: "error_code or non-empty extensions required when status is error",
|
|
1908
|
+
path: ["result", "error_code"]
|
|
1909
|
+
});
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
if (ev.extensions) {
|
|
1913
|
+
for (const key of Object.keys(ev.extensions)) {
|
|
1914
|
+
if (!EXTENSION_KEY_PATTERN.test(key)) {
|
|
1915
|
+
ctx.addIssue({
|
|
1916
|
+
code: z.ZodIssueCode.custom,
|
|
1917
|
+
message: `Invalid extension key format: ${key} (must be reverse-DNS/name[@version])`,
|
|
1918
|
+
path: ["extensions", key]
|
|
1919
|
+
});
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
if (usesReservedPrefix(ev.kind) && !WELL_KNOWN_KINDS.includes(ev.kind)) {
|
|
1924
|
+
ctx.addIssue({
|
|
1925
|
+
code: z.ZodIssueCode.custom,
|
|
1926
|
+
message: `kind "${ev.kind}" uses reserved prefix`,
|
|
1927
|
+
path: ["kind"]
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
if (requiresTool(ev.kind) && !ev.tool) {
|
|
1931
|
+
ctx.addIssue({
|
|
1932
|
+
code: z.ZodIssueCode.custom,
|
|
1933
|
+
message: `kind "${ev.kind}" requires tool field`,
|
|
1934
|
+
path: ["tool"]
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
if (requiresResource(ev.kind)) {
|
|
1938
|
+
if (!ev.resource) {
|
|
1939
|
+
ctx.addIssue({
|
|
1940
|
+
code: z.ZodIssueCode.custom,
|
|
1941
|
+
message: `kind "${ev.kind}" requires resource field`,
|
|
1942
|
+
path: ["resource"]
|
|
1943
|
+
});
|
|
1944
|
+
} else if (!hasMeaningfulResource(ev.resource)) {
|
|
1945
|
+
ctx.addIssue({
|
|
1946
|
+
code: z.ZodIssueCode.custom,
|
|
1947
|
+
message: `kind "${ev.kind}" requires resource.uri to be non-empty`,
|
|
1948
|
+
path: ["resource", "uri"]
|
|
1949
|
+
});
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
);
|
|
1954
|
+
function validateInteractionOrdered(input) {
|
|
1955
|
+
const errors = [];
|
|
1956
|
+
const warnings = [];
|
|
1957
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) {
|
|
1958
|
+
return {
|
|
1959
|
+
valid: false,
|
|
1960
|
+
errors: [
|
|
1961
|
+
{
|
|
1962
|
+
code: "E_INTERACTION_INVALID_FORMAT",
|
|
1963
|
+
message: "Input must be an object"
|
|
1964
|
+
}
|
|
1965
|
+
],
|
|
1966
|
+
warnings: []
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
const obj = input;
|
|
1970
|
+
if (typeof obj.interaction_id !== "string" || obj.interaction_id.length === 0) {
|
|
1971
|
+
errors.push({
|
|
1972
|
+
code: "E_INTERACTION_MISSING_ID",
|
|
1973
|
+
message: "interaction_id is required",
|
|
1974
|
+
field: "interaction_id"
|
|
1975
|
+
});
|
|
1976
|
+
} else if (obj.interaction_id.length > INTERACTION_LIMITS.maxInteractionIdLength) {
|
|
1977
|
+
errors.push({
|
|
1978
|
+
code: "E_INTERACTION_INVALID_FORMAT",
|
|
1979
|
+
message: `interaction_id exceeds max length (${INTERACTION_LIMITS.maxInteractionIdLength})`,
|
|
1980
|
+
field: "interaction_id"
|
|
1981
|
+
});
|
|
1982
|
+
}
|
|
1983
|
+
if (typeof obj.kind !== "string" || obj.kind.length === 0) {
|
|
1984
|
+
errors.push({
|
|
1985
|
+
code: "E_INTERACTION_MISSING_KIND",
|
|
1986
|
+
message: "kind is required",
|
|
1987
|
+
field: "kind"
|
|
1988
|
+
});
|
|
1989
|
+
} else if (obj.kind.length < 2 || obj.kind.length > INTERACTION_LIMITS.maxKindLength) {
|
|
1990
|
+
errors.push({
|
|
1991
|
+
code: "E_INTERACTION_INVALID_KIND_FORMAT",
|
|
1992
|
+
message: `kind must be 2-${INTERACTION_LIMITS.maxKindLength} characters`,
|
|
1993
|
+
field: "kind"
|
|
1994
|
+
});
|
|
1995
|
+
} else if (!KIND_FORMAT_PATTERN.test(obj.kind)) {
|
|
1996
|
+
errors.push({
|
|
1997
|
+
code: "E_INTERACTION_INVALID_KIND_FORMAT",
|
|
1998
|
+
message: "kind must match pattern: lowercase, start with letter, end with alphanumeric",
|
|
1999
|
+
field: "kind"
|
|
2000
|
+
});
|
|
2001
|
+
} else {
|
|
2002
|
+
for (const prefix of RESERVED_KIND_PREFIXES) {
|
|
2003
|
+
if (obj.kind.startsWith(prefix) && !WELL_KNOWN_KINDS.includes(obj.kind)) {
|
|
2004
|
+
errors.push({
|
|
2005
|
+
code: "E_INTERACTION_KIND_RESERVED",
|
|
2006
|
+
message: `kind "${obj.kind}" uses reserved prefix "${prefix}"`,
|
|
2007
|
+
field: "kind"
|
|
2008
|
+
});
|
|
2009
|
+
break;
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
if (errors.length === 0 && !WELL_KNOWN_KINDS.includes(obj.kind)) {
|
|
2013
|
+
warnings.push({
|
|
2014
|
+
code: "W_INTERACTION_KIND_UNREGISTERED",
|
|
2015
|
+
message: `kind "${obj.kind}" is not in the well-known registry`,
|
|
2016
|
+
field: "kind"
|
|
2017
|
+
});
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
if (typeof obj.started_at !== "string") {
|
|
2021
|
+
errors.push({
|
|
2022
|
+
code: "E_INTERACTION_MISSING_STARTED_AT",
|
|
2023
|
+
message: "started_at is required",
|
|
2024
|
+
field: "started_at"
|
|
2025
|
+
});
|
|
2026
|
+
} else {
|
|
2027
|
+
const startedAtDate = new Date(obj.started_at);
|
|
2028
|
+
if (isNaN(startedAtDate.getTime())) {
|
|
2029
|
+
errors.push({
|
|
2030
|
+
code: "E_INTERACTION_MISSING_STARTED_AT",
|
|
2031
|
+
message: "started_at must be a valid ISO 8601 datetime",
|
|
2032
|
+
field: "started_at"
|
|
2033
|
+
});
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
if (typeof obj.executor !== "object" || obj.executor === null) {
|
|
2037
|
+
errors.push({
|
|
2038
|
+
code: "E_INTERACTION_MISSING_EXECUTOR",
|
|
2039
|
+
message: "executor is required",
|
|
2040
|
+
field: "executor"
|
|
2041
|
+
});
|
|
2042
|
+
} else {
|
|
2043
|
+
const executor = obj.executor;
|
|
2044
|
+
if (typeof executor.platform !== "string" || executor.platform.length === 0) {
|
|
2045
|
+
errors.push({
|
|
2046
|
+
code: "E_INTERACTION_MISSING_EXECUTOR",
|
|
2047
|
+
message: "executor.platform is required",
|
|
2048
|
+
field: "executor.platform"
|
|
2049
|
+
});
|
|
2050
|
+
} else if (executor.platform.length > INTERACTION_LIMITS.maxPlatformLength) {
|
|
2051
|
+
errors.push({
|
|
2052
|
+
code: "E_INTERACTION_INVALID_FORMAT",
|
|
2053
|
+
message: `executor.platform exceeds max length (${INTERACTION_LIMITS.maxPlatformLength})`,
|
|
2054
|
+
field: "executor.platform"
|
|
2055
|
+
});
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
if (errors.length > 0) {
|
|
2059
|
+
return { valid: false, errors, warnings };
|
|
2060
|
+
}
|
|
2061
|
+
const validateDigest = (digest, fieldPath) => {
|
|
2062
|
+
const digestErrors = [];
|
|
2063
|
+
if (typeof digest !== "object" || digest === null) {
|
|
2064
|
+
digestErrors.push({
|
|
2065
|
+
code: "E_INTERACTION_INVALID_DIGEST",
|
|
2066
|
+
message: "digest must be an object",
|
|
2067
|
+
field: fieldPath
|
|
2068
|
+
});
|
|
2069
|
+
return { errors: digestErrors, valid: false };
|
|
2070
|
+
}
|
|
2071
|
+
const d = digest;
|
|
2072
|
+
if (!CANONICAL_DIGEST_ALGS.includes(d.alg)) {
|
|
2073
|
+
digestErrors.push({
|
|
2074
|
+
code: "E_INTERACTION_INVALID_DIGEST_ALG",
|
|
2075
|
+
message: `digest.alg must be one of: ${CANONICAL_DIGEST_ALGS.join(", ")}`,
|
|
2076
|
+
field: `${fieldPath}.alg`
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
2079
|
+
if (typeof d.value !== "string" || !DIGEST_VALUE_PATTERN.test(d.value)) {
|
|
2080
|
+
digestErrors.push({
|
|
2081
|
+
code: "E_INTERACTION_INVALID_DIGEST",
|
|
2082
|
+
message: "digest.value must be 64 lowercase hex chars",
|
|
2083
|
+
field: `${fieldPath}.value`
|
|
2084
|
+
});
|
|
2085
|
+
}
|
|
2086
|
+
if (typeof d.bytes !== "number" || !Number.isInteger(d.bytes) || d.bytes < 0) {
|
|
2087
|
+
digestErrors.push({
|
|
2088
|
+
code: "E_INTERACTION_INVALID_DIGEST",
|
|
2089
|
+
message: "digest.bytes must be a non-negative integer",
|
|
2090
|
+
field: `${fieldPath}.bytes`
|
|
2091
|
+
});
|
|
2092
|
+
}
|
|
2093
|
+
return { errors: digestErrors, valid: digestErrors.length === 0 };
|
|
2094
|
+
};
|
|
2095
|
+
if (obj.input !== void 0) {
|
|
2096
|
+
const inputObj = obj.input;
|
|
2097
|
+
if (inputObj.digest !== void 0) {
|
|
2098
|
+
const result = validateDigest(inputObj.digest, "input.digest");
|
|
2099
|
+
errors.push(...result.errors);
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
if (obj.output !== void 0) {
|
|
2103
|
+
const outputObj = obj.output;
|
|
2104
|
+
if (outputObj.digest !== void 0) {
|
|
2105
|
+
const result = validateDigest(outputObj.digest, "output.digest");
|
|
2106
|
+
errors.push(...result.errors);
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
if (typeof obj.completed_at === "string" && typeof obj.started_at === "string") {
|
|
2110
|
+
const startedAt = new Date(obj.started_at).getTime();
|
|
2111
|
+
const completedAt = new Date(obj.completed_at).getTime();
|
|
2112
|
+
if (!isNaN(startedAt) && !isNaN(completedAt) && completedAt < startedAt) {
|
|
2113
|
+
errors.push({
|
|
2114
|
+
code: "E_INTERACTION_INVALID_TIMING",
|
|
2115
|
+
message: "completed_at must be >= started_at",
|
|
2116
|
+
field: "completed_at"
|
|
2117
|
+
});
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
if (obj.output !== void 0) {
|
|
2121
|
+
const result = obj.result;
|
|
2122
|
+
if (!result?.status) {
|
|
2123
|
+
errors.push({
|
|
2124
|
+
code: "E_INTERACTION_MISSING_RESULT",
|
|
2125
|
+
message: "result.status is required when output is present",
|
|
2126
|
+
field: "result"
|
|
2127
|
+
});
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
if (obj.result !== void 0) {
|
|
2131
|
+
const result = obj.result;
|
|
2132
|
+
if (result.status === "error") {
|
|
2133
|
+
const hasErrorCode = Boolean(result.error_code);
|
|
2134
|
+
const hasNonEmptyExtensions = obj.extensions !== void 0 && typeof obj.extensions === "object" && obj.extensions !== null && Object.keys(obj.extensions).length > 0;
|
|
2135
|
+
if (!hasErrorCode && !hasNonEmptyExtensions) {
|
|
2136
|
+
errors.push({
|
|
2137
|
+
code: "E_INTERACTION_MISSING_ERROR_DETAIL",
|
|
2138
|
+
message: "error_code or non-empty extensions required when result.status is error",
|
|
2139
|
+
field: "result.error_code"
|
|
2140
|
+
});
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
if (obj.extensions !== void 0) {
|
|
2145
|
+
if (typeof obj.extensions !== "object" || obj.extensions === null) {
|
|
2146
|
+
errors.push({
|
|
2147
|
+
code: "E_INTERACTION_INVALID_FORMAT",
|
|
2148
|
+
message: "extensions must be an object",
|
|
2149
|
+
field: "extensions"
|
|
2150
|
+
});
|
|
2151
|
+
} else {
|
|
2152
|
+
for (const key of Object.keys(obj.extensions)) {
|
|
2153
|
+
if (!EXTENSION_KEY_PATTERN.test(key)) {
|
|
2154
|
+
errors.push({
|
|
2155
|
+
code: "E_INTERACTION_INVALID_EXTENSION_KEY",
|
|
2156
|
+
message: `Invalid extension key format: "${key}" (must be reverse-DNS/name[@version])`,
|
|
2157
|
+
field: `extensions.${key}`
|
|
2158
|
+
});
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
const kind = obj.kind;
|
|
2164
|
+
const hasTool = obj.tool !== void 0;
|
|
2165
|
+
const hasResource = obj.resource !== void 0;
|
|
2166
|
+
if (requiresTool(kind) && !hasTool) {
|
|
2167
|
+
errors.push({
|
|
2168
|
+
code: "E_INTERACTION_MISSING_TARGET",
|
|
2169
|
+
message: `kind "${kind}" requires tool field`,
|
|
2170
|
+
field: "tool"
|
|
2171
|
+
});
|
|
2172
|
+
}
|
|
2173
|
+
if (requiresResource(kind)) {
|
|
2174
|
+
if (!hasResource) {
|
|
2175
|
+
errors.push({
|
|
2176
|
+
code: "E_INTERACTION_MISSING_TARGET",
|
|
2177
|
+
message: `kind "${kind}" requires resource field`,
|
|
2178
|
+
field: "resource"
|
|
2179
|
+
});
|
|
2180
|
+
} else if (!hasMeaningfulResource(obj.resource)) {
|
|
2181
|
+
errors.push({
|
|
2182
|
+
code: "E_INTERACTION_MISSING_TARGET",
|
|
2183
|
+
message: `kind "${kind}" requires resource.uri to be non-empty`,
|
|
2184
|
+
field: "resource.uri"
|
|
2185
|
+
});
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
if (!hasTool && !hasResource && errors.length === 0) {
|
|
2189
|
+
warnings.push({
|
|
2190
|
+
code: "W_INTERACTION_MISSING_TARGET",
|
|
2191
|
+
message: "Neither tool nor resource field is present",
|
|
2192
|
+
field: "tool"
|
|
2193
|
+
});
|
|
2194
|
+
}
|
|
2195
|
+
if (errors.length > 0) {
|
|
2196
|
+
return { valid: false, errors, warnings };
|
|
2197
|
+
}
|
|
2198
|
+
const zodResult = InteractionEvidenceV01Schema.safeParse(input);
|
|
2199
|
+
if (!zodResult.success) {
|
|
2200
|
+
return {
|
|
2201
|
+
valid: false,
|
|
2202
|
+
errors: [
|
|
2203
|
+
{
|
|
2204
|
+
code: "E_INTERACTION_INVALID_FORMAT",
|
|
2205
|
+
message: zodResult.error.issues[0]?.message || "Schema validation failed",
|
|
2206
|
+
field: zodResult.error.issues[0]?.path.join(".")
|
|
2207
|
+
}
|
|
2208
|
+
],
|
|
2209
|
+
warnings
|
|
2210
|
+
};
|
|
2211
|
+
}
|
|
2212
|
+
return { valid: true, value: zodResult.data, warnings };
|
|
2213
|
+
}
|
|
2214
|
+
function validateInteraction(input) {
|
|
2215
|
+
const result = validateInteractionOrdered(input);
|
|
2216
|
+
if (result.valid) {
|
|
2217
|
+
return { valid: true };
|
|
2218
|
+
}
|
|
2219
|
+
const firstError = result.errors[0];
|
|
2220
|
+
return {
|
|
2221
|
+
valid: false,
|
|
2222
|
+
error_code: firstError?.code,
|
|
2223
|
+
error_field: firstError?.field
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2226
|
+
function validateInteractionEvidence(evidence) {
|
|
2227
|
+
return InteractionEvidenceV01Schema.parse(evidence);
|
|
2228
|
+
}
|
|
2229
|
+
function isValidInteractionEvidence(evidence) {
|
|
2230
|
+
return InteractionEvidenceV01Schema.safeParse(evidence).success;
|
|
2231
|
+
}
|
|
2232
|
+
function isWellKnownKind(kind) {
|
|
2233
|
+
return WELL_KNOWN_KINDS.includes(kind);
|
|
2234
|
+
}
|
|
2235
|
+
function isReservedKindPrefix(kind) {
|
|
2236
|
+
return RESERVED_KIND_PREFIXES.some((prefix) => kind.startsWith(prefix));
|
|
2237
|
+
}
|
|
2238
|
+
function isDigestTruncated(digest) {
|
|
2239
|
+
return digest.alg.startsWith("sha-256:trunc-");
|
|
2240
|
+
}
|
|
2241
|
+
function getInteraction(receipt) {
|
|
2242
|
+
return receipt.evidence?.extensions?.[INTERACTION_EXTENSION_KEY];
|
|
2243
|
+
}
|
|
2244
|
+
function setInteraction(receipt, interaction) {
|
|
2245
|
+
if (!receipt.evidence) {
|
|
2246
|
+
receipt.evidence = {};
|
|
2247
|
+
}
|
|
2248
|
+
if (!receipt.evidence.extensions) {
|
|
2249
|
+
receipt.evidence.extensions = {};
|
|
2250
|
+
}
|
|
2251
|
+
receipt.evidence.extensions[INTERACTION_EXTENSION_KEY] = interaction;
|
|
2252
|
+
}
|
|
2253
|
+
function hasInteraction(receipt) {
|
|
2254
|
+
return receipt.evidence?.extensions?.[INTERACTION_EXTENSION_KEY] !== void 0;
|
|
2255
|
+
}
|
|
2256
|
+
function createReceiptView(envelope) {
|
|
2257
|
+
const interaction = getInteraction(envelope);
|
|
2258
|
+
const workflow = envelope.auth?.extensions?.["org.peacprotocol/workflow"];
|
|
2259
|
+
return {
|
|
2260
|
+
envelope,
|
|
2261
|
+
interaction,
|
|
2262
|
+
interactions: interaction ? [interaction] : [],
|
|
2263
|
+
workflow
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
function createInteractionEvidence(params) {
|
|
2267
|
+
const evidence = {
|
|
2268
|
+
interaction_id: params.interaction_id,
|
|
2269
|
+
kind: params.kind,
|
|
2270
|
+
executor: {
|
|
2271
|
+
platform: params.executor.platform,
|
|
2272
|
+
...params.executor.version && { version: params.executor.version },
|
|
2273
|
+
...params.executor.plugin_id && { plugin_id: params.executor.plugin_id },
|
|
2274
|
+
...params.executor.plugin_digest && { plugin_digest: params.executor.plugin_digest }
|
|
2275
|
+
},
|
|
2276
|
+
...params.tool && { tool: params.tool },
|
|
2277
|
+
...params.resource && { resource: params.resource },
|
|
2278
|
+
...params.input && { input: params.input },
|
|
2279
|
+
...params.output && { output: params.output },
|
|
2280
|
+
started_at: params.started_at,
|
|
2281
|
+
...params.completed_at && { completed_at: params.completed_at },
|
|
2282
|
+
...params.duration_ms !== void 0 && { duration_ms: params.duration_ms },
|
|
2283
|
+
...params.result && { result: params.result },
|
|
2284
|
+
...params.policy && { policy: params.policy },
|
|
2285
|
+
...params.refs && { refs: params.refs },
|
|
2286
|
+
...params.extensions && { extensions: params.extensions }
|
|
2287
|
+
};
|
|
2288
|
+
return validateInteractionEvidence(evidence);
|
|
2289
|
+
}
|
|
2290
|
+
var CreditMethodSchema = z.enum(["inline", "references", "model-card"]);
|
|
2291
|
+
var CREDIT_METHODS = ["inline", "references", "model-card"];
|
|
2292
|
+
var ContributionTypeSchema = z.enum(["direct", "ecosystem", "open"]);
|
|
2293
|
+
var CONTRIBUTION_TYPES = ["direct", "ecosystem", "open"];
|
|
2294
|
+
var CreditObligationSchema = z.object({
|
|
2295
|
+
/** Whether credit is required (REQUIRED) */
|
|
2296
|
+
required: z.boolean(),
|
|
2297
|
+
/** URL for citation/attribution (OPTIONAL) */
|
|
2298
|
+
citation_url: z.string().url().max(2048).optional(),
|
|
2299
|
+
/** How credit should be provided (OPTIONAL, defaults to implementation choice) */
|
|
2300
|
+
method: CreditMethodSchema.optional(),
|
|
2301
|
+
/** Human-readable credit text template (OPTIONAL) */
|
|
2302
|
+
credit_text: z.string().max(1024).optional()
|
|
2303
|
+
}).strict().superRefine((data, ctx) => {
|
|
2304
|
+
if (data.required && !data.citation_url && !data.credit_text && !data.method) {
|
|
2305
|
+
ctx.addIssue({
|
|
2306
|
+
code: z.ZodIssueCode.custom,
|
|
2307
|
+
message: "When credit is required, at least one of citation_url, credit_text, or method must be specified",
|
|
2308
|
+
path: ["required"]
|
|
2309
|
+
});
|
|
2310
|
+
}
|
|
2311
|
+
});
|
|
2312
|
+
var ContributionObligationSchema = z.object({
|
|
2313
|
+
/** Type of contribution (REQUIRED) */
|
|
2314
|
+
type: ContributionTypeSchema,
|
|
2315
|
+
/** Destination for contributions (OPTIONAL, e.g., fund URL, wallet address) */
|
|
2316
|
+
destination: z.string().max(2048).optional(),
|
|
2317
|
+
/** Minimum contribution amount in minor units (OPTIONAL) */
|
|
2318
|
+
min_amount: z.number().int().min(0).optional(),
|
|
2319
|
+
/** Currency for min_amount (OPTIONAL, ISO 4217 or crypto symbol like USDC) */
|
|
2320
|
+
currency: z.string().min(3).max(8).regex(/^[A-Z0-9]{3,8}$/).optional()
|
|
2321
|
+
}).strict().superRefine((data, ctx) => {
|
|
2322
|
+
if ((data.type === "direct" || data.type === "ecosystem") && !data.destination) {
|
|
2323
|
+
ctx.addIssue({
|
|
2324
|
+
code: z.ZodIssueCode.custom,
|
|
2325
|
+
message: `Destination is required when contribution type is '${data.type}'`,
|
|
2326
|
+
path: ["destination"]
|
|
2327
|
+
});
|
|
2328
|
+
}
|
|
2329
|
+
if (data.min_amount !== void 0 && !data.currency) {
|
|
2330
|
+
ctx.addIssue({
|
|
2331
|
+
code: z.ZodIssueCode.custom,
|
|
2332
|
+
message: "Currency is required when min_amount is specified",
|
|
2333
|
+
path: ["currency"]
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2336
|
+
});
|
|
2337
|
+
var OBLIGATIONS_EXTENSION_KEY = "peac/obligations";
|
|
2338
|
+
var ObligationsExtensionSchema = z.object({
|
|
2339
|
+
/** Credit/attribution obligations (OPTIONAL) */
|
|
2340
|
+
credit: CreditObligationSchema.optional(),
|
|
2341
|
+
/** Contribution/payment model (OPTIONAL) */
|
|
2342
|
+
contribution: ContributionObligationSchema.optional()
|
|
2343
|
+
}).strict();
|
|
2344
|
+
function validateCreditObligation(data) {
|
|
2345
|
+
const result = CreditObligationSchema.safeParse(data);
|
|
2346
|
+
if (result.success) {
|
|
2347
|
+
return { ok: true, value: result.data };
|
|
2348
|
+
}
|
|
2349
|
+
return { ok: false, error: result.error.message };
|
|
2350
|
+
}
|
|
2351
|
+
function validateContributionObligation(data) {
|
|
2352
|
+
const result = ContributionObligationSchema.safeParse(data);
|
|
2353
|
+
if (result.success) {
|
|
2354
|
+
return { ok: true, value: result.data };
|
|
2355
|
+
}
|
|
2356
|
+
return { ok: false, error: result.error.message };
|
|
2357
|
+
}
|
|
2358
|
+
function validateObligationsExtension(data) {
|
|
2359
|
+
const result = ObligationsExtensionSchema.safeParse(data);
|
|
2360
|
+
if (result.success) {
|
|
2361
|
+
return { ok: true, value: result.data };
|
|
2362
|
+
}
|
|
2363
|
+
return { ok: false, error: result.error.message };
|
|
2364
|
+
}
|
|
2365
|
+
function extractObligationsExtension(extensions) {
|
|
2366
|
+
if (!extensions || !(OBLIGATIONS_EXTENSION_KEY in extensions)) {
|
|
2367
|
+
return void 0;
|
|
2368
|
+
}
|
|
2369
|
+
const result = validateObligationsExtension(extensions[OBLIGATIONS_EXTENSION_KEY]);
|
|
2370
|
+
if (result.ok) {
|
|
2371
|
+
return result.value;
|
|
2372
|
+
}
|
|
2373
|
+
return void 0;
|
|
2374
|
+
}
|
|
2375
|
+
function isCreditRequired(obligations) {
|
|
2376
|
+
return obligations?.credit?.required === true;
|
|
2377
|
+
}
|
|
2378
|
+
function isContributionRequired(obligations) {
|
|
2379
|
+
const type = obligations?.contribution?.type;
|
|
2380
|
+
return type === "direct" || type === "ecosystem";
|
|
2381
|
+
}
|
|
2382
|
+
function createCreditObligation(params) {
|
|
2383
|
+
const credit = { required: params.required };
|
|
2384
|
+
if (params.citation_url) credit.citation_url = params.citation_url;
|
|
2385
|
+
if (params.method) credit.method = params.method;
|
|
2386
|
+
if (params.credit_text) credit.credit_text = params.credit_text;
|
|
2387
|
+
return { credit };
|
|
2388
|
+
}
|
|
2389
|
+
function createContributionObligation(params) {
|
|
2390
|
+
const contribution = { type: params.type };
|
|
2391
|
+
if (params.destination) contribution.destination = params.destination;
|
|
2392
|
+
if (params.min_amount !== void 0) contribution.min_amount = params.min_amount;
|
|
2393
|
+
if (params.currency) contribution.currency = params.currency;
|
|
2394
|
+
return { contribution };
|
|
2395
|
+
}
|
|
2396
|
+
function createObligationsExtension(credit, contribution) {
|
|
2397
|
+
const result = {};
|
|
2398
|
+
if (credit) {
|
|
2399
|
+
result.credit = { required: credit.required };
|
|
2400
|
+
if (credit.citation_url) result.credit.citation_url = credit.citation_url;
|
|
2401
|
+
if (credit.method) result.credit.method = credit.method;
|
|
2402
|
+
if (credit.credit_text) result.credit.credit_text = credit.credit_text;
|
|
2403
|
+
}
|
|
2404
|
+
if (contribution) {
|
|
2405
|
+
result.contribution = { type: contribution.type };
|
|
2406
|
+
if (contribution.destination) result.contribution.destination = contribution.destination;
|
|
2407
|
+
if (contribution.min_amount !== void 0)
|
|
2408
|
+
result.contribution.min_amount = contribution.min_amount;
|
|
2409
|
+
if (contribution.currency) result.contribution.currency = contribution.currency;
|
|
2410
|
+
}
|
|
2411
|
+
return result;
|
|
2412
|
+
}
|
|
2413
|
+
var ATTESTATION_RECEIPT_TYPE = "peac/attestation-receipt";
|
|
2414
|
+
var MIDDLEWARE_INTERACTION_KEY = "org.peacprotocol/middleware-interaction@0.1";
|
|
2415
|
+
var ATTESTATION_LIMITS = {
|
|
2416
|
+
/** Maximum issuer URL length */
|
|
2417
|
+
maxIssuerLength: 2048,
|
|
2418
|
+
/** Maximum audience URL length */
|
|
2419
|
+
maxAudienceLength: 2048,
|
|
2420
|
+
/** Maximum subject length */
|
|
2421
|
+
maxSubjectLength: 256,
|
|
2422
|
+
/** Maximum path length in interaction binding */
|
|
2423
|
+
maxPathLength: 2048,
|
|
2424
|
+
/** Maximum method length */
|
|
2425
|
+
maxMethodLength: 16,
|
|
2426
|
+
/** Maximum HTTP status code */
|
|
2427
|
+
maxStatusCode: 599,
|
|
2428
|
+
/** Minimum HTTP status code */
|
|
2429
|
+
minStatusCode: 100
|
|
2430
|
+
};
|
|
2431
|
+
var httpsUrl2 = z.string().url().max(ATTESTATION_LIMITS.maxIssuerLength).refine((url) => url.startsWith("https://"), "Must be HTTPS URL");
|
|
2432
|
+
var uuidv72 = z.string().regex(
|
|
2433
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
|
2434
|
+
"Must be UUIDv7 format"
|
|
2435
|
+
);
|
|
2436
|
+
var MinimalInteractionBindingSchema = z.object({
|
|
2437
|
+
/** HTTP method (uppercase, e.g., GET, POST) */
|
|
2438
|
+
method: z.string().min(1).max(ATTESTATION_LIMITS.maxMethodLength).transform((m) => m.toUpperCase()),
|
|
2439
|
+
/** Request path (no query string by default) */
|
|
2440
|
+
path: z.string().min(1).max(ATTESTATION_LIMITS.maxPathLength),
|
|
2441
|
+
/** HTTP response status code */
|
|
2442
|
+
status: z.number().int().min(ATTESTATION_LIMITS.minStatusCode).max(ATTESTATION_LIMITS.maxStatusCode)
|
|
2443
|
+
}).strict();
|
|
2444
|
+
var AttestationExtensionsSchema = z.record(z.string(), z.unknown());
|
|
2445
|
+
var AttestationReceiptClaimsSchema = z.object({
|
|
2446
|
+
/** Issuer URL (normalized, no trailing slash) */
|
|
2447
|
+
iss: httpsUrl2,
|
|
2448
|
+
/** Audience URL */
|
|
2449
|
+
aud: httpsUrl2,
|
|
2450
|
+
/** Issued at (Unix seconds) */
|
|
2451
|
+
iat: z.number().int().nonnegative(),
|
|
2452
|
+
/** Expiration (Unix seconds) */
|
|
2453
|
+
exp: z.number().int().nonnegative(),
|
|
2454
|
+
/** Receipt ID (UUIDv7) */
|
|
2455
|
+
rid: uuidv72,
|
|
2456
|
+
/** Subject identifier (optional) */
|
|
2457
|
+
sub: z.string().max(ATTESTATION_LIMITS.maxSubjectLength).optional(),
|
|
2458
|
+
/** Extensions (optional) */
|
|
2459
|
+
ext: AttestationExtensionsSchema.optional()
|
|
2460
|
+
}).strict();
|
|
2461
|
+
function validateAttestationReceiptClaims(input) {
|
|
2462
|
+
const result = AttestationReceiptClaimsSchema.safeParse(input);
|
|
2463
|
+
if (result.success) {
|
|
2464
|
+
return { valid: true };
|
|
2465
|
+
}
|
|
2466
|
+
const firstIssue = result.error.issues[0];
|
|
2467
|
+
return {
|
|
2468
|
+
valid: false,
|
|
2469
|
+
error_code: "E_ATTESTATION_INVALID_CLAIMS",
|
|
2470
|
+
error_message: firstIssue?.message || "Invalid attestation receipt claims"
|
|
2471
|
+
};
|
|
2472
|
+
}
|
|
2473
|
+
function isAttestationReceiptClaims(claims) {
|
|
2474
|
+
return AttestationReceiptClaimsSchema.safeParse(claims).success;
|
|
2475
|
+
}
|
|
2476
|
+
function validateMinimalInteractionBinding(input) {
|
|
2477
|
+
const result = MinimalInteractionBindingSchema.safeParse(input);
|
|
2478
|
+
if (result.success) {
|
|
2479
|
+
return { valid: true };
|
|
2480
|
+
}
|
|
2481
|
+
const firstIssue = result.error.issues[0];
|
|
2482
|
+
return {
|
|
2483
|
+
valid: false,
|
|
2484
|
+
error_code: "E_ATTESTATION_INVALID_INTERACTION",
|
|
2485
|
+
error_message: firstIssue?.message || "Invalid interaction binding"
|
|
2486
|
+
};
|
|
2487
|
+
}
|
|
2488
|
+
function isMinimalInteractionBinding(binding) {
|
|
2489
|
+
return MinimalInteractionBindingSchema.safeParse(binding).success;
|
|
2490
|
+
}
|
|
2491
|
+
function createAttestationReceiptClaims(params) {
|
|
2492
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2493
|
+
const expiresIn = params.expiresIn ?? 300;
|
|
2494
|
+
let normalizedIssuer = params.issuer;
|
|
2495
|
+
while (normalizedIssuer.endsWith("/")) {
|
|
2496
|
+
normalizedIssuer = normalizedIssuer.slice(0, -1);
|
|
2497
|
+
}
|
|
2498
|
+
const ext = { ...params.extensions };
|
|
2499
|
+
if (params.interaction) {
|
|
2500
|
+
ext[MIDDLEWARE_INTERACTION_KEY] = params.interaction;
|
|
2501
|
+
}
|
|
2502
|
+
const claims = {
|
|
2503
|
+
iss: normalizedIssuer,
|
|
2504
|
+
aud: params.audience,
|
|
2505
|
+
iat: now,
|
|
2506
|
+
exp: now + expiresIn,
|
|
2507
|
+
rid: params.rid,
|
|
2508
|
+
...params.sub && { sub: params.sub },
|
|
2509
|
+
...Object.keys(ext).length > 0 && { ext }
|
|
2510
|
+
};
|
|
2511
|
+
return AttestationReceiptClaimsSchema.parse(claims);
|
|
2512
|
+
}
|
|
2513
|
+
function isAttestationOnly(claims) {
|
|
2514
|
+
return !("amt" in claims) && !("cur" in claims) && !("payment" in claims);
|
|
2515
|
+
}
|
|
2516
|
+
function isPaymentReceipt(claims) {
|
|
2517
|
+
return "amt" in claims && "cur" in claims && "payment" in claims;
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
// src/receipt-parser.ts
|
|
2521
|
+
function classifyReceipt(obj) {
|
|
2522
|
+
if ("amt" in obj || "cur" in obj || "payment" in obj) {
|
|
2523
|
+
return "commerce";
|
|
2524
|
+
}
|
|
2525
|
+
return "attestation";
|
|
2526
|
+
}
|
|
2527
|
+
function parseReceiptClaims(input, _opts) {
|
|
2528
|
+
if (input === null || input === void 0 || typeof input !== "object" || Array.isArray(input)) {
|
|
2529
|
+
return {
|
|
2530
|
+
ok: false,
|
|
2531
|
+
error: {
|
|
2532
|
+
code: "E_PARSE_INVALID_INPUT",
|
|
2533
|
+
message: "Input must be a non-null object"
|
|
2534
|
+
}
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
const obj = input;
|
|
2538
|
+
const variant = classifyReceipt(obj);
|
|
2539
|
+
if (variant === "commerce") {
|
|
2540
|
+
const result2 = ReceiptClaimsSchema.safeParse(obj);
|
|
2541
|
+
if (!result2.success) {
|
|
2542
|
+
return {
|
|
2543
|
+
ok: false,
|
|
2544
|
+
error: {
|
|
2545
|
+
code: "E_PARSE_COMMERCE_INVALID",
|
|
2546
|
+
message: `Commerce receipt validation failed: ${result2.error.issues.map((i) => i.message).join("; ")}`,
|
|
2547
|
+
issues: result2.error.issues
|
|
2548
|
+
}
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
return {
|
|
2552
|
+
ok: true,
|
|
2553
|
+
variant: "commerce",
|
|
2554
|
+
claims: result2.data
|
|
2555
|
+
};
|
|
2556
|
+
}
|
|
2557
|
+
const result = AttestationReceiptClaimsSchema.safeParse(obj);
|
|
2558
|
+
if (!result.success) {
|
|
2559
|
+
return {
|
|
2560
|
+
ok: false,
|
|
2561
|
+
error: {
|
|
2562
|
+
code: "E_PARSE_ATTESTATION_INVALID",
|
|
2563
|
+
message: `Attestation receipt validation failed: ${result.error.issues.map((i) => i.message).join("; ")}`,
|
|
2564
|
+
issues: result.error.issues
|
|
2565
|
+
}
|
|
2566
|
+
};
|
|
2567
|
+
}
|
|
2568
|
+
return {
|
|
2569
|
+
ok: true,
|
|
2570
|
+
variant: "attestation",
|
|
2571
|
+
claims: result.data
|
|
2572
|
+
};
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
export { AGENT_IDENTITY_TYPE, AIPREFSnapshot as AIPREFSnapshotSchema, ATTESTATION_LIMITS, ATTESTATION_RECEIPT_TYPE, ATTRIBUTION_LIMITS, ATTRIBUTION_TYPE, ATTRIBUTION_USAGES, AgentIdentityAttestationSchema, AgentIdentityEvidenceSchema, AgentIdentityVerifiedSchema, AgentProofSchema, AttestationExtensionsSchema, AttestationReceiptClaimsSchema, AttestationSchema, AttributionAttestationSchema, AttributionEvidenceSchema, AttributionSourceSchema, AttributionUsageSchema, BindingDetailsSchema, CANONICAL_DIGEST_ALGS, CANONICAL_PURPOSES, CONTRIBUTION_TYPES, CONTROL_TYPES, CREDIT_METHODS, CanonicalPurposeSchema, ContactMethodSchema, ContentHashSchema, ContributionObligationSchema, ContributionTypeSchema, ControlBlockSchema, ControlDecisionSchema, ControlLicensingModeSchema, ControlPurposeSchema, ControlStepSchema, ControlTypeSchema, CreditMethodSchema, CreditObligationSchema, DERIVATION_TYPES, DIGEST_SIZE_CONSTANTS, DIGEST_VALUE_PATTERN, DISPUTE_GROUNDS_CODES, DISPUTE_LIMITS, DISPUTE_OUTCOMES, DISPUTE_STATES, DISPUTE_TARGET_TYPES, DISPUTE_TRANSITIONS, DISPUTE_TYPE, DISPUTE_TYPES, DerivationTypeSchema, DigestAlgSchema, DigestSchema, DisputeAttestationSchema, DisputeContactSchema, DisputeEvidenceSchema, DisputeGroundsCodeSchema, DisputeGroundsSchema, DisputeIdSchema, DisputeOutcomeSchema, DisputeResolutionSchema, DisputeStateSchema, DisputeTargetTypeSchema, DisputeTypeSchema, DocumentRefSchema, ERROR_CATEGORIES_CANONICAL, ERROR_CODES, EXTENSION_KEY_PATTERN, ExecutorSchema, Extensions, ExtensionsSchema, HashAlgorithmSchema, HashEncodingSchema, INTERACTION_EXTENSION_KEY, INTERACTION_LIMITS, INTERNAL_PURPOSE_UNDECLARED, IdentityBindingSchema, InteractionEvidenceV01Schema, JSON_EVIDENCE_LIMITS, JWSHeader, JsonArraySchema, JsonObjectSchema, JsonPrimitiveSchema, JsonValueSchema, KIND_FORMAT_PATTERN, KindSchema, MAX_PURPOSE_TOKENS_PER_REQUEST, MAX_PURPOSE_TOKEN_LENGTH, MIDDLEWARE_INTERACTION_KEY, MinimalInteractionBindingSchema, NormalizedPayment, OBLIGATIONS_EXTENSION_KEY, ORCHESTRATION_FRAMEWORKS, ObligationsExtensionSchema, OrchestrationFrameworkSchema, PEAC_ALG, PEAC_DISCOVERY_MAX_BYTES, PEAC_DISCOVERY_PATH, PEAC_ISSUER_CONFIG_MAX_BYTES, PEAC_ISSUER_CONFIG_PATH, PEAC_ISSUER_CONFIG_VERSION, PEAC_POLICY_FALLBACK_PATH, PEAC_POLICY_MAX_BYTES, PEAC_POLICY_PATH, PEAC_PURPOSE_APPLIED_HEADER, PEAC_PURPOSE_HEADER, PEAC_PURPOSE_REASON_HEADER, PEAC_RECEIPT_HEADER, PEAC_RECEIPT_SCHEMA_URL, PEAC_WIRE_TYP, POLICY_DECISIONS, PROOF_METHODS, PURPOSE_REASONS, PURPOSE_TOKEN_REGEX, PayloadRefSchema, PaymentEvidenceSchema, PaymentRoutingSchema, PaymentSplitSchema, PolicyContextSchema, ProofMethodSchema, PurposeReasonSchema, PurposeTokenSchema, REDACTION_MODES, REMEDIATION_TYPES, RESERVED_KIND_PREFIXES, RESULT_STATUSES, ReceiptClaims, ReceiptClaimsSchema, RefsSchema, RemediationSchema, RemediationTypeSchema, ResourceTargetSchema, ResultSchema, STEP_ID_PATTERN, StepIdSchema, SubjectProfileSchema, SubjectProfileSnapshotSchema, Subject as SubjectSchema, SubjectTypeSchema, TERMINAL_STATES, ToolTargetSchema, VerifyRequest as VerifyRequestSchema, WELL_KNOWN_KINDS, WORKFLOW_EXTENSION_KEY, WORKFLOW_ID_PATTERN, WORKFLOW_LIMITS, WORKFLOW_STATUSES, WORKFLOW_SUMMARY_TYPE, WorkflowContextSchema, WorkflowErrorContextSchema, WorkflowIdSchema, WorkflowStatusSchema, WorkflowSummaryAttestationSchema, WorkflowSummaryEvidenceSchema, assertJsonSafeIterative, canTransitionTo, computeTotalWeight, createAgentIdentityAttestation, createAttestationReceiptClaims, createAttributionAttestation, createContributionObligation, createCreditObligation, createDisputeAttestation, createEvidenceNotJsonError, createInteractionEvidence, createObligationsExtension, createPEACError, createReceiptView, createStepId, createWorkflowContext, createWorkflowContextInvalidError, createWorkflowDagInvalidError, createWorkflowId, createWorkflowSummaryAttestation, deriveKnownPurposes, detectCycleInSources, determinePurposeReason, extractObligationsExtension, getInteraction, getValidTransitions, hasInteraction, hasUnknownPurposeTokens, hasValidDagSemantics, isAgentIdentityAttestation, isAttestationExpired, isAttestationNotYetValid, isAttestationOnly, isAttestationReceiptClaims, isAttributionAttestation, isAttributionExpired, isAttributionNotYetValid, isCanonicalPurpose, isContributionRequired, isCreditRequired, isDigestTruncated, isDisputeAttestation, isDisputeExpired, isDisputeNotYetValid, isLegacyPurpose, isMinimalInteractionBinding, isPaymentReceipt, isReservedKindPrefix, isTerminalState, isTerminalWorkflowStatus, isUndeclaredPurpose, isValidDisputeAttestation, isValidInteractionEvidence, isValidPurposeReason, isValidPurposeToken, isValidWorkflowContext, isWellKnownKind, isWorkflowSummaryAttestation, mapLegacyToCanonical, normalizePurposeToken, normalizeToCanonicalOrPreserve, parsePurposeHeader, parseReceiptClaims, setInteraction, toCoreClaims, transitionDisputeState, validateAgentIdentityAttestation, validateAttestationReceiptClaims, validateAttributionAttestation, validateAttributionSource, validateContentHash, validateContributionObligation, validateCreditObligation, validateDisputeAttestation, validateDisputeContact, validateDisputeResolution, validateEvidence, validateIdentityBinding, validateInteraction, validateInteractionEvidence, validateInteractionOrdered, validateMinimalInteractionBinding, validateObligationsExtension, validatePurposeTokens, validateSubjectSnapshot, validateWorkflowContext, validateWorkflowContextOrdered, validateWorkflowSummaryAttestation };
|
|
2576
|
+
//# sourceMappingURL=index.mjs.map
|
|
2577
|
+
//# sourceMappingURL=index.mjs.map
|