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