@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
package/dist/dispute.js DELETED
@@ -1,832 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DisputeAttestationSchema = exports.DISPUTE_TYPE = exports.DisputeEvidenceSchema = exports.DocumentRefSchema = exports.DisputeContactSchema = exports.ContactMethodSchema = exports.DisputeResolutionSchema = exports.RemediationSchema = exports.REMEDIATION_TYPES = exports.RemediationTypeSchema = exports.DISPUTE_OUTCOMES = exports.DisputeOutcomeSchema = exports.DISPUTE_TRANSITIONS = exports.TERMINAL_STATES = exports.DISPUTE_STATES = exports.DisputeStateSchema = exports.DisputeGroundsSchema = exports.DISPUTE_GROUNDS_CODES = exports.DisputeGroundsCodeSchema = exports.DISPUTE_TARGET_TYPES = exports.DisputeTargetTypeSchema = exports.DISPUTE_TYPES = exports.DisputeTypeSchema = exports.DisputeIdSchema = exports.DISPUTE_LIMITS = void 0;
4
- exports.canTransitionTo = canTransitionTo;
5
- exports.isTerminalState = isTerminalState;
6
- exports.getValidTransitions = getValidTransitions;
7
- exports.validateDisputeAttestation = validateDisputeAttestation;
8
- exports.isValidDisputeAttestation = isValidDisputeAttestation;
9
- exports.isDisputeAttestation = isDisputeAttestation;
10
- exports.validateDisputeResolution = validateDisputeResolution;
11
- exports.validateDisputeContact = validateDisputeContact;
12
- exports.createDisputeAttestation = createDisputeAttestation;
13
- exports.transitionDisputeState = transitionDisputeState;
14
- exports.isDisputeExpired = isDisputeExpired;
15
- exports.isDisputeNotYetValid = isDisputeNotYetValid;
16
- /**
17
- * Dispute Attestation Types and Validators (v0.9.27+)
18
- *
19
- * Provides formal mechanism for contesting PEAC receipts, attributions,
20
- * and identity claims with lifecycle state management.
21
- *
22
- * @see docs/specs/DISPUTE.md for normative specification
23
- */
24
- const zod_1 = require("zod");
25
- const attribution_1 = require("./attribution");
26
- // =============================================================================
27
- // DISPUTE LIMITS (v0.9.27+)
28
- // =============================================================================
29
- /**
30
- * Dispute limits for DoS protection and validation.
31
- *
32
- * These are implementation safety limits, not protocol constraints.
33
- */
34
- exports.DISPUTE_LIMITS = {
35
- /** Maximum grounds per dispute */
36
- maxGrounds: 10,
37
- /** Maximum supporting receipts */
38
- maxSupportingReceipts: 50,
39
- /** Maximum supporting attributions */
40
- maxSupportingAttributions: 50,
41
- /** Maximum supporting documents */
42
- maxSupportingDocuments: 20,
43
- /** Maximum description length in chars */
44
- maxDescriptionLength: 4000,
45
- /** Maximum details length per ground in chars */
46
- maxGroundDetailsLength: 1000,
47
- /** Maximum rationale length in chars */
48
- maxRationaleLength: 4000,
49
- /** Maximum remediation details length in chars */
50
- maxRemediationDetailsLength: 4000,
51
- /** Minimum description for 'other' dispute type */
52
- minOtherDescriptionLength: 50,
53
- };
54
- // =============================================================================
55
- // ULID VALIDATION (v0.9.27+)
56
- // =============================================================================
57
- /**
58
- * ULID format regex: 26 characters, Crockford Base32, UPPERCASE ONLY.
59
- *
60
- * ULIDs are time-ordered, globally unique identifiers that are URL-safe.
61
- * Format: 10 characters timestamp + 16 characters randomness
62
- *
63
- * CASE SENSITIVITY DECISION (v0.9.27):
64
- * While the ULID spec allows case-insensitive decoding (lowercase is valid),
65
- * PEAC enforces UPPERCASE as the canonical form for dispute IDs. This ensures:
66
- * 1. Consistent string comparison without normalization
67
- * 2. Predictable indexing and lookup in storage systems
68
- * 3. Deterministic hash computation for audit trails
69
- *
70
- * Implementations generating ULIDs MUST use uppercase encoding.
71
- * Implementations receiving ULIDs MAY normalize to uppercase before validation
72
- * if interoperating with systems that produce lowercase, but SHOULD warn.
73
- *
74
- * @see https://github.com/ulid/spec
75
- */
76
- const ULID_REGEX = /^[0-9A-HJKMNP-TV-Z]{26}$/;
77
- /**
78
- * Dispute ID schema using ULID format.
79
- *
80
- * @example "01ARZ3NDEKTSV4RRFFQ69G5FAV"
81
- */
82
- exports.DisputeIdSchema = zod_1.z.string().regex(ULID_REGEX, 'Invalid ULID format');
83
- // =============================================================================
84
- // DISPUTE TYPES (v0.9.27+)
85
- // =============================================================================
86
- /**
87
- * Type of dispute being filed.
88
- *
89
- * - 'unauthorized_access': Content accessed without valid receipt
90
- * - 'attribution_missing': Used content without attribution
91
- * - 'attribution_incorrect': Attribution exists but is wrong
92
- * - 'receipt_invalid': Receipt was fraudulently issued
93
- * - 'identity_spoofed': Agent identity was impersonated
94
- * - 'purpose_mismatch': Declared purpose doesn't match actual use
95
- * - 'policy_violation': Terms/policy violated despite receipt
96
- * - 'other': Catch-all (requires description >= 50 chars)
97
- */
98
- exports.DisputeTypeSchema = zod_1.z.enum([
99
- 'unauthorized_access',
100
- 'attribution_missing',
101
- 'attribution_incorrect',
102
- 'receipt_invalid',
103
- 'identity_spoofed',
104
- 'purpose_mismatch',
105
- 'policy_violation',
106
- 'other',
107
- ]);
108
- /**
109
- * Array of valid dispute types for runtime checks.
110
- */
111
- exports.DISPUTE_TYPES = [
112
- 'unauthorized_access',
113
- 'attribution_missing',
114
- 'attribution_incorrect',
115
- 'receipt_invalid',
116
- 'identity_spoofed',
117
- 'purpose_mismatch',
118
- 'policy_violation',
119
- 'other',
120
- ];
121
- // =============================================================================
122
- // DISPUTE TARGET TYPES (v0.9.27+)
123
- // =============================================================================
124
- /**
125
- * Type of entity being disputed.
126
- *
127
- * - 'receipt': A PEAC receipt
128
- * - 'attribution': An attribution attestation
129
- * - 'identity': An agent identity attestation
130
- * - 'policy': A policy decision or enforcement
131
- */
132
- exports.DisputeTargetTypeSchema = zod_1.z.enum(['receipt', 'attribution', 'identity', 'policy']);
133
- /**
134
- * Array of valid target types for runtime checks.
135
- */
136
- exports.DISPUTE_TARGET_TYPES = ['receipt', 'attribution', 'identity', 'policy'];
137
- // =============================================================================
138
- // DISPUTE GROUNDS (v0.9.27+)
139
- // =============================================================================
140
- /**
141
- * Specific grounds for the dispute.
142
- *
143
- * Evidence-based:
144
- * - 'missing_receipt': No receipt exists for access
145
- * - 'expired_receipt': Receipt was expired at time of use
146
- * - 'forged_receipt': Receipt signature invalid or tampered
147
- * - 'receipt_not_applicable': Receipt doesn't cover the resource
148
- *
149
- * Attribution-based:
150
- * - 'content_not_used': Content was not actually used
151
- * - 'source_misidentified': Wrong source attributed
152
- * - 'usage_type_wrong': RAG claimed but was training, etc.
153
- * - 'weight_inaccurate': Attribution weight is incorrect
154
- *
155
- * Identity-based:
156
- * - 'agent_impersonation': Agent ID was spoofed
157
- * - 'key_compromise': Signing key was compromised
158
- * - 'delegation_invalid': Delegation chain is broken
159
- *
160
- * Policy-based:
161
- * - 'purpose_exceeded': Used beyond declared purpose
162
- * - 'terms_violated': Specific terms were violated
163
- * - 'rate_limit_exceeded': Exceeded rate limits
164
- */
165
- exports.DisputeGroundsCodeSchema = zod_1.z.enum([
166
- // Evidence-based
167
- 'missing_receipt',
168
- 'expired_receipt',
169
- 'forged_receipt',
170
- 'receipt_not_applicable',
171
- // Attribution-based
172
- 'content_not_used',
173
- 'source_misidentified',
174
- 'usage_type_wrong',
175
- 'weight_inaccurate',
176
- // Identity-based
177
- 'agent_impersonation',
178
- 'key_compromise',
179
- 'delegation_invalid',
180
- // Policy-based
181
- 'purpose_exceeded',
182
- 'terms_violated',
183
- 'rate_limit_exceeded',
184
- ]);
185
- /**
186
- * Array of valid grounds codes for runtime checks.
187
- */
188
- exports.DISPUTE_GROUNDS_CODES = [
189
- 'missing_receipt',
190
- 'expired_receipt',
191
- 'forged_receipt',
192
- 'receipt_not_applicable',
193
- 'content_not_used',
194
- 'source_misidentified',
195
- 'usage_type_wrong',
196
- 'weight_inaccurate',
197
- 'agent_impersonation',
198
- 'key_compromise',
199
- 'delegation_invalid',
200
- 'purpose_exceeded',
201
- 'terms_violated',
202
- 'rate_limit_exceeded',
203
- ];
204
- /**
205
- * Individual dispute ground with supporting evidence reference.
206
- */
207
- exports.DisputeGroundsSchema = zod_1.z
208
- .object({
209
- /** Specific code for this ground (REQUIRED) */
210
- code: exports.DisputeGroundsCodeSchema,
211
- /** Reference to supporting evidence (OPTIONAL) */
212
- evidence_ref: zod_1.z.string().max(2048).optional(),
213
- /** Additional context for this ground (OPTIONAL) */
214
- details: zod_1.z.string().max(exports.DISPUTE_LIMITS.maxGroundDetailsLength).optional(),
215
- })
216
- .strict();
217
- // =============================================================================
218
- // DISPUTE LIFECYCLE STATES (v0.9.27+)
219
- // =============================================================================
220
- /**
221
- * Dispute lifecycle states.
222
- *
223
- * State flow:
224
- * ```
225
- * FILED -> ACKNOWLEDGED -> UNDER_REVIEW -> RESOLVED
226
- * | | |
227
- * +-> REJECTED +-> ESCALATED +-> APPEALED
228
- * |
229
- * +-> FINAL
230
- * ```
231
- *
232
- * Terminal states (REQUIRE resolution): resolved, rejected, final
233
- * Non-terminal states: filed, acknowledged, under_review, escalated, appealed
234
- */
235
- exports.DisputeStateSchema = zod_1.z.enum([
236
- 'filed',
237
- 'acknowledged',
238
- 'under_review',
239
- 'escalated',
240
- 'resolved',
241
- 'rejected',
242
- 'appealed',
243
- 'final',
244
- ]);
245
- /**
246
- * Array of valid dispute states for runtime checks.
247
- */
248
- exports.DISPUTE_STATES = [
249
- 'filed',
250
- 'acknowledged',
251
- 'under_review',
252
- 'escalated',
253
- 'resolved',
254
- 'rejected',
255
- 'appealed',
256
- 'final',
257
- ];
258
- /**
259
- * Terminal states that REQUIRE a resolution field.
260
- */
261
- exports.TERMINAL_STATES = ['resolved', 'rejected', 'final'];
262
- /**
263
- * Canonical state transition table for dispute lifecycle.
264
- *
265
- * This is the SINGLE SOURCE OF TRUTH for valid transitions.
266
- * Do not duplicate elsewhere - reference this constant.
267
- */
268
- exports.DISPUTE_TRANSITIONS = {
269
- filed: ['acknowledged', 'rejected'],
270
- acknowledged: ['under_review', 'rejected'],
271
- under_review: ['resolved', 'escalated'],
272
- escalated: ['resolved'],
273
- resolved: ['appealed', 'final'],
274
- rejected: ['appealed', 'final'],
275
- appealed: ['under_review', 'final'],
276
- final: [], // Terminal - no transitions out
277
- };
278
- /**
279
- * Check if a state transition is valid.
280
- *
281
- * @param current - Current dispute state
282
- * @param next - Proposed next state
283
- * @returns True if the transition is valid
284
- */
285
- function canTransitionTo(current, next) {
286
- return exports.DISPUTE_TRANSITIONS[current].includes(next);
287
- }
288
- /**
289
- * Check if a state is terminal (requires resolution).
290
- *
291
- * @param state - Dispute state to check
292
- * @returns True if the state is terminal
293
- */
294
- function isTerminalState(state) {
295
- return exports.TERMINAL_STATES.includes(state);
296
- }
297
- /**
298
- * Get valid next states from current state.
299
- *
300
- * @param current - Current dispute state
301
- * @returns Array of valid next states
302
- */
303
- function getValidTransitions(current) {
304
- return exports.DISPUTE_TRANSITIONS[current];
305
- }
306
- // =============================================================================
307
- // DISPUTE OUTCOME AND RESOLUTION (v0.9.27+)
308
- // =============================================================================
309
- /**
310
- * Outcome of a resolved dispute.
311
- *
312
- * - 'upheld': Dispute was valid, in favor of filer
313
- * - 'dismissed': Dispute invalid or without merit
314
- * - 'partially_upheld': Some grounds upheld, others dismissed
315
- * - 'settled': Parties reached agreement
316
- */
317
- exports.DisputeOutcomeSchema = zod_1.z.enum(['upheld', 'dismissed', 'partially_upheld', 'settled']);
318
- /**
319
- * Array of valid outcomes for runtime checks.
320
- */
321
- exports.DISPUTE_OUTCOMES = ['upheld', 'dismissed', 'partially_upheld', 'settled'];
322
- /**
323
- * Type of remediation action taken.
324
- *
325
- * - 'attribution_corrected': Attribution was fixed
326
- * - 'receipt_revoked': Receipt was revoked
327
- * - 'access_restored': Access was restored
328
- * - 'compensation': Financial compensation provided
329
- * - 'policy_updated': Policy was updated
330
- * - 'no_action': No action required
331
- * - 'other': Other remediation
332
- */
333
- exports.RemediationTypeSchema = zod_1.z.enum([
334
- 'attribution_corrected',
335
- 'receipt_revoked',
336
- 'access_restored',
337
- 'compensation',
338
- 'policy_updated',
339
- 'no_action',
340
- 'other',
341
- ]);
342
- /**
343
- * Array of valid remediation types for runtime checks.
344
- */
345
- exports.REMEDIATION_TYPES = [
346
- 'attribution_corrected',
347
- 'receipt_revoked',
348
- 'access_restored',
349
- 'compensation',
350
- 'policy_updated',
351
- 'no_action',
352
- 'other',
353
- ];
354
- /**
355
- * Remediation action taken to address the dispute.
356
- */
357
- exports.RemediationSchema = zod_1.z
358
- .object({
359
- /** Type of remediation (REQUIRED) */
360
- type: exports.RemediationTypeSchema,
361
- /** Details of the remediation action (REQUIRED) */
362
- details: zod_1.z.string().min(1).max(exports.DISPUTE_LIMITS.maxRemediationDetailsLength),
363
- /** Deadline for completing remediation (OPTIONAL) */
364
- deadline: zod_1.z.string().datetime().optional(),
365
- })
366
- .strict();
367
- /**
368
- * Resolution of a dispute.
369
- *
370
- * Required for terminal states (resolved, rejected, final).
371
- */
372
- exports.DisputeResolutionSchema = zod_1.z
373
- .object({
374
- /** Outcome of the dispute (REQUIRED) */
375
- outcome: exports.DisputeOutcomeSchema,
376
- /** When the decision was made (REQUIRED) */
377
- decided_at: zod_1.z.string().datetime(),
378
- /** Who made the decision (REQUIRED) */
379
- decided_by: zod_1.z.string().min(1).max(2048),
380
- /** Explanation of the decision (REQUIRED) */
381
- rationale: zod_1.z.string().min(1).max(exports.DISPUTE_LIMITS.maxRationaleLength),
382
- /** Remediation action if applicable (OPTIONAL) */
383
- remediation: exports.RemediationSchema.optional(),
384
- })
385
- .strict();
386
- // =============================================================================
387
- // DISPUTE CONTACT (v0.9.27+)
388
- // =============================================================================
389
- /**
390
- * Contact method for dispute resolution.
391
- *
392
- * - 'email': Email address
393
- * - 'url': URL (webhook, contact form)
394
- * - 'did': Decentralized identifier
395
- */
396
- exports.ContactMethodSchema = zod_1.z.enum(['email', 'url', 'did']);
397
- /**
398
- * Contact information for dispute communication.
399
- *
400
- * Validated based on method type.
401
- */
402
- exports.DisputeContactSchema = zod_1.z
403
- .object({
404
- /** Contact method (REQUIRED) */
405
- method: exports.ContactMethodSchema,
406
- /** Contact value (REQUIRED) */
407
- value: zod_1.z.string().min(1).max(2048),
408
- })
409
- .strict()
410
- .superRefine((contact, ctx) => {
411
- if (contact.method === 'email') {
412
- // Basic email validation (RFC 5322 simplified)
413
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
414
- if (!emailRegex.test(contact.value)) {
415
- ctx.addIssue({
416
- code: zod_1.z.ZodIssueCode.custom,
417
- message: 'Invalid email format',
418
- path: ['value'],
419
- });
420
- }
421
- }
422
- else if (contact.method === 'did') {
423
- // DID must start with did:
424
- if (!contact.value.startsWith('did:')) {
425
- ctx.addIssue({
426
- code: zod_1.z.ZodIssueCode.custom,
427
- message: 'DID must start with "did:"',
428
- path: ['value'],
429
- });
430
- }
431
- }
432
- else if (contact.method === 'url') {
433
- // URL validation
434
- try {
435
- new URL(contact.value);
436
- }
437
- catch {
438
- ctx.addIssue({
439
- code: zod_1.z.ZodIssueCode.custom,
440
- message: 'Invalid URL format',
441
- path: ['value'],
442
- });
443
- }
444
- }
445
- });
446
- // =============================================================================
447
- // DOCUMENT REFERENCE (v0.9.27+)
448
- // =============================================================================
449
- /**
450
- * Reference to an external document supporting the dispute.
451
- */
452
- exports.DocumentRefSchema = zod_1.z
453
- .object({
454
- /** URI of the document (REQUIRED) */
455
- uri: zod_1.z.string().url().max(2048),
456
- /** Content hash for integrity verification (OPTIONAL) */
457
- content_hash: attribution_1.ContentHashSchema.optional(),
458
- /** Brief description of the document (OPTIONAL) */
459
- description: zod_1.z.string().max(500).optional(),
460
- })
461
- .strict();
462
- // =============================================================================
463
- // DISPUTE EVIDENCE (v0.9.27+)
464
- // =============================================================================
465
- /**
466
- * Base evidence schema without invariants.
467
- */
468
- const DisputeEvidenceBaseSchema = zod_1.z
469
- .object({
470
- /** Type of dispute (REQUIRED) */
471
- dispute_type: exports.DisputeTypeSchema,
472
- /** Reference to disputed target: jti:{id}, URL, or URN (REQUIRED) */
473
- target_ref: zod_1.z.string().min(1).max(2048),
474
- /** Type of target being disputed (REQUIRED) */
475
- target_type: exports.DisputeTargetTypeSchema,
476
- /** Grounds for the dispute (REQUIRED, at least 1) */
477
- grounds: zod_1.z.array(exports.DisputeGroundsSchema).min(1).max(exports.DISPUTE_LIMITS.maxGrounds),
478
- /** Human-readable description (REQUIRED) */
479
- description: zod_1.z.string().min(1).max(exports.DISPUTE_LIMITS.maxDescriptionLength),
480
- /** Receipt references supporting the claim (OPTIONAL) */
481
- supporting_receipts: zod_1.z
482
- .array(zod_1.z.string().max(2048))
483
- .max(exports.DISPUTE_LIMITS.maxSupportingReceipts)
484
- .optional(),
485
- /** Attribution references supporting the claim (OPTIONAL) */
486
- supporting_attributions: zod_1.z
487
- .array(zod_1.z.string().max(2048))
488
- .max(exports.DISPUTE_LIMITS.maxSupportingAttributions)
489
- .optional(),
490
- /** External document references (OPTIONAL) */
491
- supporting_documents: zod_1.z
492
- .array(exports.DocumentRefSchema)
493
- .max(exports.DISPUTE_LIMITS.maxSupportingDocuments)
494
- .optional(),
495
- /** Contact for dispute resolution (OPTIONAL) */
496
- contact: exports.DisputeContactSchema.optional(),
497
- /** Current lifecycle state (REQUIRED) */
498
- state: exports.DisputeStateSchema,
499
- /** When state was last changed (OPTIONAL) */
500
- state_changed_at: zod_1.z.string().datetime().optional(),
501
- /** Reason for state change (OPTIONAL) */
502
- state_reason: zod_1.z.string().max(1000).optional(),
503
- /** Resolution details (REQUIRED for terminal states) */
504
- resolution: exports.DisputeResolutionSchema.optional(),
505
- /** Advisory: filing window used by issuer in days (OPTIONAL, informative only) */
506
- window_hint_days: zod_1.z.number().int().positive().max(365).optional(),
507
- })
508
- .strict();
509
- /**
510
- * Dispute evidence with cross-field invariants enforced via superRefine.
511
- *
512
- * Invariants:
513
- * 1. Terminal states (resolved, rejected, final) REQUIRE resolution
514
- * 2. Resolution is ONLY valid for terminal states
515
- * 3. Dispute type 'other' requires description >= 50 characters
516
- */
517
- exports.DisputeEvidenceSchema = DisputeEvidenceBaseSchema.superRefine((evidence, ctx) => {
518
- const terminalStates = ['resolved', 'rejected', 'final'];
519
- // Invariant 1: Terminal states REQUIRE resolution
520
- if (terminalStates.includes(evidence.state) && !evidence.resolution) {
521
- ctx.addIssue({
522
- code: zod_1.z.ZodIssueCode.custom,
523
- message: `Resolution is required when state is "${evidence.state}"`,
524
- path: ['resolution'],
525
- });
526
- }
527
- // Invariant 2: Resolution REQUIRES terminal state
528
- if (evidence.resolution && !terminalStates.includes(evidence.state)) {
529
- ctx.addIssue({
530
- code: zod_1.z.ZodIssueCode.custom,
531
- message: `Resolution is only valid for terminal states (resolved, rejected, final), not "${evidence.state}"`,
532
- path: ['state'],
533
- });
534
- }
535
- // Invariant 3: 'other' dispute type requires meaningful description
536
- if (evidence.dispute_type === 'other' &&
537
- evidence.description.length < exports.DISPUTE_LIMITS.minOtherDescriptionLength) {
538
- ctx.addIssue({
539
- code: zod_1.z.ZodIssueCode.custom,
540
- message: `Dispute type "other" requires description of at least ${exports.DISPUTE_LIMITS.minOtherDescriptionLength} characters`,
541
- path: ['description'],
542
- });
543
- }
544
- });
545
- // =============================================================================
546
- // DISPUTE ATTESTATION (v0.9.27+)
547
- // =============================================================================
548
- /**
549
- * Attestation type literal for disputes.
550
- */
551
- exports.DISPUTE_TYPE = 'peac/dispute';
552
- /**
553
- * DisputeAttestation - formal mechanism for contesting PEAC claims.
554
- *
555
- * This attestation provides a standardized way to dispute receipts,
556
- * attributions, identity claims, or policy decisions.
557
- *
558
- * @example
559
- * ```typescript
560
- * const dispute: DisputeAttestation = {
561
- * type: 'peac/dispute',
562
- * issuer: 'https://publisher.example.com',
563
- * issued_at: '2026-01-06T12:00:00Z',
564
- * ref: '01ARZ3NDEKTSV4RRFFQ69G5FAV',
565
- * evidence: {
566
- * dispute_type: 'unauthorized_access',
567
- * target_ref: 'jti:01H5KPT9QZA123456789VWXYZG',
568
- * target_type: 'receipt',
569
- * grounds: [{ code: 'missing_receipt' }],
570
- * description: 'Content was accessed without a valid receipt.',
571
- * state: 'filed',
572
- * },
573
- * };
574
- * ```
575
- */
576
- exports.DisputeAttestationSchema = zod_1.z
577
- .object({
578
- /** Attestation type (MUST be 'peac/dispute') */
579
- type: zod_1.z.literal(exports.DISPUTE_TYPE),
580
- /** Party filing the dispute (REQUIRED) */
581
- issuer: zod_1.z.string().min(1).max(2048),
582
- /** When the dispute was filed (REQUIRED) */
583
- issued_at: zod_1.z.string().datetime(),
584
- /** When the dispute expires (OPTIONAL) */
585
- expires_at: zod_1.z.string().datetime().optional(),
586
- /** Unique dispute reference in ULID format (REQUIRED) */
587
- ref: exports.DisputeIdSchema,
588
- /** Dispute evidence and state */
589
- evidence: exports.DisputeEvidenceSchema,
590
- })
591
- .strict();
592
- // =============================================================================
593
- // VALIDATION HELPERS (v0.9.27+)
594
- // =============================================================================
595
- /**
596
- * Validate a DisputeAttestation.
597
- *
598
- * @param data - Unknown data to validate
599
- * @returns Result with validated attestation or error message
600
- *
601
- * @example
602
- * ```typescript
603
- * const result = validateDisputeAttestation(data);
604
- * if (result.ok) {
605
- * console.log('Dispute ref:', result.value.ref);
606
- * } else {
607
- * console.error('Validation error:', result.error);
608
- * }
609
- * ```
610
- */
611
- function validateDisputeAttestation(data) {
612
- const result = exports.DisputeAttestationSchema.safeParse(data);
613
- if (result.success) {
614
- return { ok: true, value: result.data };
615
- }
616
- return { ok: false, error: result.error.message };
617
- }
618
- /**
619
- * Check if an object is a valid DisputeAttestation.
620
- *
621
- * @param data - Unknown data to check
622
- * @returns True if valid
623
- */
624
- function isValidDisputeAttestation(data) {
625
- return exports.DisputeAttestationSchema.safeParse(data).success;
626
- }
627
- /**
628
- * Check if an object has the dispute attestation type.
629
- *
630
- * @param attestation - Object with a type field
631
- * @returns True if type is 'peac/dispute'
632
- */
633
- function isDisputeAttestation(attestation) {
634
- return attestation.type === exports.DISPUTE_TYPE;
635
- }
636
- /**
637
- * Validate a DisputeResolution.
638
- *
639
- * @param data - Unknown data to validate
640
- * @returns Result with validated resolution or error message
641
- */
642
- function validateDisputeResolution(data) {
643
- const result = exports.DisputeResolutionSchema.safeParse(data);
644
- if (result.success) {
645
- return { ok: true, value: result.data };
646
- }
647
- return { ok: false, error: result.error.message };
648
- }
649
- /**
650
- * Validate a DisputeContact.
651
- *
652
- * @param data - Unknown data to validate
653
- * @returns Result with validated contact or error message
654
- */
655
- function validateDisputeContact(data) {
656
- const result = exports.DisputeContactSchema.safeParse(data);
657
- if (result.success) {
658
- return { ok: true, value: result.data };
659
- }
660
- return { ok: false, error: result.error.message };
661
- }
662
- /**
663
- * Create a DisputeAttestation with current timestamp and 'filed' state.
664
- *
665
- * @param params - Attestation parameters
666
- * @returns A valid DisputeAttestation in 'filed' state
667
- *
668
- * @example
669
- * ```typescript
670
- * const dispute = createDisputeAttestation({
671
- * issuer: 'https://publisher.example.com',
672
- * ref: '01ARZ3NDEKTSV4RRFFQ69G5FAV',
673
- * dispute_type: 'unauthorized_access',
674
- * target_ref: 'jti:01H5KPT9QZA123456789VWXYZG',
675
- * target_type: 'receipt',
676
- * grounds: [{ code: 'missing_receipt' }],
677
- * description: 'Content was accessed without a valid receipt.',
678
- * });
679
- * ```
680
- */
681
- function createDisputeAttestation(params) {
682
- const now = new Date().toISOString();
683
- const evidence = {
684
- dispute_type: params.dispute_type,
685
- target_ref: params.target_ref,
686
- target_type: params.target_type,
687
- grounds: params.grounds,
688
- description: params.description,
689
- state: 'filed',
690
- };
691
- if (params.contact) {
692
- evidence.contact = params.contact;
693
- }
694
- if (params.supporting_receipts) {
695
- evidence.supporting_receipts = params.supporting_receipts;
696
- }
697
- if (params.supporting_attributions) {
698
- evidence.supporting_attributions = params.supporting_attributions;
699
- }
700
- if (params.supporting_documents) {
701
- evidence.supporting_documents = params.supporting_documents;
702
- }
703
- if (params.window_hint_days !== undefined) {
704
- evidence.window_hint_days = params.window_hint_days;
705
- }
706
- const attestation = {
707
- type: exports.DISPUTE_TYPE,
708
- issuer: params.issuer,
709
- issued_at: now,
710
- ref: params.ref,
711
- evidence,
712
- };
713
- if (params.expires_at) {
714
- attestation.expires_at = params.expires_at;
715
- }
716
- return attestation;
717
- }
718
- /**
719
- * Transition a dispute to a new state.
720
- *
721
- * @param dispute - Current dispute attestation
722
- * @param newState - Target state
723
- * @param reason - Reason for transition (optional)
724
- * @param resolution - Resolution details (required for terminal states)
725
- * @returns Updated dispute attestation or error
726
- *
727
- * @example
728
- * ```typescript
729
- * // Acknowledge a filed dispute
730
- * const acknowledged = transitionDisputeState(
731
- * dispute,
732
- * 'acknowledged',
733
- * 'Dispute received and under review'
734
- * );
735
- *
736
- * // Resolve a dispute (terminal state requires resolution)
737
- * const resolved = transitionDisputeState(
738
- * dispute,
739
- * 'resolved',
740
- * 'Investigation complete',
741
- * {
742
- * outcome: 'upheld',
743
- * decided_at: new Date().toISOString(),
744
- * decided_by: 'https://platform.example.com',
745
- * rationale: 'Evidence supports the claim.',
746
- * }
747
- * );
748
- * ```
749
- */
750
- function transitionDisputeState(dispute, newState, reason, resolution) {
751
- const currentState = dispute.evidence.state;
752
- // Check if transition is valid
753
- if (!canTransitionTo(currentState, newState)) {
754
- return {
755
- ok: false,
756
- error: `Invalid transition from "${currentState}" to "${newState}". Valid transitions: ${exports.DISPUTE_TRANSITIONS[currentState].join(', ') || 'none'}`,
757
- code: 'INVALID_TRANSITION',
758
- };
759
- }
760
- // Check resolution requirements
761
- const isTargetTerminal = isTerminalState(newState);
762
- if (isTargetTerminal && !resolution) {
763
- return {
764
- ok: false,
765
- error: `Resolution is required when transitioning to terminal state "${newState}"`,
766
- code: 'RESOLUTION_REQUIRED',
767
- };
768
- }
769
- if (!isTargetTerminal && resolution) {
770
- return {
771
- ok: false,
772
- error: `Resolution is not allowed for non-terminal state "${newState}"`,
773
- code: 'RESOLUTION_NOT_ALLOWED',
774
- };
775
- }
776
- // Create updated dispute
777
- const now = new Date().toISOString();
778
- // Destructure to separate resolution from other evidence fields
779
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
780
- const { resolution: _existingResolution, ...evidenceWithoutResolution } = dispute.evidence;
781
- // Build updated evidence: start without resolution, add back only if terminal
782
- const updatedEvidence = {
783
- ...evidenceWithoutResolution,
784
- state: newState,
785
- state_changed_at: now,
786
- };
787
- if (reason) {
788
- updatedEvidence.state_reason = reason;
789
- }
790
- // Only add resolution for terminal states
791
- if (isTargetTerminal && resolution) {
792
- updatedEvidence.resolution = resolution;
793
- }
794
- return {
795
- ok: true,
796
- value: {
797
- ...dispute,
798
- evidence: updatedEvidence,
799
- },
800
- };
801
- }
802
- // =============================================================================
803
- // TIME VALIDATION HELPERS (v0.9.27+)
804
- // =============================================================================
805
- /**
806
- * Check if a dispute attestation is expired.
807
- *
808
- * @param attestation - The attestation to check
809
- * @param clockSkew - Clock skew tolerance in milliseconds (default: 30000)
810
- * @returns True if expired
811
- */
812
- function isDisputeExpired(attestation, clockSkew = 30000) {
813
- if (!attestation.expires_at) {
814
- return false; // No expiry = never expires
815
- }
816
- const expiresAt = new Date(attestation.expires_at).getTime();
817
- const now = Date.now();
818
- return expiresAt < now - clockSkew;
819
- }
820
- /**
821
- * Check if a dispute attestation is not yet valid (issued_at in future).
822
- *
823
- * @param attestation - The attestation to check
824
- * @param clockSkew - Clock skew tolerance in milliseconds (default: 30000)
825
- * @returns True if not yet valid
826
- */
827
- function isDisputeNotYetValid(attestation, clockSkew = 30000) {
828
- const issuedAt = new Date(attestation.issued_at).getTime();
829
- const now = Date.now();
830
- return issuedAt > now + clockSkew;
831
- }
832
- //# sourceMappingURL=dispute.js.map