@adcp/sdk 14.2.0 → 14.3.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 (46) hide show
  1. package/dist/lib/reporting/inspection.d.mts +3 -3
  2. package/dist/lib/reporting/inspection.d.ts +3 -3
  3. package/dist/lib/reporting/inspection.js +229 -84
  4. package/dist/lib/reporting/inspection.mjs +229 -84
  5. package/dist/lib/reporting/ledger/status-ingest.js +3 -19
  6. package/dist/lib/reporting/ledger/status-ingest.mjs +2 -18
  7. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  8. package/dist/lib/server/create-adcp-server.js +5 -2
  9. package/dist/lib/server/create-adcp-server.mjs +5 -2
  10. package/dist/lib/signing/canonicalize.d.mts +2 -0
  11. package/dist/lib/signing/canonicalize.d.ts +2 -0
  12. package/dist/lib/signing/canonicalize.js +1 -1
  13. package/dist/lib/signing/canonicalize.mjs +1 -1
  14. package/dist/lib/signing/verifier.js +1 -1
  15. package/dist/lib/signing/verifier.mjs +1 -1
  16. package/dist/lib/testing/storyboard/request-signing/builder.js +13 -1
  17. package/dist/lib/testing/storyboard/request-signing/builder.mjs +13 -1
  18. package/dist/lib/testing/storyboard/request-signing/grader.js +1 -0
  19. package/dist/lib/testing/storyboard/request-signing/grader.mjs +1 -0
  20. package/dist/lib/testing/storyboard/request-signing/vector-loader.js +1 -1
  21. package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs +1 -1
  22. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  23. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  24. package/dist/lib/utils/pointer-utils.d.mts +2 -0
  25. package/dist/lib/utils/pointer-utils.d.ts +2 -0
  26. package/dist/lib/utils/pointer-utils.js +43 -0
  27. package/dist/lib/utils/pointer-utils.mjs +19 -0
  28. package/dist/lib/validation/diagnostic-limits.d.mts +4 -0
  29. package/dist/lib/validation/diagnostic-limits.d.ts +4 -0
  30. package/dist/lib/validation/diagnostic-limits.js +57 -0
  31. package/dist/lib/validation/diagnostic-limits.mjs +32 -0
  32. package/dist/lib/validation/schema-errors.d.mts +3 -1
  33. package/dist/lib/validation/schema-errors.d.ts +3 -1
  34. package/dist/lib/validation/schema-errors.js +18 -3
  35. package/dist/lib/validation/schema-errors.mjs +18 -3
  36. package/dist/lib/validation/schema-validator.js +56 -4
  37. package/dist/lib/validation/schema-validator.mjs +56 -4
  38. package/dist/lib/version.d.mts +3 -3
  39. package/dist/lib/version.d.ts +3 -3
  40. package/dist/lib/version.js +3 -3
  41. package/dist/lib/version.mjs +3 -3
  42. package/docs/TYPE-SUMMARY.md +2 -2
  43. package/docs/llms.txt +2 -2
  44. package/docs/migration-14.x-rc-worksheet.md +4 -4
  45. package/package.json +2 -2
  46. package/skills/call-adcp-agent/SKILL.md +4 -1
@@ -132,6 +132,38 @@ function createReportingManifestInspector(options) {
132
132
  "maxSchemaCompileMs",
133
133
  6e4
134
134
  );
135
+ const maxManifestBytes = boundedPositiveInteger(
136
+ options.maxManifestBytes ?? DEFAULT_MAX_MANIFEST_BYTES,
137
+ "maxManifestBytes",
138
+ Number.MAX_SAFE_INTEGER
139
+ );
140
+ const maxObjectBytes = boundedPositiveInteger(
141
+ options.maxObjectBytes ?? DEFAULT_MAX_OBJECT_BYTES,
142
+ "maxObjectBytes",
143
+ Number.MAX_SAFE_INTEGER
144
+ );
145
+ const maxTotalBytes = boundedPositiveInteger(
146
+ options.maxTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES,
147
+ "maxTotalBytes",
148
+ Number.MAX_SAFE_INTEGER
149
+ );
150
+ const maxRows = boundedPositiveInteger(options.maxRows ?? DEFAULT_MAX_ROWS, "maxRows", Number.MAX_SAFE_INTEGER);
151
+ const maxFiles = boundedPositiveInteger(options.maxFiles ?? DEFAULT_MAX_FILES, "maxFiles", Number.MAX_SAFE_INTEGER);
152
+ const maxDecodedObjectBytes = boundedPositiveInteger(
153
+ options.maxDecodedObjectBytes ?? maxObjectBytes,
154
+ "maxDecodedObjectBytes",
155
+ Number.MAX_SAFE_INTEGER
156
+ );
157
+ const maxDecodedTotalBytes = boundedPositiveInteger(
158
+ options.maxDecodedTotalBytes ?? maxTotalBytes,
159
+ "maxDecodedTotalBytes",
160
+ Number.MAX_SAFE_INTEGER
161
+ );
162
+ const maxRowBytes = boundedPositiveInteger(
163
+ options.maxRowBytes ?? DEFAULT_MAX_ROW_BYTES,
164
+ "maxRowBytes",
165
+ Number.MAX_SAFE_INTEGER
166
+ );
135
167
  const resolver = options.referenceResolver ?? createCanonicalReferenceResolver();
136
168
  const formatDecoders = {
137
169
  jsonl: decodeJsonLines,
@@ -149,10 +181,15 @@ function createReportingManifestInspector(options) {
149
181
  if (!resource || resource.kind !== "manifest" || !resource.manifest_sha256 || resource.manifest_version !== "1.0") {
150
182
  throw inspectionError("UNSUPPORTED_RESOURCE", "Built-in inspection requires a v1 manifest resource");
151
183
  }
152
- const credential = options.credentialProvider ? await withinInspectionDeadline(options.credentialProvider.getCredentials(context), deadline) : void 0;
153
- const maxManifestBytes = options.maxManifestBytes ?? DEFAULT_MAX_MANIFEST_BYTES;
154
- const manifestRead = await withinInspectionDeadline(
155
- options.reader.read({
184
+ const credential = options.credentialProvider ? await invokeInspectionAdapter(
185
+ () => options.credentialProvider.getCredentials(context),
186
+ deadline,
187
+ "RESOURCE_READ_FAILED",
188
+ "Reporting credentials could not be resolved",
189
+ true
190
+ ) : void 0;
191
+ const manifestRead = await invokeInspectionAdapter(
192
+ () => options.reader.read({
156
193
  role: "manifest",
157
194
  location: resource.location,
158
195
  maxBytes: maxManifestBytes,
@@ -160,8 +197,14 @@ function createReportingManifestInspector(options) {
160
197
  context,
161
198
  signal: deadlineSignal(deadline)
162
199
  }),
163
- deadline
200
+ deadline,
201
+ "RESOURCE_READ_FAILED",
202
+ "Reporting manifest could not be read",
203
+ true
164
204
  );
205
+ if (!isRecord(manifestRead) || !(manifestRead.body instanceof Uint8Array)) {
206
+ throw inspectionError("RESOURCE_READ_FAILED", "Reporting manifest reader returned invalid bytes");
207
+ }
165
208
  if (manifestRead.body.byteLength > maxManifestBytes) {
166
209
  throw inspectionError("RESOURCE_TOO_LARGE", "Reporting manifest exceeds the configured byte budget");
167
210
  }
@@ -174,10 +217,7 @@ function createReportingManifestInspector(options) {
174
217
  }
175
218
  const manifest = parseManifest(manifestRead.body);
176
219
  validateManifestBinding(manifest, context);
177
- const maxObjectBytes = options.maxObjectBytes ?? DEFAULT_MAX_OBJECT_BYTES;
178
- const maxTotalBytes = options.maxTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES;
179
- const maxRows = options.maxRows ?? DEFAULT_MAX_ROWS;
180
- if (manifest.total_size_bytes > maxTotalBytes || manifest.files.some((file) => file.size_bytes > maxObjectBytes) || manifest.files.length > (options.maxFiles ?? DEFAULT_MAX_FILES) || manifest.row_count > maxRows) {
220
+ if (manifest.total_size_bytes > maxTotalBytes || manifest.files.some((file) => file.size_bytes > maxObjectBytes) || manifest.files.length > maxFiles || manifest.row_count > maxRows) {
181
221
  throw inspectionError(
182
222
  "RESOURCE_TOO_LARGE",
183
223
  "Manifest declares data beyond the configured inspection byte budget"
@@ -201,6 +241,9 @@ function createReportingManifestInspector(options) {
201
241
  const manifestObjectRefs = new Set(manifest.files.map((file) => file.object_ref));
202
242
  const evidenceKeys = /* @__PURE__ */ new Set();
203
243
  for (const checksum of context.materialization.verification?.physical_checksums ?? []) {
244
+ if (!isRecord(checksum) || !["sha256", "sha512"].includes(checksum.algorithm) || typeof checksum.value !== "string" || !(checksum.algorithm === "sha256" ? /^[a-fA-F0-9]{64}$/ : /^[a-fA-F0-9]{128}$/).test(checksum.value)) {
245
+ throw inspectionError("OBJECT_DIGEST_MISMATCH", "Producer checksum evidence is invalid");
246
+ }
204
247
  const key = `${checksum.object_ref}\0${checksum.algorithm}`;
205
248
  if (!manifestObjectRefs.has(checksum.object_ref) || evidenceKeys.has(key)) {
206
249
  throw inspectionError(
@@ -220,8 +263,8 @@ function createReportingManifestInspector(options) {
220
263
  totalBytes += file.size_bytes;
221
264
  if (totalBytes > maxTotalBytes) throw inspectionError("RESOURCE_TOO_LARGE", "Manifest exceeds total byte budget");
222
265
  assertInspectionDeadline(deadline);
223
- const read = await withinInspectionDeadline(
224
- options.reader.read({
266
+ const read = await invokeInspectionAdapter(
267
+ () => options.reader.read({
225
268
  role: "object",
226
269
  location: resource.location,
227
270
  objectRef: file.object_ref,
@@ -231,16 +274,19 @@ function createReportingManifestInspector(options) {
231
274
  context,
232
275
  signal: deadlineSignal(deadline)
233
276
  }),
234
- deadline
277
+ deadline,
278
+ "RESOURCE_READ_FAILED",
279
+ "Reporting object could not be read",
280
+ true
235
281
  );
282
+ if (!isRecord(read) || !(read.body instanceof Uint8Array)) {
283
+ throw inspectionError("RESOURCE_READ_FAILED", "Reporting object reader returned invalid bytes");
284
+ }
236
285
  if (read.body.byteLength !== file.size_bytes) {
237
- throw inspectionError("OBJECT_SIZE_MISMATCH", `Object ${file.object_ref} size does not match the manifest`);
286
+ throw inspectionError("OBJECT_SIZE_MISMATCH", "Object size does not match the manifest");
238
287
  }
239
288
  if (!constantTimeHexEqual(sha256(read.body), file.sha256)) {
240
- throw inspectionError(
241
- "OBJECT_DIGEST_MISMATCH",
242
- `Object ${file.object_ref} checksum does not match the manifest`
243
- );
289
+ throw inspectionError("OBJECT_DIGEST_MISMATCH", "Object checksum does not match the manifest");
244
290
  }
245
291
  for (const producerChecksum of context.materialization.verification?.physical_checksums?.filter(
246
292
  (checksum) => checksum.object_ref === file.object_ref
@@ -249,55 +295,55 @@ function createReportingManifestInspector(options) {
249
295
  if (!constantTimeHexEqual(producerChecksum.value, observed) || producerChecksum.algorithm === "sha256" && !constantTimeHexEqual(producerChecksum.value, file.sha256)) {
250
296
  throw inspectionError(
251
297
  "OBJECT_DIGEST_MISMATCH",
252
- `Producer checksum evidence for ${file.object_ref} does not match the delivered object`
298
+ "Producer checksum evidence does not match the delivered object"
253
299
  );
254
300
  }
255
301
  }
256
302
  let decoded;
257
303
  try {
258
- decoded = await withinInspectionDeadline(
259
- Promise.resolve(decompress(read.body, options.maxDecodedObjectBytes ?? maxObjectBytes)),
260
- deadline
304
+ decoded = await invokeInspectionAdapter(
305
+ () => decompress(read.body, maxDecodedObjectBytes),
306
+ deadline,
307
+ "MANIFEST_INVALID",
308
+ "Could not decode the compressed reporting object"
261
309
  );
262
310
  } catch (error) {
263
311
  if (error instanceof ReportingInspectionError) throw error;
264
- throw inspectionError(
265
- "MANIFEST_INVALID",
266
- `Could not decode ${manifest.compression} object ${file.object_ref}`,
267
- false,
268
- error
269
- );
312
+ throw inspectionError("MANIFEST_INVALID", "Could not decode the compressed reporting object", false, error);
270
313
  }
271
- if (decoded.byteLength > (options.maxDecodedObjectBytes ?? maxObjectBytes)) {
272
- throw inspectionError("RESOURCE_TOO_LARGE", `Decoded object ${file.object_ref} exceeds the byte budget`);
314
+ if (!(decoded instanceof Uint8Array)) {
315
+ throw inspectionError("MANIFEST_INVALID", "Reporting decompressor returned invalid bytes");
316
+ }
317
+ if (decoded.byteLength > maxDecodedObjectBytes) {
318
+ throw inspectionError("RESOURCE_TOO_LARGE", "Decoded reporting object exceeds the byte budget");
273
319
  }
274
320
  totalDecodedBytes += decoded.byteLength;
275
- if (totalDecodedBytes > (options.maxDecodedTotalBytes ?? maxTotalBytes)) {
321
+ if (totalDecodedBytes > maxDecodedTotalBytes) {
276
322
  throw inspectionError("RESOURCE_TOO_LARGE", "Decoded reporting data exceeds the aggregate byte budget");
277
323
  }
278
324
  let fileRows;
279
325
  try {
280
- fileRows = await withinInspectionDeadline(
281
- Promise.resolve(
282
- decoder(decoded, {
283
- format: manifest.format,
284
- objectRef: file.object_ref,
285
- rowSchema: schema,
286
- inspection: context,
287
- maxRows: maxRows - rows.length
288
- })
289
- ),
290
- deadline
326
+ fileRows = await invokeInspectionAdapter(
327
+ () => decoder(decoded, {
328
+ format: manifest.format,
329
+ objectRef: file.object_ref,
330
+ rowSchema: schema,
331
+ inspection: context,
332
+ maxRows: maxRows - rows.length
333
+ }),
334
+ deadline,
335
+ "MANIFEST_INVALID",
336
+ "Could not parse the reporting object"
291
337
  );
292
338
  } catch (error) {
293
339
  if (error instanceof ReportingInspectionError) throw error;
294
- throw inspectionError("MANIFEST_INVALID", `Could not parse ${manifest.format} object ${file.object_ref}`);
340
+ throw inspectionError("MANIFEST_INVALID", "Could not parse the reporting object");
341
+ }
342
+ if (!Array.isArray(fileRows) || !fileRows.every(isRecord)) {
343
+ throw inspectionError("MANIFEST_INVALID", "Reporting decoder returned invalid rows");
295
344
  }
296
345
  if (fileRows.length !== file.row_count) {
297
- throw inspectionError(
298
- "OBJECT_ROW_COUNT_MISMATCH",
299
- `Object ${file.object_ref} row count does not match the manifest`
300
- );
346
+ throw inspectionError("OBJECT_ROW_COUNT_MISMATCH", "Object row count does not match the manifest");
301
347
  }
302
348
  if (fileRows.length > maxRows - rows.length) {
303
349
  throw inspectionError("RESOURCE_TOO_LARGE", "Reporting row count exceeds the configured inspection budget");
@@ -308,7 +354,7 @@ function createReportingManifestInspector(options) {
308
354
  if (index % 256 === 0) assertInspectionDeadline(deadline);
309
355
  assertBoundedJsonData(rows[index], "ROW_SCHEMA_VIOLATION");
310
356
  assertIJson(rows[index]);
311
- if (Buffer.byteLength(canonicalize(rows[index])) > (options.maxRowBytes ?? DEFAULT_MAX_ROW_BYTES)) {
357
+ if (Buffer.byteLength(canonicalize(rows[index])) > maxRowBytes) {
312
358
  throw inspectionError("RESOURCE_TOO_LARGE", `Row ${index} exceeds the configured byte budget`);
313
359
  }
314
360
  if (!validateRow(rows[index])) {
@@ -323,10 +369,15 @@ function createReportingManifestInspector(options) {
323
369
  );
324
370
  }
325
371
  }
326
- const observedTotals = options.controlTotalCalculator ? await withinInspectionDeadline(
327
- Promise.resolve(options.controlTotalCalculator(rows, context.revision.control_totals, context)),
328
- deadline
372
+ const observedTotals = options.controlTotalCalculator ? await invokeInspectionAdapter(
373
+ () => options.controlTotalCalculator(rows, context.revision.control_totals, context),
374
+ deadline,
375
+ "CONTROL_TOTAL_UNSUPPORTED",
376
+ "Reporting control totals could not be calculated"
329
377
  ) : calculateNamedControlTotals(rows, context.revision.control_totals, reportDefinition, deadline);
378
+ if (!Array.isArray(observedTotals) || !observedTotals.every(isControlTotal)) {
379
+ throw inspectionError("CONTROL_TOTAL_UNSUPPORTED", "Reporting calculator returned invalid control totals");
380
+ }
330
381
  const expectedCanonicalDigest = context.revision.canonical_content_digest;
331
382
  let canonicalContentDigest;
332
383
  if (expectedCanonicalDigest && context.expected.verificationProfile === "canonical_digest") {
@@ -367,7 +418,18 @@ function createReportingManifestInspector(options) {
367
418
  );
368
419
  }
369
420
  }
370
- const commitRef = typeof options.consumerCommitRef === "function" ? options.consumerCommitRef(context) : options.consumerCommitRef;
421
+ assertInspectionDeadline(deadline);
422
+ const consumerCommitRef = options.consumerCommitRef;
423
+ const commitRef = typeof consumerCommitRef === "function" ? await invokeInspectionAdapter(
424
+ () => consumerCommitRef(context),
425
+ deadline,
426
+ "CONSUMER_COMMIT_FAILED",
427
+ "Reporting consumer commit reference could not be resolved"
428
+ ) : consumerCommitRef;
429
+ if (commitRef !== void 0 && typeof commitRef !== "string") {
430
+ throw inspectionError("CONSUMER_COMMIT_FAILED", "Reporting consumer commit reference is invalid");
431
+ }
432
+ assertInspectionDeadline(deadline);
371
433
  return {
372
434
  rowCount: rows.length,
373
435
  controlTotals: observedTotals,
@@ -379,17 +441,26 @@ function createReportingManifestInspector(options) {
379
441
  }
380
442
  async function resolveRowSchema(context, resolver, allowedOrigins, deadline) {
381
443
  assertAllowedReferenceOrigin(context.revision.schema_uri, allowedOrigins, "ROW_SCHEMA_FETCH_FAILED");
382
- const result = await withinInspectionDeadline(
383
- resolver.resolve({
444
+ const result = await invokeInspectionAdapter(
445
+ () => resolver.resolve({
384
446
  uri: context.revision.schema_uri,
385
447
  digest: `sha256:${context.revision.schema_sha256.toLowerCase()}`
386
448
  }),
387
- deadline
449
+ deadline,
450
+ "ROW_SCHEMA_FETCH_FAILED",
451
+ "Pinned row schema could not be resolved",
452
+ true
388
453
  );
454
+ assertReferenceResult(result, "ROW_SCHEMA_FETCH_FAILED");
389
455
  if (!result.ok) {
390
- throw new ReportingInspectionError("ROW_SCHEMA_FETCH_FAILED", result.error.message, result.error.retryable, {
391
- referenceCode: result.error.code
392
- });
456
+ throw new ReportingInspectionError(
457
+ "ROW_SCHEMA_FETCH_FAILED",
458
+ "Pinned row schema could not be resolved",
459
+ result.error.retryable,
460
+ {
461
+ referenceCode: result.error.code
462
+ }
463
+ );
393
464
  }
394
465
  requireContentType(result.contentType, ["application/schema+json", "application/json"], "ROW_SCHEMA_INVALID");
395
466
  const document = parsePinnedJson(result.body, "ROW_SCHEMA_INVALID");
@@ -457,7 +528,7 @@ function rejectReferenceCycles(schema, refs) {
457
528
  const edges = /* @__PURE__ */ new Map();
458
529
  for (const ref of refs) {
459
530
  const target = ref === "#" ? schema : ref.startsWith("#/") ? resolveJsonPointer(schema, ref.slice(1)) : anchors.get(ref.slice(1));
460
- if (target === void 0) throw inspectionError("ROW_SCHEMA_INVALID", `Row schema reference ${ref} is unresolved`);
531
+ if (target === void 0) throw inspectionError("ROW_SCHEMA_INVALID", "Row schema reference is unresolved");
461
532
  const childRefs = /* @__PURE__ */ new Set();
462
533
  collectRefs(target, childRefs);
463
534
  edges.set(ref, childRefs);
@@ -522,17 +593,26 @@ async function validateReportDefinition(context, resolver, allowedOrigins, deadl
522
593
  allowedOrigins,
523
594
  "REPORT_DEFINITION_FETCH_FAILED"
524
595
  );
525
- const result = await withinInspectionDeadline(
526
- resolver.resolve({
596
+ const result = await invokeInspectionAdapter(
597
+ () => resolver.resolve({
527
598
  uri: context.revision.report_definition_uri,
528
599
  digest: `sha256:${context.revision.report_definition_sha256.toLowerCase()}`
529
600
  }),
530
- deadline
601
+ deadline,
602
+ "REPORT_DEFINITION_FETCH_FAILED",
603
+ "Pinned report definition could not be resolved",
604
+ true
531
605
  );
606
+ assertReferenceResult(result, "REPORT_DEFINITION_FETCH_FAILED");
532
607
  if (!result.ok) {
533
- throw new ReportingInspectionError("REPORT_DEFINITION_FETCH_FAILED", result.error.message, result.error.retryable, {
534
- referenceCode: result.error.code
535
- });
608
+ throw new ReportingInspectionError(
609
+ "REPORT_DEFINITION_FETCH_FAILED",
610
+ "Pinned report definition could not be resolved",
611
+ result.error.retryable,
612
+ {
613
+ referenceCode: result.error.code
614
+ }
615
+ );
536
616
  }
537
617
  requireContentType(
538
618
  result.contentType,
@@ -565,17 +645,26 @@ async function validateReportDefinition(context, resolver, allowedOrigins, deadl
565
645
  async function recomputeCanonicalDigest(rows, context, resolver, allowedOrigins, deadline) {
566
646
  const expected = context.revision.canonical_content_digest;
567
647
  assertAllowedReferenceOrigin(expected.canonicalization_uri, allowedOrigins, "CANONICALIZATION_FETCH_FAILED");
568
- const result = await withinInspectionDeadline(
569
- resolver.resolve({
648
+ const result = await invokeInspectionAdapter(
649
+ () => resolver.resolve({
570
650
  uri: expected.canonicalization_uri,
571
651
  digest: `sha256:${expected.canonicalization_sha256.toLowerCase()}`
572
652
  }),
573
- deadline
653
+ deadline,
654
+ "CANONICALIZATION_FETCH_FAILED",
655
+ "Pinned canonicalization contract could not be resolved",
656
+ true
574
657
  );
658
+ assertReferenceResult(result, "CANONICALIZATION_FETCH_FAILED");
575
659
  if (!result.ok) {
576
- throw new ReportingInspectionError("CANONICALIZATION_FETCH_FAILED", result.error.message, result.error.retryable, {
577
- referenceCode: result.error.code
578
- });
660
+ throw new ReportingInspectionError(
661
+ "CANONICALIZATION_FETCH_FAILED",
662
+ "Pinned canonicalization contract could not be resolved",
663
+ result.error.retryable,
664
+ {
665
+ referenceCode: result.error.code
666
+ }
667
+ );
579
668
  }
580
669
  requireContentType(
581
670
  result.contentType,
@@ -595,6 +684,12 @@ async function recomputeCanonicalDigest(rows, context, resolver, allowedOrigins,
595
684
  for (const vector of contract.golden_vectors) {
596
685
  assertInspectionDeadline(deadline);
597
686
  const body = canonicalRows(vector.input_rows, contract.primary_keys, deadline);
687
+ if (vector.purpose === "ordering_encoding" && (Buffer.from(body).toString("utf8") === canonicalize(vector.input_rows) || !vector.input_rows.some((row) => JSON.stringify(row) !== canonicalize(row)))) {
688
+ throw inspectionError(
689
+ "CANONICALIZATION_INVALID",
690
+ "Ordering/encoding vector must exercise both row ordering and object member ordering"
691
+ );
692
+ }
598
693
  if (Buffer.from(body).toString("base64") !== vector.canonical_utf8_base64 || !constantTimeHexEqual(sha256(body), vector.sha256)) {
599
694
  throw inspectionError("CANONICALIZATION_INVALID", `Canonicalization golden vector ${vector.name} failed`);
600
695
  }
@@ -603,25 +698,41 @@ async function recomputeCanonicalDigest(rows, context, resolver, allowedOrigins,
603
698
  }
604
699
  function parseCanonicalizationContract(document, schemaSha256) {
605
700
  const allowed = ["contract_version", "media_type", "algorithm", "schema_sha256", "primary_keys", "golden_vectors"];
606
- if (!isRecord(document) || !hasOnlyKeys(document, allowed) || document.contract_version !== "1.0" || document.media_type !== "application/vnd.adcp.reporting-canonicalization+json" || document.algorithm !== "adcp_jcs_rows_v1" || typeof document.schema_sha256 !== "string" || !constantTimeHexEqual(document.schema_sha256, schemaSha256) || !Array.isArray(document.primary_keys) || document.primary_keys.length === 0 || !document.primary_keys.every((item) => typeof item === "string" && item.length > 0 && item.length <= 128) || new Set(document.primary_keys).size !== document.primary_keys.length || !Array.isArray(document.golden_vectors) || document.golden_vectors.length < 2) {
701
+ if (!isRecord(document) || !hasOnlyKeys(document, allowed) || document.contract_version !== "1.0" || document.media_type !== "application/vnd.adcp.reporting-canonicalization+json" || document.algorithm !== "adcp_jcs_rows_v1" || typeof document.schema_sha256 !== "string" || !constantTimeHexEqual(document.schema_sha256, schemaSha256) || !Array.isArray(document.primary_keys) || document.primary_keys.length === 0 || !document.primary_keys.every((item) => typeof item === "string" && item.length > 0 && item.length <= 128) || new Set(document.primary_keys).size !== document.primary_keys.length) {
607
702
  throw inspectionError(
608
703
  "CANONICALIZATION_INVALID",
609
704
  "Pinned canonicalization contract is invalid or not bound to the row schema"
610
705
  );
611
706
  }
612
- for (const vector of document.golden_vectors) {
613
- if (!isRecord(vector) || !hasOnlyKeys(vector, ["name", "input_rows", "canonical_utf8_base64", "sha256"]) || typeof vector.name !== "string" || !/^[A-Za-z0-9_.:-]{1,128}$/.test(vector.name) || !Array.isArray(vector.input_rows) || !vector.input_rows.every(isRecord) || typeof vector.canonical_utf8_base64 !== "string" || !isCanonicalBase64(vector.canonical_utf8_base64) || typeof vector.sha256 !== "string" || !/^[a-fA-F0-9]{64}$/.test(vector.sha256)) {
707
+ const named = !Array.isArray(document.golden_vectors);
708
+ let vectors;
709
+ if (named) {
710
+ const golden = document.golden_vectors;
711
+ if (!isRecord(golden) || !hasOnlyKeys(golden, ["empty_report", "ordering_encoding", "additional"]) || !isRecord(golden.empty_report) || golden.empty_report.purpose !== "empty_report" || !Array.isArray(golden.empty_report.input_rows) || golden.empty_report.input_rows.length !== 0 || !isRecord(golden.ordering_encoding) || golden.ordering_encoding.purpose !== "ordering_encoding" || !Array.isArray(golden.ordering_encoding.input_rows) || golden.ordering_encoding.input_rows.length < 2 || golden.additional !== void 0 && (!Array.isArray(golden.additional) || !golden.additional.every((vector) => isRecord(vector) && vector.purpose === "additional"))) {
712
+ throw inspectionError("CANONICALIZATION_INVALID", "Canonicalization contract contains invalid named vectors");
713
+ }
714
+ vectors = [golden.empty_report, golden.ordering_encoding, ...golden.additional ?? []];
715
+ } else {
716
+ vectors = document.golden_vectors;
717
+ }
718
+ if (vectors.length < 2) {
719
+ throw inspectionError("CANONICALIZATION_INVALID", "Canonicalization contract requires at least two golden vectors");
720
+ }
721
+ const names = /* @__PURE__ */ new Set();
722
+ for (const vector of vectors) {
723
+ if (!isRecord(vector) || !hasOnlyKeys(vector, ["name", "input_rows", "canonical_utf8_base64", "sha256", ...named ? ["purpose"] : []]) || typeof vector.name !== "string" || !/^[A-Za-z0-9_.:-]{1,128}$/.test(vector.name) || !Array.isArray(vector.input_rows) || !vector.input_rows.every(isRecord) || typeof vector.canonical_utf8_base64 !== "string" || !isCanonicalBase64(vector.canonical_utf8_base64) || typeof vector.sha256 !== "string" || !/^[a-fA-F0-9]{64}$/.test(vector.sha256) || names.has(vector.name)) {
614
724
  throw inspectionError("CANONICALIZATION_INVALID", "Canonicalization contract contains an invalid golden vector");
615
725
  }
726
+ names.add(vector.name);
616
727
  assertBoundedJsonData(vector.input_rows, "CANONICALIZATION_INVALID");
617
728
  }
618
- if (!document.golden_vectors.some((vector) => vector.input_rows.length === 0) || !document.golden_vectors.some((vector) => vector.input_rows.length >= 2)) {
729
+ if (!vectors.some((vector) => isRecord(vector) && vector.input_rows.length === 0) || !vectors.some((vector) => isRecord(vector) && vector.input_rows.length >= 2)) {
619
730
  throw inspectionError(
620
731
  "CANONICALIZATION_INVALID",
621
732
  "Canonicalization vectors must include empty-report and ordering/encoding cases"
622
733
  );
623
734
  }
624
- return document;
735
+ return { ...document, golden_vectors: vectors };
625
736
  }
626
737
  function canonicalRows(rows, primaryKeys, deadline) {
627
738
  const encoded = rows.map((row, index) => {
@@ -644,6 +755,11 @@ function canonicalRows(rows, primaryKeys, deadline) {
644
755
  }
645
756
  return Buffer.from(`[${encoded.map((item) => item.row).join(",")}]`, "utf8");
646
757
  }
758
+ function assertReferenceResult(value, code) {
759
+ if (!isRecord(value) || value.ok !== true && value.ok !== false || (value.ok ? !(value.body instanceof Uint8Array) : !isRecord(value.error) || typeof value.error.retryable !== "boolean")) {
760
+ throw inspectionError(code, "Pinned reference resolver returned an invalid result");
761
+ }
762
+ }
647
763
  function parsePinnedJson(body, code) {
648
764
  try {
649
765
  return parseStrictJson(new TextDecoder("utf-8", { fatal: true }).decode(body));
@@ -799,6 +915,9 @@ function calculateNamedControlTotals(rows, expected, reportDefinition, deadline)
799
915
  `Control total ${total.name} needs a custom calculator for its declared aggregation`
800
916
  );
801
917
  }
918
+ if (total.unit !== void 0 && metric.unit !== void 0 && metric.unit !== total.unit) {
919
+ throw inspectionError("CONTROL_TOTAL_MISMATCH", "Control-total unit does not match the pinned report definition");
920
+ }
802
921
  let sum = { coefficient: 0n, scale: 0 };
803
922
  for (let index = 0; index < rows.length; index += 1) {
804
923
  if (index % 256 === 0) assertInspectionDeadline(deadline);
@@ -832,6 +951,8 @@ function deadlineSignal(deadline) {
832
951
  return AbortSignal.timeout(Math.max(1, deadline - Date.now()));
833
952
  }
834
953
  async function withinInspectionDeadline(operation, deadline) {
954
+ void operation.catch(() => {
955
+ });
835
956
  assertInspectionDeadline(deadline);
836
957
  const remaining = deadline - Date.now();
837
958
  let timer;
@@ -855,7 +976,7 @@ function parseDecimal(value) {
855
976
  throw inspectionError("CONTROL_TOTAL_UNSUPPORTED", "Control-total decimal exceeds the precision bound");
856
977
  }
857
978
  const match = /^(-?)(\d+)(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/.exec(text);
858
- if (!match) throw inspectionError("CONTROL_TOTAL_UNSUPPORTED", `Non-decimal control value ${text}`);
979
+ if (!match) throw inspectionError("CONTROL_TOTAL_UNSUPPORTED", "Control-total value is not a decimal");
859
980
  const fraction = match[3] ?? "";
860
981
  const exponent = Number(match[4] ?? 0);
861
982
  if (!Number.isSafeInteger(exponent) || Math.abs(exponent) > 1024 || match[2].length + fraction.length > 1024) {
@@ -913,7 +1034,7 @@ function assertIJson(value, seen = /* @__PURE__ */ new Set()) {
913
1034
  const code = value.charCodeAt(index);
914
1035
  if (code >= 55296 && code <= 56319) {
915
1036
  const next = value.charCodeAt(index + 1);
916
- if (next < 56320 || next > 57343)
1037
+ if (!Number.isFinite(next) || next < 56320 || next > 57343)
917
1038
  throw inspectionError("CANONICALIZATION_INVALID", "Lone Unicode surrogate is not I-JSON");
918
1039
  index += 1;
919
1040
  } else if (code >= 56320 && code <= 57343) {
@@ -922,9 +1043,13 @@ function assertIJson(value, seen = /* @__PURE__ */ new Set()) {
922
1043
  }
923
1044
  return;
924
1045
  }
925
- if (typeof value === "number" && !Number.isFinite(value))
926
- throw inspectionError("CANONICALIZATION_INVALID", "Non-finite number is not I-JSON");
927
- if (!value || typeof value !== "object") return;
1046
+ if (value === null || typeof value === "boolean") return;
1047
+ if (typeof value === "number") {
1048
+ if (!Number.isFinite(value)) throw inspectionError("CANONICALIZATION_INVALID", "Non-finite number is not I-JSON");
1049
+ return;
1050
+ }
1051
+ if (typeof value !== "object" || !Array.isArray(value) && !isRecord(value))
1052
+ throw inspectionError("CANONICALIZATION_INVALID", "Reporting values must be JSON data");
928
1053
  if (seen.has(value)) throw inspectionError("CANONICALIZATION_INVALID", "Cyclic row is not JSON");
929
1054
  seen.add(value);
930
1055
  if (Array.isArray(value)) value.forEach((child) => assertIJson(child, seen));
@@ -963,7 +1088,7 @@ function isValidReportDefinition(value) {
963
1088
  }
964
1089
  if (!Array.isArray(value.metrics) || value.metrics.length === 0 || !value.metrics.every(
965
1090
  (metric) => isRecord(metric) && hasOnlyKeys(metric, ["name", "source_expression", "aggregation", "unit"]) && typeof metric.name === "string" && metric.name.length > 0 && metric.name.length <= 128 && typeof metric.source_expression === "string" && metric.source_expression.length > 0 && metric.source_expression.length <= 2048 && ["sum", "count", "min", "max", "average", "ratio", "last", "custom"].includes(String(metric.aggregation)) && (metric.unit === void 0 || typeof metric.unit === "string" && metric.unit.length > 0 && metric.unit.length <= 64)
966
- ) || !Array.isArray(value.dimensions) || !value.dimensions.every(
1091
+ ) || new Set(value.metrics.map((metric) => metric.name)).size !== value.metrics.length || !Array.isArray(value.dimensions) || !value.dimensions.every(
967
1092
  (dimension) => typeof dimension === "string" && dimension.length > 0 && dimension.length <= 128
968
1093
  ) || new Set(value.dimensions).size !== value.dimensions.length) {
969
1094
  return false;
@@ -1059,7 +1184,7 @@ function constantTimeHexEqual(left, right) {
1059
1184
  function requireContentType(raw, allowed, code) {
1060
1185
  const mediaType = raw?.split(";", 1)[0]?.trim().toLowerCase();
1061
1186
  if (!mediaType || !allowed.includes(mediaType)) {
1062
- throw inspectionError(code, `Pinned document returned unsupported content type ${mediaType ?? "<missing>"}`);
1187
+ throw inspectionError(code, "Pinned document returned an unsupported content type");
1063
1188
  }
1064
1189
  }
1065
1190
  function assertAllowedReferenceOrigin(raw, allowedOrigins, code) {
@@ -1081,8 +1206,28 @@ function sanitizeAjvErrors(errors) {
1081
1206
  message: error.message
1082
1207
  }));
1083
1208
  }
1084
- function inspectionError(code, message, retryable = false, cause) {
1085
- return new ReportingInspectionError(code, message, retryable, void 0, cause === void 0 ? void 0 : { cause });
1209
+ function inspectionError(code, message, retryable = false, _cause) {
1210
+ return new ReportingInspectionError(code, message, retryable);
1211
+ }
1212
+ async function invokeInspectionAdapter(callback, deadline, code, message, retryable = false) {
1213
+ try {
1214
+ assertInspectionDeadline(deadline);
1215
+ const result = await withinInspectionDeadline(
1216
+ Promise.resolve().then(() => {
1217
+ assertInspectionDeadline(deadline);
1218
+ return callback();
1219
+ }),
1220
+ deadline
1221
+ );
1222
+ assertInspectionDeadline(deadline);
1223
+ return result;
1224
+ } catch (error) {
1225
+ throw error instanceof ReportingInspectionError ? new ReportingInspectionError(
1226
+ error.code,
1227
+ error.code === "INSPECTION_TIMEOUT" ? "Reporting inspection exceeded its configured deadline" : message,
1228
+ error.retryable
1229
+ ) : inspectionError(code, message, retryable);
1230
+ }
1086
1231
  }
1087
1232
  function inspectionMismatch(code, message, observation) {
1088
1233
  return new ReportingInspectionError(code, message, false, void 0, void 0, observation);
@@ -30,6 +30,7 @@ var import_error_codes = require('../../types/error-codes.js');
30
30
  var import_sync_reporting_status_envelope = require('../../validation/sync-reporting-status-envelope.js');
31
31
  var import_version = require('../../version.js');
32
32
  var import_well_formed_unicode = require('../../utils/well-formed-unicode.js');
33
+ var import_pointer_utils = require('../../utils/pointer-utils.js');
33
34
  var import_coverage = require('./coverage.js');
34
35
  var import_handler = require('./handler.js');
35
36
  var import_schedule = require('./schedule.js');
@@ -491,30 +492,13 @@ function wireValidationDiagnostic(pointer, message, keyword) {
491
492
  return {};
492
493
  }
493
494
  if (!pointer.startsWith("/")) return { field: pointer };
494
- const field = jsonPointerToJsonPathLite(pointer);
495
- if (!field) return {};
495
+ const field = (0, import_pointer_utils.jsonPointerToJsonPathLite)(pointer);
496
+ if (!field || Buffer.byteLength(field, "utf8") > import_types.REPORTING_CONSUMER_STATUS_ERROR_FIELD_MAX_BYTES) return {};
496
497
  return {
497
498
  field,
498
499
  ...keyword ? { issues: [{ pointer, message, keyword }] } : {}
499
500
  };
500
501
  }
501
- function jsonPointerToJsonPathLite(pointer) {
502
- if (!pointer.startsWith("/")) return void 0;
503
- if (pointer === "/") return "$";
504
- let field = "";
505
- for (const encoded of pointer.slice(1).split("/")) {
506
- const segment = encoded.replace(/~1/g, "/").replace(/~0/g, "~");
507
- if (/^(0|[1-9][0-9]*)$/.test(segment)) {
508
- if (!field) return void 0;
509
- field += `[${segment}]`;
510
- } else if (/^[A-Za-z_][A-Za-z0-9_-]*$/.test(segment)) {
511
- field += field ? `.${segment}` : segment;
512
- } else {
513
- field += `${field ? "" : "$"}[${JSON.stringify(segment)}]`;
514
- }
515
- }
516
- return Buffer.byteLength(field, "utf8") <= import_types.REPORTING_CONSUMER_STATUS_ERROR_FIELD_MAX_BYTES ? field : void 0;
517
- }
518
502
  function zodIssueKeyword(issue) {
519
503
  switch (issue?.code) {
520
504
  case "invalid_type":
@@ -5,6 +5,7 @@ import { DEFAULT_UNKNOWN_ERROR_RECOVERY, getErrorRecovery } from "../../types/er
5
5
  import { validateSyncReportingStatusEnvelope } from "../../validation/sync-reporting-status-envelope.mjs";
6
6
  import { ADCP_MAJOR_VERSION, ADCP_VERSION, toReleasePrecisionVersion } from "../../version.mjs";
7
7
  import { isWellFormedUnicodeString } from "../../utils/well-formed-unicode.mjs";
8
+ import { jsonPointerToJsonPathLite } from "../../utils/pointer-utils.mjs";
8
9
  import {
9
10
  reportingLedgerConfigurationMatchesScope,
10
11
  reportingLedgerEffectivePeriod,
@@ -483,29 +484,12 @@ function wireValidationDiagnostic(pointer, message, keyword) {
483
484
  }
484
485
  if (!pointer.startsWith("/")) return { field: pointer };
485
486
  const field = jsonPointerToJsonPathLite(pointer);
486
- if (!field) return {};
487
+ if (!field || Buffer.byteLength(field, "utf8") > REPORTING_CONSUMER_STATUS_ERROR_FIELD_MAX_BYTES) return {};
487
488
  return {
488
489
  field,
489
490
  ...keyword ? { issues: [{ pointer, message, keyword }] } : {}
490
491
  };
491
492
  }
492
- function jsonPointerToJsonPathLite(pointer) {
493
- if (!pointer.startsWith("/")) return void 0;
494
- if (pointer === "/") return "$";
495
- let field = "";
496
- for (const encoded of pointer.slice(1).split("/")) {
497
- const segment = encoded.replace(/~1/g, "/").replace(/~0/g, "~");
498
- if (/^(0|[1-9][0-9]*)$/.test(segment)) {
499
- if (!field) return void 0;
500
- field += `[${segment}]`;
501
- } else if (/^[A-Za-z_][A-Za-z0-9_-]*$/.test(segment)) {
502
- field += field ? `.${segment}` : segment;
503
- } else {
504
- field += `${field ? "" : "$"}[${JSON.stringify(segment)}]`;
505
- }
506
- }
507
- return Buffer.byteLength(field, "utf8") <= REPORTING_CONSUMER_STATUS_ERROR_FIELD_MAX_BYTES ? field : void 0;
508
- }
509
493
  function zodIssueKeyword(issue) {
510
494
  switch (issue?.code) {
511
495
  case "invalid_type":
@@ -4,5 +4,5 @@
4
4
  "source_sha": "4e553ad955f83b49c7d221ab5c3ff78237ad02e3",
5
5
  "source_tarball_sha256": "580656d6466ef9f0d1119985e6726c2efea718dc671e2ad30957fcb2fd54af0f",
6
6
  "upstream_adcp_version": "2.5.3",
7
- "synced_at": "2026-10-05T13:04:22.439Z"
7
+ "synced_at": "2026-10-06T17:21:37.513Z"
8
8
  }