@peac/protocol 0.13.3 → 0.14.0

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 (33) hide show
  1. package/dist/_internal/record-core/bounded-validator.d.ts +65 -29
  2. package/dist/_internal/record-core/bounded-validator.d.ts.map +1 -1
  3. package/dist/_internal/record-core/validation-gate.d.ts +102 -0
  4. package/dist/_internal/record-core/validation-gate.d.ts.map +1 -0
  5. package/dist/_internal/record-core/validators/iat-not-yet-valid.d.ts +29 -0
  6. package/dist/_internal/record-core/validators/iat-not-yet-valid.d.ts.map +1 -0
  7. package/dist/_internal/record-core/validators/index.d.ts +7 -0
  8. package/dist/_internal/record-core/validators/index.d.ts.map +1 -1
  9. package/dist/_internal/record-core/validators/jose-typ-strictness.d.ts +39 -0
  10. package/dist/_internal/record-core/validators/jose-typ-strictness.d.ts.map +1 -0
  11. package/dist/_internal/record-core/validators/policy-binding.d.ts +33 -0
  12. package/dist/_internal/record-core/validators/policy-binding.d.ts.map +1 -0
  13. package/dist/_internal/record-core/validators/schema-parse.d.ts +72 -0
  14. package/dist/_internal/record-core/validators/schema-parse.d.ts.map +1 -0
  15. package/dist/_internal/record-core/validators/signature.d.ts +31 -0
  16. package/dist/_internal/record-core/validators/signature.d.ts.map +1 -0
  17. package/dist/_internal/record-core/validators/type-extension-enforcement.d.ts +42 -0
  18. package/dist/_internal/record-core/validators/type-extension-enforcement.d.ts.map +1 -0
  19. package/dist/_internal/record-core/validators/unknown-extension-grammar.d.ts +39 -0
  20. package/dist/_internal/record-core/validators/unknown-extension-grammar.d.ts.map +1 -0
  21. package/dist/_internal/test-helpers/candidate-runner.d.ts +52 -0
  22. package/dist/_internal/test-helpers/candidate-runner.d.ts.map +1 -0
  23. package/dist/index.cjs +360 -59
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.mjs +360 -59
  26. package/dist/index.mjs.map +1 -1
  27. package/dist/issue.d.ts.map +1 -1
  28. package/dist/verify-local.cjs +285 -18
  29. package/dist/verify-local.cjs.map +1 -1
  30. package/dist/verify-local.d.ts.map +1 -1
  31. package/dist/verify-local.mjs +285 -18
  32. package/dist/verify-local.mjs.map +1 -1
  33. package/package.json +4 -4
package/dist/index.mjs CHANGED
@@ -310,6 +310,141 @@ function validateExtensionBudgetInternal(extensions) {
310
310
  if (violations.length === 0) return ACCEPTED4;
311
311
  return { accepted: false, violations };
312
312
  }
313
+ function validateSchemaParseInternal(payload) {
314
+ const pr = parseReceiptClaims(payload);
315
+ if (!pr.ok) {
316
+ return {
317
+ accepted: false,
318
+ errorCode: pr.error.code,
319
+ errorMessage: pr.error.message,
320
+ ...pr.error.issues !== void 0 && { errorIssues: pr.error.issues }
321
+ };
322
+ }
323
+ if (pr.wireVersion !== "0.2") {
324
+ return {
325
+ accepted: false,
326
+ errorCode: "E_UNSUPPORTED_WIRE_VERSION",
327
+ errorMessage: `Unsupported wire version: ${pr.wireVersion}`
328
+ };
329
+ }
330
+ return {
331
+ accepted: true,
332
+ claims: pr.claims,
333
+ wireVersion: "0.2",
334
+ warnings: pr.warnings
335
+ };
336
+ }
337
+
338
+ // src/_internal/record-core/validators/jose-typ-strictness.ts
339
+ var ACCEPTED_NO_WARN = { accepted: true };
340
+ var ACCEPTED_TYP_MISSING = {
341
+ accepted: true,
342
+ warnings: [{ code: "typ_missing" }]
343
+ };
344
+ var REJECTED_INVALID_FORMAT = {
345
+ accepted: false,
346
+ errorCode: "E_INVALID_FORMAT"
347
+ };
348
+ function validateJoseTypStrictnessInternal(typ, strictness) {
349
+ const present = typeof typ === "string" && typ.length > 0;
350
+ if (present) return ACCEPTED_NO_WARN;
351
+ if (strictness === "strict") return REJECTED_INVALID_FORMAT;
352
+ return ACCEPTED_TYP_MISSING;
353
+ }
354
+
355
+ // src/_internal/record-core/validators/iat-not-yet-valid.ts
356
+ var ACCEPTED5 = { accepted: true };
357
+ var REJECTED2 = {
358
+ accepted: false,
359
+ errorCode: "E_NOT_YET_VALID"
360
+ };
361
+ function validateIatNotYetValidInternal(iat, now, maxClockSkew) {
362
+ if (iat > now + maxClockSkew) return REJECTED2;
363
+ return ACCEPTED5;
364
+ }
365
+ function validatePolicyBindingInternal(receiptPolicyDigest, localPolicyDigest) {
366
+ if (receiptPolicyDigest === void 0 || localPolicyDigest === void 0) {
367
+ return { accepted: true, status: "unavailable" };
368
+ }
369
+ const status = verifyPolicyBinding(receiptPolicyDigest, localPolicyDigest);
370
+ if (status === "failed") {
371
+ return { accepted: false, errorCode: "E_POLICY_BINDING_FAILED", status };
372
+ }
373
+ return { accepted: true, status };
374
+ }
375
+ var ACCEPTED_EMPTY = {
376
+ accepted: true,
377
+ warnings: []
378
+ };
379
+ function escapeJsonPointer(token) {
380
+ return token.replace(/~/g, "~0").replace(/\//g, "~1");
381
+ }
382
+ function validateUnknownExtensionGrammarInternal(extensions) {
383
+ if (extensions === void 0) return ACCEPTED_EMPTY;
384
+ const keys = Object.keys(extensions);
385
+ if (keys.length === 0) return ACCEPTED_EMPTY;
386
+ const warnings = [];
387
+ for (const key of keys) {
388
+ if (!REGISTERED_EXTENSION_GROUP_KEYS.has(key) && isValidExtensionKey(key)) {
389
+ warnings.push({
390
+ code: "unknown_extension_preserved",
391
+ pointer: `/extensions/${escapeJsonPointer(key)}`
392
+ });
393
+ }
394
+ }
395
+ return { accepted: true, warnings };
396
+ }
397
+
398
+ // src/type-extension-check.ts
399
+ function checkTypeExtensionMapping(kind, type, extensions, typeToExtensionMap, registeredExtensionGroupKeys) {
400
+ if (kind === "challenge") {
401
+ return { status: "skip" };
402
+ }
403
+ const expectedGroup = typeToExtensionMap.get(type);
404
+ if (expectedGroup === void 0) {
405
+ return { status: "skip" };
406
+ }
407
+ if (extensions !== void 0 && Object.prototype.hasOwnProperty.call(extensions, expectedGroup)) {
408
+ return { status: "ok" };
409
+ }
410
+ const presentRegistered = [];
411
+ if (extensions !== void 0) {
412
+ for (const key of Object.keys(extensions)) {
413
+ if (key !== expectedGroup && registeredExtensionGroupKeys.has(key)) {
414
+ presentRegistered.push(key);
415
+ }
416
+ }
417
+ }
418
+ return {
419
+ status: presentRegistered.length > 0 ? "mismatch" : "missing",
420
+ expected_extension_group: expectedGroup,
421
+ present_registered_extension_groups: presentRegistered
422
+ };
423
+ }
424
+
425
+ // src/_internal/record-core/validators/type-extension-enforcement.ts
426
+ var ACCEPTED_NO_WARN2 = { accepted: true };
427
+ function validateTypeExtensionEnforcementInternal(kind, type, extensions, strictness) {
428
+ const result = checkTypeExtensionMapping(
429
+ kind,
430
+ type,
431
+ extensions,
432
+ TYPE_TO_EXTENSION_MAP,
433
+ REGISTERED_EXTENSION_GROUP_KEYS
434
+ );
435
+ if (result.status === "ok" || result.status === "skip") {
436
+ return ACCEPTED_NO_WARN2;
437
+ }
438
+ if (strictness === "strict") {
439
+ const errorCode = result.status === "missing" ? "E_EXTENSION_GROUP_REQUIRED" : "E_EXTENSION_GROUP_MISMATCH";
440
+ return { accepted: false, errorCode, pointer: "/type" };
441
+ }
442
+ const warningCode = result.status === "missing" ? "extension_group_missing" : "extension_group_mismatch";
443
+ return {
444
+ accepted: true,
445
+ warnings: [{ code: warningCode, pointer: "/type" }]
446
+ };
447
+ }
313
448
 
314
449
  // src/_internal/record-core/bounded-validator.ts
315
450
  function runBoundedValidatorShadow(input) {
@@ -369,12 +504,154 @@ function runBoundedValidatorShadow(input) {
369
504
  violations.push({ layer: "extension-budget", code: v.code, path: v.path });
370
505
  }
371
506
  }
507
+ if (input.fullClaims !== void 0) {
508
+ const schemaRes = validateSchemaParseInternal(input.fullClaims);
509
+ if (!schemaRes.accepted && schemaRes.errorCode !== void 0) {
510
+ violations.push({ layer: "schema-parse", code: schemaRes.errorCode });
511
+ }
512
+ }
513
+ if (input.header !== void 0 && input.strictness !== void 0) {
514
+ const typRes = validateJoseTypStrictnessInternal(input.header.typ, input.strictness);
515
+ if (!typRes.accepted) {
516
+ violations.push({ layer: "jose-typ-strictness", code: typRes.errorCode });
517
+ } else if (typRes.warnings) {
518
+ for (const w of typRes.warnings) {
519
+ warnings.push({ layer: "jose-typ-strictness", code: w.code });
520
+ }
521
+ }
522
+ }
523
+ if (input.maxClockSkew !== void 0) {
524
+ const iatRes = validateIatNotYetValidInternal(input.claims.iat, input.now, input.maxClockSkew);
525
+ if (!iatRes.accepted && iatRes.errorCode !== void 0) {
526
+ violations.push({ layer: "iat-not-yet-valid", code: iatRes.errorCode });
527
+ }
528
+ }
529
+ if (input.receiptPolicyDigest !== void 0 && input.localPolicyDigest !== void 0) {
530
+ const policyRes = validatePolicyBindingInternal(
531
+ input.receiptPolicyDigest,
532
+ input.localPolicyDigest
533
+ );
534
+ if (!policyRes.accepted && policyRes.errorCode !== void 0) {
535
+ violations.push({ layer: "policy-binding", code: policyRes.errorCode });
536
+ }
537
+ }
538
+ const unknownExtRes = validateUnknownExtensionGrammarInternal(input.claims.extensions);
539
+ for (const w of unknownExtRes.warnings) {
540
+ warnings.push({
541
+ layer: "unknown-extension-grammar",
542
+ code: w.code,
543
+ path: w.pointer
544
+ });
545
+ }
546
+ if (input.strictness !== void 0) {
547
+ const enforceRes = validateTypeExtensionEnforcementInternal(
548
+ input.claims.kind,
549
+ input.claims.type,
550
+ input.claims.extensions,
551
+ input.strictness
552
+ );
553
+ if (!enforceRes.accepted) {
554
+ violations.push({
555
+ layer: "type-extension-enforcement",
556
+ code: enforceRes.errorCode,
557
+ path: enforceRes.pointer
558
+ });
559
+ } else if (enforceRes.warnings) {
560
+ for (const w of enforceRes.warnings) {
561
+ warnings.push({
562
+ layer: "type-extension-enforcement",
563
+ code: w.code,
564
+ path: w.pointer
565
+ });
566
+ }
567
+ }
568
+ }
372
569
  return {
373
570
  accepted: violations.length === 0,
374
571
  violations,
375
572
  warnings
376
573
  };
377
574
  }
575
+ var MAX_SANITIZED_ISSUES = 25;
576
+ function sanitizeIssues(issues) {
577
+ if (!Array.isArray(issues)) return void 0;
578
+ return issues.slice(0, MAX_SANITIZED_ISSUES).map((issue2) => {
579
+ const candidate = issue2;
580
+ const path = Array.isArray(candidate.path) ? candidate.path.map(
581
+ (segment) => typeof segment === "string" || typeof segment === "number" ? String(segment) : ""
582
+ ).join(".") : "";
583
+ const message = typeof candidate.message === "string" ? candidate.message : String(candidate.message ?? issue2);
584
+ return { path, message };
585
+ });
586
+ }
587
+ function runBoundedValidationGate(input) {
588
+ if (input.surface === "issueWire02") {
589
+ return runIssueWire02(input.payload);
590
+ }
591
+ return runVerifyLocal(input.payload);
592
+ }
593
+ function runIssueWire02(payload) {
594
+ const schemaRes = validateSchemaParseInternal(payload);
595
+ if (!schemaRes.accepted) {
596
+ const firstIssueMessage = schemaRes.errorIssues?.[0]?.message;
597
+ return {
598
+ ok: false,
599
+ code: "E_INVALID_FORMAT",
600
+ message: `Wire 0.2 claims schema validation failed: ${firstIssueMessage ?? "unknown"}`
601
+ };
602
+ }
603
+ return { ok: true };
604
+ }
605
+ function runVerifyLocal(payload) {
606
+ const kernelRes = validateKernelConstraints(payload);
607
+ if (!kernelRes.valid) {
608
+ const v = kernelRes.violations[0];
609
+ return {
610
+ ok: false,
611
+ code: "E_CONSTRAINT_VIOLATION",
612
+ message: `Kernel constraint violated: ${v.constraint} (actual: ${v.actual}, limit: ${v.limit})`
613
+ };
614
+ }
615
+ const schemaRes = validateSchemaParseInternal(payload);
616
+ if (!schemaRes.accepted) {
617
+ if (schemaRes.errorCode === "E_UNSUPPORTED_WIRE_VERSION") {
618
+ return {
619
+ ok: false,
620
+ code: "E_UNSUPPORTED_WIRE_VERSION",
621
+ message: "Wire 0.1 receipts are not supported. Re-issue as Wire 0.2 using issue()."
622
+ };
623
+ }
624
+ const sanitized = sanitizeIssues(schemaRes.errorIssues);
625
+ const details = {};
626
+ if (schemaRes.errorCode !== void 0) {
627
+ Object.assign(details, { parse_code: schemaRes.errorCode });
628
+ }
629
+ if (sanitized !== void 0) {
630
+ Object.assign(details, { issues: sanitized });
631
+ }
632
+ return {
633
+ ok: false,
634
+ code: "E_INVALID_FORMAT",
635
+ message: `Receipt schema validation failed: ${schemaRes.errorMessage ?? "unknown"}`,
636
+ ...Object.keys(details).length > 0 && { details }
637
+ };
638
+ }
639
+ if (schemaRes.wireVersion !== "0.2" || schemaRes.claims === void 0) {
640
+ return {
641
+ ok: false,
642
+ code: "E_UNSUPPORTED_WIRE_VERSION",
643
+ message: "Wire 0.1 receipts are not supported. Re-issue as Wire 0.2 using issue()."
644
+ };
645
+ }
646
+ return {
647
+ ok: true,
648
+ parsed: {
649
+ wireVersion: "0.2",
650
+ claims: schemaRes.claims,
651
+ parserWarnings: schemaRes.warnings ?? []
652
+ }
653
+ };
654
+ }
378
655
  var SECRET_PATTERNS = [
379
656
  // Compact JWS / JWT (header.payload.signature with eyJ-prefixed header).
380
657
  /eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,
@@ -812,7 +1089,7 @@ async function issue(options) {
812
1089
  return issueWire02(options);
813
1090
  }
814
1091
  async function issueWire02(options) {
815
- void readLegacyPathFlag(options);
1092
+ const legacyPath = readLegacyPathFlag(options);
816
1093
  if (!isCanonicalIss(options.iss)) {
817
1094
  throw new IssueError({
818
1095
  code: "E_ISS_NOT_CANONICAL",
@@ -841,22 +1118,39 @@ async function issueWire02(options) {
841
1118
  ...options.policy !== void 0 && { policy: options.policy },
842
1119
  ...options.extensions !== void 0 && { extensions: options.extensions }
843
1120
  };
844
- const parseResult = Wire02ClaimsSchema.safeParse(claims);
845
- if (!parseResult.success) {
846
- const firstIssue = parseResult.error.issues[0];
847
- throw new IssueError({
848
- code: "E_INVALID_FORMAT",
849
- category: "validation",
850
- severity: "error",
851
- retryable: false,
852
- http_status: 400,
853
- details: {
854
- message: `Wire 0.2 claims schema validation failed: ${firstIssue?.message ?? "unknown"}`
855
- }
1121
+ if (legacyPath) {
1122
+ const parseResult = Wire02ClaimsSchema.safeParse(claims);
1123
+ if (!parseResult.success) {
1124
+ const firstIssue = parseResult.error.issues[0];
1125
+ throw new IssueError({
1126
+ code: "E_INVALID_FORMAT",
1127
+ category: "validation",
1128
+ severity: "error",
1129
+ retryable: false,
1130
+ http_status: 400,
1131
+ details: {
1132
+ message: `Wire 0.2 claims schema validation failed: ${firstIssue?.message ?? "unknown"}`
1133
+ }
1134
+ });
1135
+ }
1136
+ } else {
1137
+ const gateResult = runBoundedValidationGate({
1138
+ surface: "issueWire02",
1139
+ payload: claims
856
1140
  });
1141
+ if (!gateResult.ok) {
1142
+ throw new IssueError({
1143
+ code: gateResult.code,
1144
+ category: "validation",
1145
+ severity: "error",
1146
+ retryable: false,
1147
+ http_status: 400,
1148
+ details: { message: gateResult.message }
1149
+ });
1150
+ }
857
1151
  }
858
1152
  const jws = await defaultCodec.encode(claims, options.privateKey, options.kid);
859
- if (isShadowEnabled(options)) {
1153
+ if (legacyPath && isShadowEnabled(options)) {
860
1154
  scheduleShadow({
861
1155
  call: "issue",
862
1156
  realResult: realObservationForIssue(),
@@ -2062,33 +2356,6 @@ async function verifyReceipt(optionsOrJws) {
2062
2356
  }
2063
2357
  }
2064
2358
 
2065
- // src/type-extension-check.ts
2066
- function checkTypeExtensionMapping(kind, type, extensions, typeToExtensionMap, registeredExtensionGroupKeys) {
2067
- if (kind === "challenge") {
2068
- return { status: "skip" };
2069
- }
2070
- const expectedGroup = typeToExtensionMap.get(type);
2071
- if (expectedGroup === void 0) {
2072
- return { status: "skip" };
2073
- }
2074
- if (extensions !== void 0 && Object.prototype.hasOwnProperty.call(extensions, expectedGroup)) {
2075
- return { status: "ok" };
2076
- }
2077
- const presentRegistered = [];
2078
- if (extensions !== void 0) {
2079
- for (const key of Object.keys(extensions)) {
2080
- if (key !== expectedGroup && registeredExtensionGroupKeys.has(key)) {
2081
- presentRegistered.push(key);
2082
- }
2083
- }
2084
- }
2085
- return {
2086
- status: presentRegistered.length > 0 ? "mismatch" : "missing",
2087
- expected_extension_group: expectedGroup,
2088
- present_registered_extension_groups: presentRegistered
2089
- };
2090
- }
2091
-
2092
2359
  // src/_internal/record-core/shadow-hook.ts
2093
2360
  function maybeRunShadowValidation(input) {
2094
2361
  return void 0;
@@ -2120,7 +2387,7 @@ function sanitizeParseIssues(issues) {
2120
2387
  }));
2121
2388
  }
2122
2389
  async function verifyLocal(jws, publicKey, options = {}) {
2123
- void readLegacyPathFlag(options);
2390
+ const legacyPath = readLegacyPathFlag(options);
2124
2391
  const {
2125
2392
  issuer,
2126
2393
  subjectUri,
@@ -2153,22 +2420,56 @@ async function verifyLocal(jws, publicKey, options = {}) {
2153
2420
  message: "JWS typ header is absent; accepted in interop mode"
2154
2421
  });
2155
2422
  }
2156
- const constraintResult = validateKernelConstraints(result.payload);
2157
- if (!constraintResult.valid) {
2158
- const v = constraintResult.violations[0];
2159
- return {
2160
- valid: false,
2161
- code: "E_CONSTRAINT_VIOLATION",
2162
- message: `Kernel constraint violated: ${v.constraint} (actual: ${v.actual}, limit: ${v.limit})`
2163
- };
2164
- }
2165
- const pr = parseReceiptClaims(result.payload);
2166
- if (!pr.ok) {
2167
- return {
2168
- valid: false,
2169
- code: "E_INVALID_FORMAT",
2170
- message: `Receipt schema validation failed: ${pr.error.message}`,
2171
- details: { parse_code: pr.error.code, issues: sanitizeParseIssues(pr.error.issues) }
2423
+ let pr;
2424
+ if (legacyPath) {
2425
+ const constraintResult = validateKernelConstraints(result.payload);
2426
+ if (!constraintResult.valid) {
2427
+ const v = constraintResult.violations[0];
2428
+ return {
2429
+ valid: false,
2430
+ code: "E_CONSTRAINT_VIOLATION",
2431
+ message: `Kernel constraint violated: ${v.constraint} (actual: ${v.actual}, limit: ${v.limit})`
2432
+ };
2433
+ }
2434
+ const parsed = parseReceiptClaims(result.payload);
2435
+ if (!parsed.ok) {
2436
+ return {
2437
+ valid: false,
2438
+ code: "E_INVALID_FORMAT",
2439
+ message: `Receipt schema validation failed: ${parsed.error.message}`,
2440
+ details: {
2441
+ parse_code: parsed.error.code,
2442
+ issues: sanitizeParseIssues(parsed.error.issues)
2443
+ }
2444
+ };
2445
+ }
2446
+ pr = parsed;
2447
+ } else {
2448
+ const gateResult = runBoundedValidationGate({
2449
+ surface: "verifyLocal",
2450
+ payload: result.payload
2451
+ });
2452
+ if (!gateResult.ok) {
2453
+ return {
2454
+ valid: false,
2455
+ code: gateResult.code,
2456
+ message: gateResult.message,
2457
+ ...gateResult.details !== void 0 && { details: gateResult.details }
2458
+ };
2459
+ }
2460
+ const parsedFromGate = gateResult.parsed;
2461
+ if (parsedFromGate === void 0) {
2462
+ return {
2463
+ valid: false,
2464
+ code: "E_INTERNAL",
2465
+ message: "Bounded validation gate accepted without surfacing parsed claims"
2466
+ };
2467
+ }
2468
+ pr = {
2469
+ ok: true,
2470
+ wireVersion: parsedFromGate.wireVersion,
2471
+ warnings: parsedFromGate.parserWarnings,
2472
+ claims: parsedFromGate.claims
2172
2473
  };
2173
2474
  }
2174
2475
  if (pr.wireVersion === "0.2") {
@@ -2285,7 +2586,7 @@ async function verifyLocal(jws, publicKey, options = {}) {
2285
2586
  ...callerBindings?.documents !== void 0 && callerBindings.documents.length > 0 && { documents: callerBindings.documents }
2286
2587
  };
2287
2588
  const sortedWarnings = sortWarnings(accumulatedWarnings);
2288
- if (isShadowEnabled(options)) {
2589
+ if (legacyPath && isShadowEnabled(options)) {
2289
2590
  const headerSnapshot = { ...result.header };
2290
2591
  scheduleShadow({
2291
2592
  call: "verifyLocal",