@peac/schema 0.10.9 → 0.10.11

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.
Files changed (77) hide show
  1. package/LICENSE +1 -1
  2. package/dist/attestation-receipt.cjs +127 -0
  3. package/dist/attestation-receipt.cjs.map +1 -0
  4. package/dist/attestation-receipt.mjs +113 -0
  5. package/dist/attestation-receipt.mjs.map +1 -0
  6. package/dist/attribution.cjs +249 -0
  7. package/dist/attribution.cjs.map +1 -0
  8. package/dist/attribution.mjs +227 -0
  9. package/dist/attribution.mjs.map +1 -0
  10. package/dist/dispute.d.ts.map +1 -1
  11. package/dist/index.cjs +2818 -0
  12. package/dist/index.cjs.map +1 -0
  13. package/dist/index.mjs +2577 -0
  14. package/dist/index.mjs.map +1 -0
  15. package/dist/interaction.cjs +619 -0
  16. package/dist/interaction.cjs.map +1 -0
  17. package/dist/interaction.mjs +583 -0
  18. package/dist/interaction.mjs.map +1 -0
  19. package/dist/normalize.cjs +84 -0
  20. package/dist/normalize.cjs.map +1 -0
  21. package/dist/normalize.d.ts +15 -9
  22. package/dist/normalize.d.ts.map +1 -1
  23. package/dist/normalize.mjs +82 -0
  24. package/dist/normalize.mjs.map +1 -0
  25. package/dist/receipt-parser.cjs +333 -0
  26. package/dist/receipt-parser.cjs.map +1 -0
  27. package/dist/receipt-parser.mjs +331 -0
  28. package/dist/receipt-parser.mjs.map +1 -0
  29. package/dist/workflow.cjs +321 -0
  30. package/dist/workflow.cjs.map +1 -0
  31. package/dist/workflow.mjs +292 -0
  32. package/dist/workflow.mjs.map +1 -0
  33. package/package.json +50 -6
  34. package/dist/agent-identity.js +0 -357
  35. package/dist/agent-identity.js.map +0 -1
  36. package/dist/attestation-receipt.js +0 -249
  37. package/dist/attestation-receipt.js.map +0 -1
  38. package/dist/attribution.js +0 -444
  39. package/dist/attribution.js.map +0 -1
  40. package/dist/constants.js +0 -73
  41. package/dist/constants.js.map +0 -1
  42. package/dist/control.js +0 -9
  43. package/dist/control.js.map +0 -1
  44. package/dist/dispute.js +0 -832
  45. package/dist/dispute.js.map +0 -1
  46. package/dist/envelope.js +0 -9
  47. package/dist/envelope.js.map +0 -1
  48. package/dist/errors.js +0 -116
  49. package/dist/errors.js.map +0 -1
  50. package/dist/evidence.js +0 -8
  51. package/dist/evidence.js.map +0 -1
  52. package/dist/index.js +0 -283
  53. package/dist/index.js.map +0 -1
  54. package/dist/interaction.js +0 -918
  55. package/dist/interaction.js.map +0 -1
  56. package/dist/json.js +0 -267
  57. package/dist/json.js.map +0 -1
  58. package/dist/normalize.js +0 -103
  59. package/dist/normalize.js.map +0 -1
  60. package/dist/obligations.js +0 -337
  61. package/dist/obligations.js.map +0 -1
  62. package/dist/purpose.js +0 -296
  63. package/dist/purpose.js.map +0 -1
  64. package/dist/receipt-parser.js +0 -89
  65. package/dist/receipt-parser.js.map +0 -1
  66. package/dist/schemas.js +0 -7
  67. package/dist/schemas.js.map +0 -1
  68. package/dist/subject.js +0 -9
  69. package/dist/subject.js.map +0 -1
  70. package/dist/types.js +0 -6
  71. package/dist/types.js.map +0 -1
  72. package/dist/validators.js +0 -421
  73. package/dist/validators.js.map +0 -1
  74. package/dist/version.js +0 -7
  75. package/dist/version.js.map +0 -1
  76. package/dist/workflow.js +0 -523
  77. package/dist/workflow.js.map +0 -1
@@ -1,918 +0,0 @@
1
- "use strict";
2
- /**
3
- * PEAC Interaction Evidence Types (v0.10.7+)
4
- *
5
- * Interaction evidence captures what happened during agent execution.
6
- * This is an extension type - stored at evidence.extensions["org.peacprotocol/interaction@0.1"]
7
- *
8
- * Design principles:
9
- * - Wire-stable: Uses extensions mechanism (NOT a top-level evidence field)
10
- * - Hash-only by default: Privacy-preserving content digests
11
- * - Open kind registry: Not a closed enum, converges through convention
12
- * - 1 receipt = 1 interaction: No batching (use bundles for that)
13
- *
14
- * @see docs/specs/INTERACTION-EVIDENCE.md
15
- */
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.InteractionEvidenceV01Schema = exports.KindSchema = exports.RefsSchema = exports.PolicyContextSchema = exports.ResultSchema = exports.ResourceTargetSchema = exports.ToolTargetSchema = exports.ExecutorSchema = exports.PayloadRefSchema = exports.DigestSchema = exports.DigestAlgSchema = exports.DIGEST_VALUE_PATTERN = exports.EXTENSION_KEY_PATTERN = exports.KIND_FORMAT_PATTERN = exports.INTERACTION_LIMITS = exports.RESERVED_KIND_PREFIXES = exports.WELL_KNOWN_KINDS = exports.POLICY_DECISIONS = exports.REDACTION_MODES = exports.RESULT_STATUSES = exports.DIGEST_SIZE_CONSTANTS = exports.CANONICAL_DIGEST_ALGS = exports.INTERACTION_EXTENSION_KEY = void 0;
18
- exports.validateInteractionOrdered = validateInteractionOrdered;
19
- exports.validateInteraction = validateInteraction;
20
- exports.validateInteractionEvidence = validateInteractionEvidence;
21
- exports.isValidInteractionEvidence = isValidInteractionEvidence;
22
- exports.isWellKnownKind = isWellKnownKind;
23
- exports.isReservedKindPrefix = isReservedKindPrefix;
24
- exports.isDigestTruncated = isDigestTruncated;
25
- exports.getInteraction = getInteraction;
26
- exports.setInteraction = setInteraction;
27
- exports.hasInteraction = hasInteraction;
28
- exports.createReceiptView = createReceiptView;
29
- exports.createInteractionEvidence = createInteractionEvidence;
30
- const zod_1 = require("zod");
31
- // ============================================================================
32
- // Constants
33
- // ============================================================================
34
- /**
35
- * Extension key for interaction evidence
36
- * Used in evidence.extensions['org.peacprotocol/interaction@0.1']
37
- */
38
- exports.INTERACTION_EXTENSION_KEY = 'org.peacprotocol/interaction@0.1';
39
- /**
40
- * Canonical digest algorithms (NORMATIVE)
41
- *
42
- * k = 1024 bytes (binary), m = 1024*1024 bytes (binary)
43
- * NO case-insensitivity, NO regex matching - strict canonical set only.
44
- */
45
- exports.CANONICAL_DIGEST_ALGS = [
46
- 'sha-256', // Full SHA-256
47
- 'sha-256:trunc-64k', // First 64KB (65536 bytes)
48
- 'sha-256:trunc-1m', // First 1MB (1048576 bytes)
49
- ];
50
- /**
51
- * Size constants for digest truncation (NORMATIVE)
52
- */
53
- exports.DIGEST_SIZE_CONSTANTS = {
54
- k: 1024, // 1 KB = 1024 bytes (binary)
55
- m: 1024 * 1024, // 1 MB = 1048576 bytes (binary)
56
- 'trunc-64k': 65536, // 64 * 1024
57
- 'trunc-1m': 1048576, // 1024 * 1024
58
- };
59
- /**
60
- * Result status values for interaction outcomes
61
- */
62
- exports.RESULT_STATUSES = ['ok', 'error', 'timeout', 'canceled'];
63
- /**
64
- * Redaction modes for payload references
65
- */
66
- exports.REDACTION_MODES = ['hash_only', 'redacted', 'plaintext_allowlisted'];
67
- /**
68
- * Policy decision values
69
- */
70
- exports.POLICY_DECISIONS = ['allow', 'deny', 'constrained'];
71
- /**
72
- * Well-known interaction kinds (informational, not normative)
73
- *
74
- * The kind field accepts any string matching the kind grammar.
75
- * These well-known values are listed in the PEAC registries for interop.
76
- * New kinds do NOT require protocol updates - just use the name.
77
- *
78
- * Custom kinds: use "custom:<reverse-dns>" or "<reverse-dns>:<token>"
79
- */
80
- exports.WELL_KNOWN_KINDS = [
81
- 'tool.call',
82
- 'http.request',
83
- 'fs.read',
84
- 'fs.write',
85
- 'message',
86
- ];
87
- /**
88
- * Reserved kind prefixes (NORMATIVE)
89
- *
90
- * Kinds starting with these prefixes that are NOT in WELL_KNOWN_KINDS
91
- * MUST be rejected to prevent namespace pollution.
92
- */
93
- exports.RESERVED_KIND_PREFIXES = ['peac.', 'org.peacprotocol.'];
94
- /**
95
- * Interaction evidence limits (DoS protection)
96
- */
97
- exports.INTERACTION_LIMITS = {
98
- /** Maximum interaction ID length */
99
- maxInteractionIdLength: 256,
100
- /** Maximum kind length */
101
- maxKindLength: 128,
102
- /** Maximum platform name length */
103
- maxPlatformLength: 64,
104
- /** Maximum version string length */
105
- maxVersionLength: 64,
106
- /** Maximum plugin ID length */
107
- maxPluginIdLength: 128,
108
- /** Maximum tool name length */
109
- maxToolNameLength: 256,
110
- /** Maximum provider length */
111
- maxProviderLength: 128,
112
- /** Maximum URI length */
113
- maxUriLength: 2048,
114
- /** Maximum method length */
115
- maxMethodLength: 16,
116
- /** Maximum error code length */
117
- maxErrorCodeLength: 128,
118
- /** Maximum payment reference length */
119
- maxPaymentReferenceLength: 256,
120
- /** Maximum receipt RID length */
121
- maxReceiptRidLength: 128,
122
- };
123
- // ============================================================================
124
- // Shared Invariant Helpers (used by both schema and ordered validator)
125
- // ============================================================================
126
- /**
127
- * Check if a kind requires a tool target (internal helper)
128
- */
129
- function requiresTool(kind) {
130
- return kind.startsWith('tool.');
131
- }
132
- /**
133
- * Check if a kind requires a resource target (internal helper)
134
- */
135
- function requiresResource(kind) {
136
- return kind.startsWith('http.') || kind.startsWith('fs.');
137
- }
138
- /**
139
- * Check if a kind uses a reserved prefix (internal helper)
140
- */
141
- function usesReservedPrefix(kind) {
142
- return exports.RESERVED_KIND_PREFIXES.some((prefix) => kind.startsWith(prefix));
143
- }
144
- /**
145
- * Check if a resource object is meaningful - has at least uri (internal helper)
146
- * Empty resource objects {} are not valid targets.
147
- */
148
- function hasMeaningfulResource(resource) {
149
- if (!resource)
150
- return false;
151
- if (typeof resource.uri === 'string' && resource.uri.length > 0)
152
- return true;
153
- return false;
154
- }
155
- // ============================================================================
156
- // Format Patterns
157
- // ============================================================================
158
- /**
159
- * Kind format pattern
160
- *
161
- * Lowercase letters, digits, dots, underscores, colons, hyphens.
162
- * Must start with a letter, end with letter or digit.
163
- * Min 2 chars, max 128 chars.
164
- *
165
- * Examples: "tool.call", "http.request", "custom:com.example.foo"
166
- */
167
- exports.KIND_FORMAT_PATTERN = /^[a-z][a-z0-9._:-]{0,126}[a-z0-9]$/;
168
- /**
169
- * Extension key format pattern (NORMATIVE)
170
- *
171
- * Reverse-DNS domain + '/' + key name + optional version
172
- * Pattern: ^([a-z0-9-]+\.)+[a-z0-9-]+\/[a-z][a-z0-9._:-]{0,126}[a-z0-9](?:@[0-9]+(?:\.[0-9]+)*)?$
173
- *
174
- * Examples:
175
- * - "com.example/foo"
176
- * - "org.peacprotocol/interaction@0.1"
177
- * - "io.vendor/custom-data"
178
- */
179
- exports.EXTENSION_KEY_PATTERN = /^([a-z0-9-]+\.)+[a-z0-9-]+\/[a-z][a-z0-9._:-]{0,126}[a-z0-9](?:@[0-9]+(?:\.[0-9]+)*)?$/;
180
- /**
181
- * Digest value format (64 lowercase hex chars)
182
- */
183
- exports.DIGEST_VALUE_PATTERN = /^[a-f0-9]{64}$/;
184
- // ============================================================================
185
- // Zod Schemas
186
- // ============================================================================
187
- /**
188
- * Digest algorithm schema - strict canonical set
189
- */
190
- exports.DigestAlgSchema = zod_1.z.enum(exports.CANONICAL_DIGEST_ALGS);
191
- /**
192
- * Digest schema for privacy-preserving content hashing
193
- */
194
- exports.DigestSchema = zod_1.z
195
- .object({
196
- /** Algorithm: 'sha-256' (full) or 'sha-256:trunc-{size}' (truncated) */
197
- alg: exports.DigestAlgSchema,
198
- /** 64 lowercase hex chars (SHA-256 output) */
199
- value: zod_1.z.string().regex(exports.DIGEST_VALUE_PATTERN, 'Must be 64 lowercase hex chars'),
200
- /** Original byte length before any truncation (REQUIRED) */
201
- bytes: zod_1.z.number().int().nonnegative(),
202
- })
203
- .strict();
204
- /**
205
- * Payload reference schema (on-wire, portable)
206
- */
207
- exports.PayloadRefSchema = zod_1.z
208
- .object({
209
- /** Content digest */
210
- digest: exports.DigestSchema,
211
- /** Redaction mode */
212
- redaction: zod_1.z.enum(exports.REDACTION_MODES),
213
- })
214
- .strict();
215
- /**
216
- * Executor identity schema (who ran this)
217
- */
218
- exports.ExecutorSchema = zod_1.z
219
- .object({
220
- /** Platform identifier: 'openclaw', 'mcp', 'a2a', 'claude-code' */
221
- platform: zod_1.z.string().min(1).max(exports.INTERACTION_LIMITS.maxPlatformLength),
222
- /** Platform version */
223
- version: zod_1.z.string().max(exports.INTERACTION_LIMITS.maxVersionLength).optional(),
224
- /** Plugin that captured this */
225
- plugin_id: zod_1.z.string().max(exports.INTERACTION_LIMITS.maxPluginIdLength).optional(),
226
- /** Hash of plugin package (provenance) */
227
- plugin_digest: exports.DigestSchema.optional(),
228
- })
229
- .strict();
230
- /**
231
- * Tool target schema (for tool.call kind)
232
- */
233
- exports.ToolTargetSchema = zod_1.z
234
- .object({
235
- /** Tool name */
236
- name: zod_1.z.string().min(1).max(exports.INTERACTION_LIMITS.maxToolNameLength),
237
- /** Tool provider */
238
- provider: zod_1.z.string().max(exports.INTERACTION_LIMITS.maxProviderLength).optional(),
239
- /** Tool version */
240
- version: zod_1.z.string().max(exports.INTERACTION_LIMITS.maxVersionLength).optional(),
241
- })
242
- .strict();
243
- /**
244
- * Resource target schema (for http/fs kinds)
245
- */
246
- exports.ResourceTargetSchema = zod_1.z
247
- .object({
248
- /** Resource URI */
249
- uri: zod_1.z.string().max(exports.INTERACTION_LIMITS.maxUriLength).optional(),
250
- /** HTTP method or operation */
251
- method: zod_1.z.string().max(exports.INTERACTION_LIMITS.maxMethodLength).optional(),
252
- })
253
- .strict();
254
- /**
255
- * Execution result schema
256
- */
257
- exports.ResultSchema = zod_1.z
258
- .object({
259
- /** Result status */
260
- status: zod_1.z.enum(exports.RESULT_STATUSES),
261
- /** Error code (PEAC error code or namespaced) */
262
- error_code: zod_1.z.string().max(exports.INTERACTION_LIMITS.maxErrorCodeLength).optional(),
263
- /** Whether the operation can be retried */
264
- retryable: zod_1.z.boolean().optional(),
265
- })
266
- .strict();
267
- /**
268
- * Policy context schema (policy state at execution time)
269
- */
270
- exports.PolicyContextSchema = zod_1.z
271
- .object({
272
- /** Policy decision */
273
- decision: zod_1.z.enum(exports.POLICY_DECISIONS),
274
- /** Whether sandbox mode was enabled */
275
- sandbox_enabled: zod_1.z.boolean().optional(),
276
- /** Whether elevated permissions were granted */
277
- elevated: zod_1.z.boolean().optional(),
278
- /** Hash of effective policy document */
279
- effective_policy_digest: exports.DigestSchema.optional(),
280
- })
281
- .strict();
282
- /**
283
- * References schema (links to related evidence)
284
- */
285
- exports.RefsSchema = zod_1.z
286
- .object({
287
- /** Links to evidence.payment.reference */
288
- payment_reference: zod_1.z.string().max(exports.INTERACTION_LIMITS.maxPaymentReferenceLength).optional(),
289
- /** Correlation across receipts */
290
- related_receipt_rid: zod_1.z.string().max(exports.INTERACTION_LIMITS.maxReceiptRidLength).optional(),
291
- })
292
- .strict();
293
- /**
294
- * Kind schema with format validation
295
- */
296
- exports.KindSchema = zod_1.z
297
- .string()
298
- .min(2)
299
- .max(exports.INTERACTION_LIMITS.maxKindLength)
300
- .regex(exports.KIND_FORMAT_PATTERN, 'Invalid kind format');
301
- /**
302
- * Interaction evidence schema (base, without cross-field refinements)
303
- */
304
- const InteractionEvidenceV01BaseSchema = zod_1.z
305
- .object({
306
- /** Stable ID for idempotency/dedupe (REQUIRED) */
307
- interaction_id: zod_1.z.string().min(1).max(exports.INTERACTION_LIMITS.maxInteractionIdLength),
308
- /** Event kind - open string, not closed enum (REQUIRED) */
309
- kind: exports.KindSchema,
310
- /** Executor identity (REQUIRED) */
311
- executor: exports.ExecutorSchema,
312
- /** Tool target (when kind is tool-related) */
313
- tool: exports.ToolTargetSchema.optional(),
314
- /** Resource target (when kind is http/fs-related) */
315
- resource: exports.ResourceTargetSchema.optional(),
316
- /** Input payload reference */
317
- input: exports.PayloadRefSchema.optional(),
318
- /** Output payload reference */
319
- output: exports.PayloadRefSchema.optional(),
320
- /** Start time (RFC 3339) (REQUIRED) */
321
- started_at: zod_1.z.string().datetime(),
322
- /** Completion time (RFC 3339) */
323
- completed_at: zod_1.z.string().datetime().optional(),
324
- /** Duration in milliseconds (OPTIONAL, non-normative) */
325
- duration_ms: zod_1.z.number().int().nonnegative().optional(),
326
- /** Execution outcome */
327
- result: exports.ResultSchema.optional(),
328
- /** Policy context at execution */
329
- policy: exports.PolicyContextSchema.optional(),
330
- /** References to related evidence */
331
- refs: exports.RefsSchema.optional(),
332
- /** Platform-specific extensions (MUST be namespaced) */
333
- extensions: zod_1.z.record(zod_1.z.unknown()).optional(),
334
- })
335
- .strict();
336
- /**
337
- * Interaction evidence schema with invariant refinements
338
- *
339
- * ALL REJECT invariants are enforced here. This ensures that
340
- * both direct schema validation and ordered validation produce
341
- * consistent results.
342
- */
343
- exports.InteractionEvidenceV01Schema = InteractionEvidenceV01BaseSchema.superRefine((ev, ctx) => {
344
- // Invariant 1: completed_at >= started_at
345
- if (ev.completed_at && ev.started_at) {
346
- const startedAt = new Date(ev.started_at).getTime();
347
- const completedAt = new Date(ev.completed_at).getTime();
348
- if (completedAt < startedAt) {
349
- ctx.addIssue({
350
- code: zod_1.z.ZodIssueCode.custom,
351
- message: 'completed_at must be >= started_at',
352
- path: ['completed_at'],
353
- });
354
- }
355
- }
356
- // Invariant 2: output requires result.status
357
- if (ev.output && !ev.result?.status) {
358
- ctx.addIssue({
359
- code: zod_1.z.ZodIssueCode.custom,
360
- message: 'result.status is required when output is present',
361
- path: ['result'],
362
- });
363
- }
364
- // Invariant 3: error status requires error_code or NON-EMPTY extensions
365
- // Empty extensions object {} is not valid detail
366
- if (ev.result?.status === 'error') {
367
- const hasErrorCode = Boolean(ev.result.error_code);
368
- const hasNonEmptyExtensions = ev.extensions !== undefined && Object.keys(ev.extensions).length > 0;
369
- if (!hasErrorCode && !hasNonEmptyExtensions) {
370
- ctx.addIssue({
371
- code: zod_1.z.ZodIssueCode.custom,
372
- message: 'error_code or non-empty extensions required when status is error',
373
- path: ['result', 'error_code'],
374
- });
375
- }
376
- }
377
- // Invariant 4: extension keys must be properly namespaced
378
- if (ev.extensions) {
379
- for (const key of Object.keys(ev.extensions)) {
380
- if (!exports.EXTENSION_KEY_PATTERN.test(key)) {
381
- ctx.addIssue({
382
- code: zod_1.z.ZodIssueCode.custom,
383
- message: `Invalid extension key format: ${key} (must be reverse-DNS/name[@version])`,
384
- path: ['extensions', key],
385
- });
386
- }
387
- }
388
- }
389
- // Invariant 5: reserved kind prefixes (REJECT rule)
390
- // Kinds starting with peac.* or org.peacprotocol.* that are NOT in WELL_KNOWN_KINDS are rejected
391
- if (usesReservedPrefix(ev.kind) &&
392
- !exports.WELL_KNOWN_KINDS.includes(ev.kind)) {
393
- ctx.addIssue({
394
- code: zod_1.z.ZodIssueCode.custom,
395
- message: `kind "${ev.kind}" uses reserved prefix`,
396
- path: ['kind'],
397
- });
398
- }
399
- // Invariant 6: target consistency (REJECT rule)
400
- // tool.* kinds MUST have tool field with name
401
- if (requiresTool(ev.kind) && !ev.tool) {
402
- ctx.addIssue({
403
- code: zod_1.z.ZodIssueCode.custom,
404
- message: `kind "${ev.kind}" requires tool field`,
405
- path: ['tool'],
406
- });
407
- }
408
- // http.* and fs.* kinds MUST have resource field with meaningful content (at least uri)
409
- if (requiresResource(ev.kind)) {
410
- if (!ev.resource) {
411
- ctx.addIssue({
412
- code: zod_1.z.ZodIssueCode.custom,
413
- message: `kind "${ev.kind}" requires resource field`,
414
- path: ['resource'],
415
- });
416
- }
417
- else if (!hasMeaningfulResource(ev.resource)) {
418
- ctx.addIssue({
419
- code: zod_1.z.ZodIssueCode.custom,
420
- message: `kind "${ev.kind}" requires resource.uri to be non-empty`,
421
- path: ['resource', 'uri'],
422
- });
423
- }
424
- }
425
- });
426
- // ============================================================================
427
- // Ordered Validation (Conformance)
428
- // ============================================================================
429
- /**
430
- * Validate interaction evidence with explicit evaluation ordering.
431
- *
432
- * Returns canonical error codes per validation ordering.
433
- * This function does NOT depend on Zod's internal validation ordering.
434
- * Cross-language implementations MUST produce identical error_code values
435
- * for the same invalid input.
436
- *
437
- * Validation order:
438
- * 1. Required field presence
439
- * 2. Required field format (interaction_id, kind, started_at)
440
- * 3. Kind format and reserved prefix check
441
- * 4. Executor field validation
442
- * 5. Optional field format (completed_at, digests, etc.)
443
- * 6. Cross-field invariants (timing, output-result, error-detail)
444
- * 7. Extension key namespacing
445
- * 8. Target consistency (kind prefix -> target field)
446
- *
447
- * @param input - Raw input to validate
448
- * @returns Validation result with canonical error codes on failure
449
- */
450
- function validateInteractionOrdered(input) {
451
- const errors = [];
452
- const warnings = [];
453
- // Step 1: Type check (arrays are typeof 'object' but not valid input)
454
- if (typeof input !== 'object' || input === null || Array.isArray(input)) {
455
- return {
456
- valid: false,
457
- errors: [
458
- {
459
- code: 'E_INTERACTION_INVALID_FORMAT',
460
- message: 'Input must be an object',
461
- },
462
- ],
463
- warnings: [],
464
- };
465
- }
466
- const obj = input;
467
- // Step 2: Required field presence
468
- if (typeof obj.interaction_id !== 'string' || obj.interaction_id.length === 0) {
469
- errors.push({
470
- code: 'E_INTERACTION_MISSING_ID',
471
- message: 'interaction_id is required',
472
- field: 'interaction_id',
473
- });
474
- }
475
- else if (obj.interaction_id.length > exports.INTERACTION_LIMITS.maxInteractionIdLength) {
476
- errors.push({
477
- code: 'E_INTERACTION_INVALID_FORMAT',
478
- message: `interaction_id exceeds max length (${exports.INTERACTION_LIMITS.maxInteractionIdLength})`,
479
- field: 'interaction_id',
480
- });
481
- }
482
- // Step 3: Kind format validation
483
- // Empty string is semantically "missing", not "invalid format"
484
- if (typeof obj.kind !== 'string' || obj.kind.length === 0) {
485
- errors.push({
486
- code: 'E_INTERACTION_MISSING_KIND',
487
- message: 'kind is required',
488
- field: 'kind',
489
- });
490
- }
491
- else if (obj.kind.length < 2 || obj.kind.length > exports.INTERACTION_LIMITS.maxKindLength) {
492
- errors.push({
493
- code: 'E_INTERACTION_INVALID_KIND_FORMAT',
494
- message: `kind must be 2-${exports.INTERACTION_LIMITS.maxKindLength} characters`,
495
- field: 'kind',
496
- });
497
- }
498
- else if (!exports.KIND_FORMAT_PATTERN.test(obj.kind)) {
499
- errors.push({
500
- code: 'E_INTERACTION_INVALID_KIND_FORMAT',
501
- message: 'kind must match pattern: lowercase, start with letter, end with alphanumeric',
502
- field: 'kind',
503
- });
504
- }
505
- else {
506
- // Check reserved prefixes
507
- for (const prefix of exports.RESERVED_KIND_PREFIXES) {
508
- if (obj.kind.startsWith(prefix) && !exports.WELL_KNOWN_KINDS.includes(obj.kind)) {
509
- errors.push({
510
- code: 'E_INTERACTION_KIND_RESERVED',
511
- message: `kind "${obj.kind}" uses reserved prefix "${prefix}"`,
512
- field: 'kind',
513
- });
514
- break;
515
- }
516
- }
517
- // Warn if not in well-known registry (but valid format)
518
- if (errors.length === 0 &&
519
- !exports.WELL_KNOWN_KINDS.includes(obj.kind)) {
520
- warnings.push({
521
- code: 'W_INTERACTION_KIND_UNREGISTERED',
522
- message: `kind "${obj.kind}" is not in the well-known registry`,
523
- field: 'kind',
524
- });
525
- }
526
- }
527
- // Step 4: started_at validation
528
- // Invalid format is treated as "missing" because the value is unusable
529
- // E_INTERACTION_INVALID_TIMING is reserved for relational errors (completed_at < started_at)
530
- if (typeof obj.started_at !== 'string') {
531
- errors.push({
532
- code: 'E_INTERACTION_MISSING_STARTED_AT',
533
- message: 'started_at is required',
534
- field: 'started_at',
535
- });
536
- }
537
- else {
538
- const startedAtDate = new Date(obj.started_at);
539
- if (isNaN(startedAtDate.getTime())) {
540
- errors.push({
541
- code: 'E_INTERACTION_MISSING_STARTED_AT',
542
- message: 'started_at must be a valid ISO 8601 datetime',
543
- field: 'started_at',
544
- });
545
- }
546
- }
547
- // Step 5: Executor validation
548
- if (typeof obj.executor !== 'object' || obj.executor === null) {
549
- errors.push({
550
- code: 'E_INTERACTION_MISSING_EXECUTOR',
551
- message: 'executor is required',
552
- field: 'executor',
553
- });
554
- }
555
- else {
556
- const executor = obj.executor;
557
- if (typeof executor.platform !== 'string' || executor.platform.length === 0) {
558
- errors.push({
559
- code: 'E_INTERACTION_MISSING_EXECUTOR',
560
- message: 'executor.platform is required',
561
- field: 'executor.platform',
562
- });
563
- }
564
- else if (executor.platform.length > exports.INTERACTION_LIMITS.maxPlatformLength) {
565
- errors.push({
566
- code: 'E_INTERACTION_INVALID_FORMAT',
567
- message: `executor.platform exceeds max length (${exports.INTERACTION_LIMITS.maxPlatformLength})`,
568
- field: 'executor.platform',
569
- });
570
- }
571
- }
572
- // Return early if we have fatal errors from required fields
573
- if (errors.length > 0) {
574
- return { valid: false, errors, warnings };
575
- }
576
- // Step 6: Digest validation (optional fields)
577
- const validateDigest = (digest, fieldPath) => {
578
- const digestErrors = [];
579
- if (typeof digest !== 'object' || digest === null) {
580
- digestErrors.push({
581
- code: 'E_INTERACTION_INVALID_DIGEST',
582
- message: 'digest must be an object',
583
- field: fieldPath,
584
- });
585
- return { errors: digestErrors, valid: false };
586
- }
587
- const d = digest;
588
- if (!exports.CANONICAL_DIGEST_ALGS.includes(d.alg)) {
589
- digestErrors.push({
590
- code: 'E_INTERACTION_INVALID_DIGEST_ALG',
591
- message: `digest.alg must be one of: ${exports.CANONICAL_DIGEST_ALGS.join(', ')}`,
592
- field: `${fieldPath}.alg`,
593
- });
594
- }
595
- if (typeof d.value !== 'string' || !exports.DIGEST_VALUE_PATTERN.test(d.value)) {
596
- digestErrors.push({
597
- code: 'E_INTERACTION_INVALID_DIGEST',
598
- message: 'digest.value must be 64 lowercase hex chars',
599
- field: `${fieldPath}.value`,
600
- });
601
- }
602
- if (typeof d.bytes !== 'number' || !Number.isInteger(d.bytes) || d.bytes < 0) {
603
- digestErrors.push({
604
- code: 'E_INTERACTION_INVALID_DIGEST',
605
- message: 'digest.bytes must be a non-negative integer',
606
- field: `${fieldPath}.bytes`,
607
- });
608
- }
609
- return { errors: digestErrors, valid: digestErrors.length === 0 };
610
- };
611
- // Validate input digest if present
612
- if (obj.input !== undefined) {
613
- const inputObj = obj.input;
614
- if (inputObj.digest !== undefined) {
615
- const result = validateDigest(inputObj.digest, 'input.digest');
616
- errors.push(...result.errors);
617
- }
618
- }
619
- // Validate output digest if present
620
- if (obj.output !== undefined) {
621
- const outputObj = obj.output;
622
- if (outputObj.digest !== undefined) {
623
- const result = validateDigest(outputObj.digest, 'output.digest');
624
- errors.push(...result.errors);
625
- }
626
- }
627
- // Step 7: Timing invariant (completed_at >= started_at)
628
- if (typeof obj.completed_at === 'string' && typeof obj.started_at === 'string') {
629
- const startedAt = new Date(obj.started_at).getTime();
630
- const completedAt = new Date(obj.completed_at).getTime();
631
- if (!isNaN(startedAt) && !isNaN(completedAt) && completedAt < startedAt) {
632
- errors.push({
633
- code: 'E_INTERACTION_INVALID_TIMING',
634
- message: 'completed_at must be >= started_at',
635
- field: 'completed_at',
636
- });
637
- }
638
- }
639
- // Step 8: Output requires result invariant
640
- if (obj.output !== undefined) {
641
- const result = obj.result;
642
- if (!result?.status) {
643
- errors.push({
644
- code: 'E_INTERACTION_MISSING_RESULT',
645
- message: 'result.status is required when output is present',
646
- field: 'result',
647
- });
648
- }
649
- }
650
- // Step 9: Error status requires detail (error_code OR non-empty extensions)
651
- // Empty extensions object {} is not valid detail
652
- if (obj.result !== undefined) {
653
- const result = obj.result;
654
- if (result.status === 'error') {
655
- const hasErrorCode = Boolean(result.error_code);
656
- const hasNonEmptyExtensions = obj.extensions !== undefined &&
657
- typeof obj.extensions === 'object' &&
658
- obj.extensions !== null &&
659
- Object.keys(obj.extensions).length > 0;
660
- if (!hasErrorCode && !hasNonEmptyExtensions) {
661
- errors.push({
662
- code: 'E_INTERACTION_MISSING_ERROR_DETAIL',
663
- message: 'error_code or non-empty extensions required when result.status is error',
664
- field: 'result.error_code',
665
- });
666
- }
667
- }
668
- }
669
- // Step 10: Extension key namespacing
670
- if (obj.extensions !== undefined) {
671
- if (typeof obj.extensions !== 'object' || obj.extensions === null) {
672
- errors.push({
673
- code: 'E_INTERACTION_INVALID_FORMAT',
674
- message: 'extensions must be an object',
675
- field: 'extensions',
676
- });
677
- }
678
- else {
679
- for (const key of Object.keys(obj.extensions)) {
680
- if (!exports.EXTENSION_KEY_PATTERN.test(key)) {
681
- errors.push({
682
- code: 'E_INTERACTION_INVALID_EXTENSION_KEY',
683
- message: `Invalid extension key format: "${key}" (must be reverse-DNS/name[@version])`,
684
- field: `extensions.${key}`,
685
- });
686
- }
687
- }
688
- }
689
- }
690
- // Step 11: Target consistency (prefix-aware, using shared helpers)
691
- const kind = obj.kind;
692
- const hasTool = obj.tool !== undefined;
693
- const hasResource = obj.resource !== undefined;
694
- // tool.* kinds MUST have tool field
695
- if (requiresTool(kind) && !hasTool) {
696
- errors.push({
697
- code: 'E_INTERACTION_MISSING_TARGET',
698
- message: `kind "${kind}" requires tool field`,
699
- field: 'tool',
700
- });
701
- }
702
- // http.* and fs.* kinds MUST have resource field with meaningful content
703
- if (requiresResource(kind)) {
704
- if (!hasResource) {
705
- errors.push({
706
- code: 'E_INTERACTION_MISSING_TARGET',
707
- message: `kind "${kind}" requires resource field`,
708
- field: 'resource',
709
- });
710
- }
711
- else if (!hasMeaningfulResource(obj.resource)) {
712
- errors.push({
713
- code: 'E_INTERACTION_MISSING_TARGET',
714
- message: `kind "${kind}" requires resource.uri to be non-empty`,
715
- field: 'resource.uri',
716
- });
717
- }
718
- }
719
- // Warn if neither target present (non-strict kinds like 'message')
720
- if (!hasTool && !hasResource && errors.length === 0) {
721
- warnings.push({
722
- code: 'W_INTERACTION_MISSING_TARGET',
723
- message: 'Neither tool nor resource field is present',
724
- field: 'tool',
725
- });
726
- }
727
- // Return early if we have errors
728
- if (errors.length > 0) {
729
- return { valid: false, errors, warnings };
730
- }
731
- // Final Zod validation for strict schema compliance
732
- const zodResult = exports.InteractionEvidenceV01Schema.safeParse(input);
733
- if (!zodResult.success) {
734
- return {
735
- valid: false,
736
- errors: [
737
- {
738
- code: 'E_INTERACTION_INVALID_FORMAT',
739
- message: zodResult.error.issues[0]?.message || 'Schema validation failed',
740
- field: zodResult.error.issues[0]?.path.join('.'),
741
- },
742
- ],
743
- warnings,
744
- };
745
- }
746
- return { valid: true, value: zodResult.data, warnings };
747
- }
748
- // ============================================================================
749
- // Helper Functions
750
- // ============================================================================
751
- /**
752
- * Validate interaction evidence (simple API for conformance harness)
753
- *
754
- * This is the recommended entry point for conformance testing.
755
- * Returns a simple result matching the existing harness contract.
756
- *
757
- * ## Warnings Contract
758
- *
759
- * This compat API intentionally **omits warnings** from the result.
760
- * Warnings are available via validateInteractionOrdered() for consumers
761
- * who explicitly opt in. Stable consumers should:
762
- * - Use this function for pass/fail validation
763
- * - Use validateInteractionOrdered() only when warnings are needed
764
- *
765
- * This prevents breaking changes if warning codes are added/modified.
766
- *
767
- * @param input - Raw input to validate
768
- * @returns Simple validation result with error_code on failure (no warnings)
769
- */
770
- function validateInteraction(input) {
771
- const result = validateInteractionOrdered(input);
772
- if (result.valid) {
773
- return { valid: true };
774
- }
775
- const firstError = result.errors[0];
776
- return {
777
- valid: false,
778
- error_code: firstError?.code,
779
- error_field: firstError?.field,
780
- };
781
- }
782
- /**
783
- * Validate interaction evidence (throwing)
784
- *
785
- * @param evidence - Object to validate
786
- * @returns Validated InteractionEvidenceV01
787
- * @throws ZodError if validation fails
788
- */
789
- function validateInteractionEvidence(evidence) {
790
- return exports.InteractionEvidenceV01Schema.parse(evidence);
791
- }
792
- /**
793
- * Check if an object is valid interaction evidence (non-throwing)
794
- *
795
- * @param evidence - Object to check
796
- * @returns True if valid InteractionEvidenceV01
797
- */
798
- function isValidInteractionEvidence(evidence) {
799
- return exports.InteractionEvidenceV01Schema.safeParse(evidence).success;
800
- }
801
- /**
802
- * Check if a kind is in the well-known registry
803
- *
804
- * @param kind - Kind string to check
805
- * @returns True if well-known
806
- */
807
- function isWellKnownKind(kind) {
808
- return exports.WELL_KNOWN_KINDS.includes(kind);
809
- }
810
- /**
811
- * Check if a kind uses a reserved prefix
812
- *
813
- * @param kind - Kind string to check
814
- * @returns True if uses reserved prefix
815
- */
816
- function isReservedKindPrefix(kind) {
817
- return exports.RESERVED_KIND_PREFIXES.some((prefix) => kind.startsWith(prefix));
818
- }
819
- /**
820
- * Check if a digest uses truncation
821
- *
822
- * @param digest - Digest to check
823
- * @returns True if truncated
824
- */
825
- function isDigestTruncated(digest) {
826
- return digest.alg.startsWith('sha-256:trunc-');
827
- }
828
- // ============================================================================
829
- // SDK Accessors
830
- // ============================================================================
831
- /**
832
- * Get interaction evidence from a PEAC envelope
833
- *
834
- * @param receipt - PEAC envelope to read from
835
- * @returns Interaction evidence if present, undefined otherwise
836
- */
837
- function getInteraction(receipt) {
838
- return receipt.evidence?.extensions?.[exports.INTERACTION_EXTENSION_KEY];
839
- }
840
- /**
841
- * Set interaction evidence on a PEAC envelope (mutates)
842
- *
843
- * @param receipt - PEAC envelope to modify
844
- * @param interaction - Interaction evidence to set
845
- */
846
- function setInteraction(receipt, interaction) {
847
- if (!receipt.evidence) {
848
- receipt.evidence = {};
849
- }
850
- if (!receipt.evidence.extensions) {
851
- receipt.evidence.extensions = {};
852
- }
853
- receipt.evidence.extensions[exports.INTERACTION_EXTENSION_KEY] = interaction;
854
- }
855
- /**
856
- * Check if a PEAC envelope has interaction evidence
857
- *
858
- * @param receipt - PEAC envelope to check
859
- * @returns True if interaction evidence is present
860
- */
861
- function hasInteraction(receipt) {
862
- return receipt.evidence?.extensions?.[exports.INTERACTION_EXTENSION_KEY] !== undefined;
863
- }
864
- /**
865
- * Create a view over a PEAC envelope that provides first-class access
866
- * to extension data without modifying the wire format.
867
- *
868
- * @param envelope - PEAC envelope to create view for
869
- * @returns ReceiptView with convenient accessors
870
- *
871
- * @example
872
- * const view = createReceiptView(envelope);
873
- * if (view.interaction) {
874
- * console.log(view.interaction.kind);
875
- * }
876
- */
877
- function createReceiptView(envelope) {
878
- const interaction = getInteraction(envelope);
879
- const workflow = envelope.auth?.extensions?.['org.peacprotocol/workflow'];
880
- return {
881
- envelope,
882
- interaction,
883
- interactions: interaction ? [interaction] : [],
884
- workflow,
885
- };
886
- }
887
- /**
888
- * Create validated interaction evidence
889
- *
890
- * @param params - Interaction parameters
891
- * @returns Validated InteractionEvidenceV01
892
- * @throws ZodError if validation fails
893
- */
894
- function createInteractionEvidence(params) {
895
- const evidence = {
896
- interaction_id: params.interaction_id,
897
- kind: params.kind,
898
- executor: {
899
- platform: params.executor.platform,
900
- ...(params.executor.version && { version: params.executor.version }),
901
- ...(params.executor.plugin_id && { plugin_id: params.executor.plugin_id }),
902
- ...(params.executor.plugin_digest && { plugin_digest: params.executor.plugin_digest }),
903
- },
904
- ...(params.tool && { tool: params.tool }),
905
- ...(params.resource && { resource: params.resource }),
906
- ...(params.input && { input: params.input }),
907
- ...(params.output && { output: params.output }),
908
- started_at: params.started_at,
909
- ...(params.completed_at && { completed_at: params.completed_at }),
910
- ...(params.duration_ms !== undefined && { duration_ms: params.duration_ms }),
911
- ...(params.result && { result: params.result }),
912
- ...(params.policy && { policy: params.policy }),
913
- ...(params.refs && { refs: params.refs }),
914
- ...(params.extensions && { extensions: params.extensions }),
915
- };
916
- return validateInteractionEvidence(evidence);
917
- }
918
- //# sourceMappingURL=interaction.js.map