@peac/protocol 0.13.4 → 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.
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Production-facing bounded validation gate.
3
+ *
4
+ * INTERNAL ONLY. Not re-exported from `packages/protocol/src/index.ts`.
5
+ *
6
+ * The gate is the only production wrapper for the bounded validation
7
+ * path. The pre-existing `runBoundedValidatorShadow` from
8
+ * `bounded-validator.ts` remains the entry point for shadow / corpus
9
+ * / parity-harness consumers and is not the primary admission path
10
+ * for the protocol entry points; the production-wrapper boundary is
11
+ * enforced by `tests/tooling/production-gate-boundary.test.ts`.
12
+ *
13
+ * The gate is entrypoint-aware: callers identify themselves with a
14
+ * `surface` discriminator, and the gate applies a per-surface
15
+ * production projection allowlist. Layers in the bounded validator's
16
+ * broader composition that fall outside the allowlist for a given
17
+ * surface stay available to shadow / parity tests through
18
+ * `runBoundedValidatorShadow` but are not surfaced as production
19
+ * failure or production warning here.
20
+ *
21
+ * Allowlist for surface 'issueWire02':
22
+ * - schema-parse only.
23
+ *
24
+ * Allowlist for surface 'verifyLocal':
25
+ * - kernel-constraints (via canonical `validateKernelConstraints`).
26
+ * - schema-parse (via the extended `validateSchemaParseInternal`).
27
+ *
28
+ * Out of scope here (kept inline-canonical on both branches by the
29
+ * caller): caller-option-bearing checks (issuer mismatch, subjectUri
30
+ * mismatch, policyDigest format), strictness routing for missing typ,
31
+ * iat-not-yet-valid, occurred_at temporal, type-extension warnings,
32
+ * type-extension enforcement, policy-binding compute, bindings
33
+ * construction, sortWarnings.
34
+ *
35
+ * The canonical materializer (claim encoding, JWS signing, JWS
36
+ * decoding, result-shape construction) is shared between the
37
+ * bounded-default and rollback branches and is not touched by the
38
+ * gate.
39
+ */
40
+ import type { VerificationWarning } from '@peac/kernel';
41
+ import type { Wire02Claims } from '@peac/schema';
42
+ /**
43
+ * Discriminator identifying which protocol entry point invoked the
44
+ * gate. Drives the production projection allowlist. Adding a third
45
+ * surface requires a deliberate plan amendment, not an unannounced
46
+ * source change.
47
+ */
48
+ export type GateSurface = 'issueWire02' | 'verifyLocal';
49
+ /** Sanitized parse-issue projection surfaced on rejection. */
50
+ export interface SanitizedParseIssue {
51
+ readonly path: string;
52
+ readonly message: string;
53
+ }
54
+ /** Parsed-claims projection surfaced on `verifyLocal` acceptance. */
55
+ export interface ValidationGateParsed {
56
+ readonly wireVersion: '0.2';
57
+ readonly claims: Wire02Claims;
58
+ readonly parserWarnings: readonly VerificationWarning[];
59
+ }
60
+ /** Failure-detail projection. Smaller than the bounded layer set. */
61
+ export interface ValidationGateFailureDetails {
62
+ readonly parse_code?: string;
63
+ readonly issues?: readonly SanitizedParseIssue[];
64
+ }
65
+ /** Successful gate result. */
66
+ export interface ValidationGateSuccess {
67
+ readonly ok: true;
68
+ readonly parsed?: ValidationGateParsed;
69
+ }
70
+ /** Failed gate result. */
71
+ export interface ValidationGateFailure {
72
+ readonly ok: false;
73
+ readonly code: string;
74
+ readonly message: string;
75
+ readonly details?: ValidationGateFailureDetails;
76
+ }
77
+ export type ValidationGateResult = ValidationGateSuccess | ValidationGateFailure;
78
+ /** Inputs to the gate. Only `surface` and `payload` are required. */
79
+ export interface ValidationGateInput {
80
+ readonly surface: GateSurface;
81
+ /**
82
+ * Full claims object. For surface 'issueWire02' this is the just-
83
+ * constructed `Wire02Claims` cast to a plain record (the same
84
+ * object that today flows through `Wire02ClaimsSchema.safeParse`).
85
+ * For surface 'verifyLocal' this is the decoded JWS payload (the
86
+ * same object that today flows through `validateKernelConstraints`
87
+ * and `parseReceiptClaims`).
88
+ */
89
+ readonly payload: unknown;
90
+ }
91
+ /**
92
+ * Run the production projection of the bounded validation path.
93
+ *
94
+ * Behavior is byte-equivalent to the inline canonical sequences at
95
+ * `issue.ts` and `verify-local.ts` for every input on the covered
96
+ * runtime matrix. Any cross-branch divergence on JWS bytes, result
97
+ * shape, error code, warning order, thrown error class, success /
98
+ * failure classification, or observable default behavior is a
99
+ * validation-equivalence regression.
100
+ */
101
+ export declare function runBoundedValidationGate(input: ValidationGateInput): ValidationGateResult;
102
+ //# sourceMappingURL=validation-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation-gate.d.ts","sourceRoot":"","sources":["../../../src/_internal/record-core/validation-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGjD;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,aAAa,CAAC;AAExD,8DAA8D;AAC9D,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,qEAAqE;AACrE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,cAAc,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACzD;AAED,qEAAqE;AACrE,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;CAClD;AAED,8BAA8B;AAC9B,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,MAAM,CAAC,EAAE,oBAAoB,CAAC;CACxC;AAED,0BAA0B;AAC1B,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,4BAA4B,CAAC;CACjD;AAED,MAAM,MAAM,oBAAoB,GAAG,qBAAqB,GAAG,qBAAqB,CAAC;AAEjF,qEAAqE;AACrE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAwBD;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,mBAAmB,GAAG,oBAAoB,CAKzF"}
@@ -1,42 +1,72 @@
1
1
  /**
2
- * Bounded internal schema-parse validator (canonical-composed).
2
+ * Bounded internal schema-parse validator.
3
3
  *
4
4
  * INTERNAL ONLY. Thin wrapper around the canonical
5
- * `parseReceiptClaims` from `@peac/schema`, projecting the canonical
6
- * result into the bounded validator's normalized shape. The wrapper
7
- * has no decision logic of its own: it delegates the parse, then
8
- * surfaces the canonical accept/reject and the canonical error code.
5
+ * `parseReceiptClaims` from `@peac/schema`. Projects the canonical
6
+ * accept/reject signal plus the full canonical projection (parsed
7
+ * claims, parser warnings, canonical error triple) so production
8
+ * callers can reproduce the canonical surface byte-equally without
9
+ * re-invoking `parseReceiptClaims`.
9
10
  *
10
- * This is observational equivalence by construction, not a divergent
11
- * parser. The layer boundary allows an alternate implementation
12
- * without changing callers; the input/output shape is stable enough
13
- * for a drop-in replacement should rigorous cross-implementation
14
- * parity beyond observational evidence be required.
15
- *
16
- * Module is observational only; not re-exported from
17
- * `packages/protocol/src/index.ts` and not wired into the public
18
- * runtime path.
11
+ * Module is internal-only; not re-exported from
12
+ * `packages/protocol/src/index.ts`.
19
13
  *
20
14
  * SCOPE:
21
15
  * - Run `parseReceiptClaims(payload)` and surface its accept/reject.
22
- * - On rejection, surface `pr.error.code` (canonical parse-error code)
23
- * so the candidate's error taxonomy matches the canonical's.
24
- * - On acceptance, no warnings are surfaced from this layer; parser
25
- * warnings (e.g., `type_unregistered`, `unknown_extension_preserved`)
26
- * are emitted by the canonical parser and consumed at higher layers
27
- * (verifyLocal warning aggregation).
16
+ * - On acceptance: surface `wireVersion`, parsed `claims`, and
17
+ * parser `warnings` (e.g., `type_unregistered`,
18
+ * `unknown_extension_preserved`). Higher-layer aggregation merges
19
+ * these into the caller's warning array.
20
+ * - On rejection: surface the canonical error triple
21
+ * (`errorCode`, `errorMessage`, `errorIssues`) so callers can
22
+ * project the canonical `details: { parse_code, issues }` and
23
+ * `message: 'Receipt schema validation failed: <message>'`
24
+ * surface byte-equally.
28
25
  */
26
+ import type { ZodError } from 'zod';
27
+ import type { VerificationWarning } from '@peac/kernel';
29
28
  import type { Wire02Claims } from '@peac/schema';
30
29
  export interface SchemaParseResult {
31
30
  readonly accepted: boolean;
31
+ /**
32
+ * Canonical parse-error code from `parseReceiptClaims` when the
33
+ * parse rejects. Mirrors `pr.error.code`.
34
+ */
32
35
  readonly errorCode?: string;
36
+ /**
37
+ * Canonical parse-error message from `parseReceiptClaims` when the
38
+ * parse rejects. Mirrors `pr.error.message`. Required for callers
39
+ * that reproduce the canonical surface message
40
+ * `Receipt schema validation failed: <message>` byte-equally.
41
+ */
42
+ readonly errorMessage?: string;
43
+ /**
44
+ * Canonical parse-error issues from `parseReceiptClaims` when the
45
+ * parse rejects. Mirrors `pr.error.issues`. Callers downstream
46
+ * apply their own bounded sanitization (path-join, length cap)
47
+ * before surfacing on a public verifier-result `details` payload.
48
+ */
49
+ readonly errorIssues?: ZodError['issues'];
33
50
  /**
34
51
  * Parsed Wire 0.2 claims when accepted. Internal-only; does not
35
- * appear on any emitted record or public surface. Callers that need
36
- * a typed view of the parsed claims read this field; callers that
37
- * only need the accept/reject signal ignore it.
52
+ * appear on any emitted record or public surface.
38
53
  */
39
54
  readonly claims?: Wire02Claims;
55
+ /**
56
+ * Wire version surfaced from `parseReceiptClaims` on acceptance.
57
+ * The bounded surface only admits `'0.2'`; Wire 0.1 acceptance from
58
+ * the canonical parser is rejected at this layer with
59
+ * `E_UNSUPPORTED_WIRE_VERSION` because the bounded composition is
60
+ * keyed on Wire 0.2 claim shapes.
61
+ */
62
+ readonly wireVersion?: '0.2';
63
+ /**
64
+ * Parser warnings surfaced on acceptance. Mirrors `pr.warnings` for
65
+ * Wire 0.2 acceptance. Callers that aggregate into a single
66
+ * canonical warning array merge this list at the canonical-equivalent
67
+ * flow position before the canonical sort.
68
+ */
69
+ readonly warnings?: readonly VerificationWarning[];
40
70
  }
41
71
  export declare function validateSchemaParseInternal(payload: unknown): SchemaParseResult;
42
72
  //# sourceMappingURL=schema-parse.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema-parse.d.ts","sourceRoot":"","sources":["../../../../src/_internal/record-core/validators/schema-parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;CAChC;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,GAAG,iBAAiB,CAS/E"}
1
+ {"version":3,"file":"schema-parse.d.ts","sourceRoot":"","sources":["../../../../src/_internal/record-core/validators/schema-parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAExD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC1C;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACpD;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,GAAG,iBAAiB,CAuB/E"}
package/dist/index.cjs CHANGED
@@ -314,12 +314,26 @@ function validateExtensionBudgetInternal(extensions) {
314
314
  function validateSchemaParseInternal(payload) {
315
315
  const pr = schema.parseReceiptClaims(payload);
316
316
  if (!pr.ok) {
317
- return { accepted: false, errorCode: pr.error.code };
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
+ };
318
323
  }
319
324
  if (pr.wireVersion !== "0.2") {
320
- return { accepted: false, errorCode: "E_UNSUPPORTED_WIRE_VERSION" };
325
+ return {
326
+ accepted: false,
327
+ errorCode: "E_UNSUPPORTED_WIRE_VERSION",
328
+ errorMessage: `Unsupported wire version: ${pr.wireVersion}`
329
+ };
321
330
  }
322
- return { accepted: true, claims: pr.claims };
331
+ return {
332
+ accepted: true,
333
+ claims: pr.claims,
334
+ wireVersion: "0.2",
335
+ warnings: pr.warnings
336
+ };
323
337
  }
324
338
 
325
339
  // src/_internal/record-core/validators/jose-typ-strictness.ts
@@ -559,6 +573,86 @@ function runBoundedValidatorShadow(input) {
559
573
  warnings
560
574
  };
561
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
+ }
562
656
  var SECRET_PATTERNS = [
563
657
  // Compact JWS / JWT (header.payload.signature with eyJ-prefixed header).
564
658
  /eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,
@@ -996,7 +1090,7 @@ async function issue(options) {
996
1090
  return issueWire02(options);
997
1091
  }
998
1092
  async function issueWire02(options) {
999
- void readLegacyPathFlag(options);
1093
+ const legacyPath = readLegacyPathFlag(options);
1000
1094
  if (!schema.isCanonicalIss(options.iss)) {
1001
1095
  throw new IssueError({
1002
1096
  code: "E_ISS_NOT_CANONICAL",
@@ -1025,22 +1119,39 @@ async function issueWire02(options) {
1025
1119
  ...options.policy !== void 0 && { policy: options.policy },
1026
1120
  ...options.extensions !== void 0 && { extensions: options.extensions }
1027
1121
  };
1028
- const parseResult = schema.Wire02ClaimsSchema.safeParse(claims);
1029
- if (!parseResult.success) {
1030
- const firstIssue = parseResult.error.issues[0];
1031
- throw new IssueError({
1032
- code: "E_INVALID_FORMAT",
1033
- category: "validation",
1034
- severity: "error",
1035
- retryable: false,
1036
- http_status: 400,
1037
- details: {
1038
- message: `Wire 0.2 claims schema validation failed: ${firstIssue?.message ?? "unknown"}`
1039
- }
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
1040
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
+ }
1041
1152
  }
1042
1153
  const jws = await defaultCodec.encode(claims, options.privateKey, options.kid);
1043
- if (isShadowEnabled(options)) {
1154
+ if (legacyPath && isShadowEnabled(options)) {
1044
1155
  scheduleShadow({
1045
1156
  call: "issue",
1046
1157
  realResult: realObservationForIssue(),
@@ -2277,7 +2388,7 @@ function sanitizeParseIssues(issues) {
2277
2388
  }));
2278
2389
  }
2279
2390
  async function verifyLocal(jws, publicKey, options = {}) {
2280
- void readLegacyPathFlag(options);
2391
+ const legacyPath = readLegacyPathFlag(options);
2281
2392
  const {
2282
2393
  issuer,
2283
2394
  subjectUri,
@@ -2310,22 +2421,56 @@ async function verifyLocal(jws, publicKey, options = {}) {
2310
2421
  message: "JWS typ header is absent; accepted in interop mode"
2311
2422
  });
2312
2423
  }
2313
- const constraintResult = schema.validateKernelConstraints(result.payload);
2314
- if (!constraintResult.valid) {
2315
- const v = constraintResult.violations[0];
2316
- return {
2317
- valid: false,
2318
- code: "E_CONSTRAINT_VIOLATION",
2319
- message: `Kernel constraint violated: ${v.constraint} (actual: ${v.actual}, limit: ${v.limit})`
2320
- };
2321
- }
2322
- const pr = schema.parseReceiptClaims(result.payload);
2323
- if (!pr.ok) {
2324
- return {
2325
- valid: false,
2326
- code: "E_INVALID_FORMAT",
2327
- message: `Receipt schema validation failed: ${pr.error.message}`,
2328
- 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
2329
2474
  };
2330
2475
  }
2331
2476
  if (pr.wireVersion === "0.2") {
@@ -2442,7 +2587,7 @@ async function verifyLocal(jws, publicKey, options = {}) {
2442
2587
  ...callerBindings?.documents !== void 0 && callerBindings.documents.length > 0 && { documents: callerBindings.documents }
2443
2588
  };
2444
2589
  const sortedWarnings = schema.sortWarnings(accumulatedWarnings);
2445
- if (isShadowEnabled(options)) {
2590
+ if (legacyPath && isShadowEnabled(options)) {
2446
2591
  const headerSnapshot = { ...result.header };
2447
2592
  scheduleShadow({
2448
2593
  call: "verifyLocal",