@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
@@ -2,7 +2,7 @@ import { type CanonicalReferenceResolver } from '../canonical-references/index.m
2
2
  import { type SsrfFetchOptions } from '../net/ssrf-fetch.mjs';
3
3
  import type { ReportingControlTotal } from '../types/tools.generated.mjs';
4
4
  import type { ReportingInspectionContext, ReportingObservation } from './reconciliation.mjs';
5
- export type ReportingInspectionErrorCode = 'UNSUPPORTED_RESOURCE' | 'RESOURCE_READ_FAILED' | 'RESOURCE_NOT_READY' | 'RESOURCE_TOO_LARGE' | 'INSPECTION_TIMEOUT' | 'MANIFEST_DIGEST_MISMATCH' | 'MANIFEST_INVALID' | 'MANIFEST_INCOMPLETE' | 'MANIFEST_IDENTITY_MISMATCH' | 'MANIFEST_TOTAL_MISMATCH' | 'OBJECT_DUPLICATE' | 'OBJECT_SIZE_MISMATCH' | 'OBJECT_DIGEST_MISMATCH' | 'OBJECT_ROW_COUNT_MISMATCH' | 'FORMAT_UNSUPPORTED' | 'COMPRESSION_UNSUPPORTED' | 'ROW_SCHEMA_FETCH_FAILED' | 'ROW_SCHEMA_INVALID' | 'ROW_SCHEMA_VIOLATION' | 'REPORT_DEFINITION_FETCH_FAILED' | 'REPORT_DEFINITION_INVALID' | 'CANONICALIZATION_FETCH_FAILED' | 'CANONICALIZATION_INVALID' | 'ROW_COUNT_MISMATCH' | 'CONTROL_TOTAL_UNSUPPORTED' | 'CONTROL_TOTAL_MISMATCH' | 'CANONICAL_DIGEST_MISMATCH';
5
+ export type ReportingInspectionErrorCode = 'CONSUMER_COMMIT_FAILED' | 'UNSUPPORTED_RESOURCE' | 'RESOURCE_READ_FAILED' | 'RESOURCE_NOT_READY' | 'RESOURCE_TOO_LARGE' | 'INSPECTION_TIMEOUT' | 'MANIFEST_DIGEST_MISMATCH' | 'MANIFEST_INVALID' | 'MANIFEST_INCOMPLETE' | 'MANIFEST_IDENTITY_MISMATCH' | 'MANIFEST_TOTAL_MISMATCH' | 'OBJECT_DUPLICATE' | 'OBJECT_SIZE_MISMATCH' | 'OBJECT_DIGEST_MISMATCH' | 'OBJECT_ROW_COUNT_MISMATCH' | 'FORMAT_UNSUPPORTED' | 'COMPRESSION_UNSUPPORTED' | 'ROW_SCHEMA_FETCH_FAILED' | 'ROW_SCHEMA_INVALID' | 'ROW_SCHEMA_VIOLATION' | 'REPORT_DEFINITION_FETCH_FAILED' | 'REPORT_DEFINITION_INVALID' | 'CANONICALIZATION_FETCH_FAILED' | 'CANONICALIZATION_INVALID' | 'ROW_COUNT_MISMATCH' | 'CONTROL_TOTAL_UNSUPPORTED' | 'CONTROL_TOTAL_MISMATCH' | 'CANONICAL_DIGEST_MISMATCH';
6
6
  export declare class ReportingInspectionError extends Error {
7
7
  readonly code: ReportingInspectionErrorCode;
8
8
  readonly retryable: boolean;
@@ -78,9 +78,9 @@ export interface ReportingManifestInspectorOptions<TCredential = unknown> {
78
78
  maxFiles?: number;
79
79
  maxRows?: number;
80
80
  maxRowBytes?: number;
81
- /** Hard wall-clock budget for one inspection attempt, including custom adapters. */
81
+ /** Aggregate deadline for async work, checked cooperatively during synchronous work. Synchronous callbacks cannot be preempted. */
82
82
  maxInspectionMs?: number;
83
- /** Maximum wall-clock time permitted for synchronous row-schema compilation. */
83
+ /** Reject synchronous schema compilation overruns after compilation returns. */
84
84
  maxSchemaCompileMs?: number;
85
85
  }
86
86
  /** Build the SDK-managed manifest/file inspector used by reconcileReporting. */
@@ -2,7 +2,7 @@ import { type CanonicalReferenceResolver } from '../canonical-references';
2
2
  import { type SsrfFetchOptions } from '../net/ssrf-fetch';
3
3
  import type { ReportingControlTotal } from '../types/tools.generated';
4
4
  import type { ReportingInspectionContext, ReportingObservation } from './reconciliation';
5
- export type ReportingInspectionErrorCode = 'UNSUPPORTED_RESOURCE' | 'RESOURCE_READ_FAILED' | 'RESOURCE_NOT_READY' | 'RESOURCE_TOO_LARGE' | 'INSPECTION_TIMEOUT' | 'MANIFEST_DIGEST_MISMATCH' | 'MANIFEST_INVALID' | 'MANIFEST_INCOMPLETE' | 'MANIFEST_IDENTITY_MISMATCH' | 'MANIFEST_TOTAL_MISMATCH' | 'OBJECT_DUPLICATE' | 'OBJECT_SIZE_MISMATCH' | 'OBJECT_DIGEST_MISMATCH' | 'OBJECT_ROW_COUNT_MISMATCH' | 'FORMAT_UNSUPPORTED' | 'COMPRESSION_UNSUPPORTED' | 'ROW_SCHEMA_FETCH_FAILED' | 'ROW_SCHEMA_INVALID' | 'ROW_SCHEMA_VIOLATION' | 'REPORT_DEFINITION_FETCH_FAILED' | 'REPORT_DEFINITION_INVALID' | 'CANONICALIZATION_FETCH_FAILED' | 'CANONICALIZATION_INVALID' | 'ROW_COUNT_MISMATCH' | 'CONTROL_TOTAL_UNSUPPORTED' | 'CONTROL_TOTAL_MISMATCH' | 'CANONICAL_DIGEST_MISMATCH';
5
+ export type ReportingInspectionErrorCode = 'CONSUMER_COMMIT_FAILED' | 'UNSUPPORTED_RESOURCE' | 'RESOURCE_READ_FAILED' | 'RESOURCE_NOT_READY' | 'RESOURCE_TOO_LARGE' | 'INSPECTION_TIMEOUT' | 'MANIFEST_DIGEST_MISMATCH' | 'MANIFEST_INVALID' | 'MANIFEST_INCOMPLETE' | 'MANIFEST_IDENTITY_MISMATCH' | 'MANIFEST_TOTAL_MISMATCH' | 'OBJECT_DUPLICATE' | 'OBJECT_SIZE_MISMATCH' | 'OBJECT_DIGEST_MISMATCH' | 'OBJECT_ROW_COUNT_MISMATCH' | 'FORMAT_UNSUPPORTED' | 'COMPRESSION_UNSUPPORTED' | 'ROW_SCHEMA_FETCH_FAILED' | 'ROW_SCHEMA_INVALID' | 'ROW_SCHEMA_VIOLATION' | 'REPORT_DEFINITION_FETCH_FAILED' | 'REPORT_DEFINITION_INVALID' | 'CANONICALIZATION_FETCH_FAILED' | 'CANONICALIZATION_INVALID' | 'ROW_COUNT_MISMATCH' | 'CONTROL_TOTAL_UNSUPPORTED' | 'CONTROL_TOTAL_MISMATCH' | 'CANONICAL_DIGEST_MISMATCH';
6
6
  export declare class ReportingInspectionError extends Error {
7
7
  readonly code: ReportingInspectionErrorCode;
8
8
  readonly retryable: boolean;
@@ -78,9 +78,9 @@ export interface ReportingManifestInspectorOptions<TCredential = unknown> {
78
78
  maxFiles?: number;
79
79
  maxRows?: number;
80
80
  maxRowBytes?: number;
81
- /** Hard wall-clock budget for one inspection attempt, including custom adapters. */
81
+ /** Aggregate deadline for async work, checked cooperatively during synchronous work. Synchronous callbacks cannot be preempted. */
82
82
  maxInspectionMs?: number;
83
- /** Maximum wall-clock time permitted for synchronous row-schema compilation. */
83
+ /** Reject synchronous schema compilation overruns after compilation returns. */
84
84
  maxSchemaCompileMs?: number;
85
85
  }
86
86
  /** Build the SDK-managed manifest/file inspector used by reconcileReporting. */
@@ -162,6 +162,38 @@ function createReportingManifestInspector(options) {
162
162
  "maxSchemaCompileMs",
163
163
  6e4
164
164
  );
165
+ const maxManifestBytes = boundedPositiveInteger(
166
+ options.maxManifestBytes ?? DEFAULT_MAX_MANIFEST_BYTES,
167
+ "maxManifestBytes",
168
+ Number.MAX_SAFE_INTEGER
169
+ );
170
+ const maxObjectBytes = boundedPositiveInteger(
171
+ options.maxObjectBytes ?? DEFAULT_MAX_OBJECT_BYTES,
172
+ "maxObjectBytes",
173
+ Number.MAX_SAFE_INTEGER
174
+ );
175
+ const maxTotalBytes = boundedPositiveInteger(
176
+ options.maxTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES,
177
+ "maxTotalBytes",
178
+ Number.MAX_SAFE_INTEGER
179
+ );
180
+ const maxRows = boundedPositiveInteger(options.maxRows ?? DEFAULT_MAX_ROWS, "maxRows", Number.MAX_SAFE_INTEGER);
181
+ const maxFiles = boundedPositiveInteger(options.maxFiles ?? DEFAULT_MAX_FILES, "maxFiles", Number.MAX_SAFE_INTEGER);
182
+ const maxDecodedObjectBytes = boundedPositiveInteger(
183
+ options.maxDecodedObjectBytes ?? maxObjectBytes,
184
+ "maxDecodedObjectBytes",
185
+ Number.MAX_SAFE_INTEGER
186
+ );
187
+ const maxDecodedTotalBytes = boundedPositiveInteger(
188
+ options.maxDecodedTotalBytes ?? maxTotalBytes,
189
+ "maxDecodedTotalBytes",
190
+ Number.MAX_SAFE_INTEGER
191
+ );
192
+ const maxRowBytes = boundedPositiveInteger(
193
+ options.maxRowBytes ?? DEFAULT_MAX_ROW_BYTES,
194
+ "maxRowBytes",
195
+ Number.MAX_SAFE_INTEGER
196
+ );
165
197
  const resolver = options.referenceResolver ?? (0, import_canonical_references.createCanonicalReferenceResolver)();
166
198
  const formatDecoders = {
167
199
  jsonl: decodeJsonLines,
@@ -179,10 +211,15 @@ function createReportingManifestInspector(options) {
179
211
  if (!resource || resource.kind !== "manifest" || !resource.manifest_sha256 || resource.manifest_version !== "1.0") {
180
212
  throw inspectionError("UNSUPPORTED_RESOURCE", "Built-in inspection requires a v1 manifest resource");
181
213
  }
182
- const credential = options.credentialProvider ? await withinInspectionDeadline(options.credentialProvider.getCredentials(context), deadline) : void 0;
183
- const maxManifestBytes = options.maxManifestBytes ?? DEFAULT_MAX_MANIFEST_BYTES;
184
- const manifestRead = await withinInspectionDeadline(
185
- options.reader.read({
214
+ const credential = options.credentialProvider ? await invokeInspectionAdapter(
215
+ () => options.credentialProvider.getCredentials(context),
216
+ deadline,
217
+ "RESOURCE_READ_FAILED",
218
+ "Reporting credentials could not be resolved",
219
+ true
220
+ ) : void 0;
221
+ const manifestRead = await invokeInspectionAdapter(
222
+ () => options.reader.read({
186
223
  role: "manifest",
187
224
  location: resource.location,
188
225
  maxBytes: maxManifestBytes,
@@ -190,8 +227,14 @@ function createReportingManifestInspector(options) {
190
227
  context,
191
228
  signal: deadlineSignal(deadline)
192
229
  }),
193
- deadline
230
+ deadline,
231
+ "RESOURCE_READ_FAILED",
232
+ "Reporting manifest could not be read",
233
+ true
194
234
  );
235
+ if (!isRecord(manifestRead) || !(manifestRead.body instanceof Uint8Array)) {
236
+ throw inspectionError("RESOURCE_READ_FAILED", "Reporting manifest reader returned invalid bytes");
237
+ }
195
238
  if (manifestRead.body.byteLength > maxManifestBytes) {
196
239
  throw inspectionError("RESOURCE_TOO_LARGE", "Reporting manifest exceeds the configured byte budget");
197
240
  }
@@ -204,10 +247,7 @@ function createReportingManifestInspector(options) {
204
247
  }
205
248
  const manifest = parseManifest(manifestRead.body);
206
249
  validateManifestBinding(manifest, context);
207
- const maxObjectBytes = options.maxObjectBytes ?? DEFAULT_MAX_OBJECT_BYTES;
208
- const maxTotalBytes = options.maxTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES;
209
- const maxRows = options.maxRows ?? DEFAULT_MAX_ROWS;
210
- 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) {
250
+ if (manifest.total_size_bytes > maxTotalBytes || manifest.files.some((file) => file.size_bytes > maxObjectBytes) || manifest.files.length > maxFiles || manifest.row_count > maxRows) {
211
251
  throw inspectionError(
212
252
  "RESOURCE_TOO_LARGE",
213
253
  "Manifest declares data beyond the configured inspection byte budget"
@@ -231,6 +271,9 @@ function createReportingManifestInspector(options) {
231
271
  const manifestObjectRefs = new Set(manifest.files.map((file) => file.object_ref));
232
272
  const evidenceKeys = /* @__PURE__ */ new Set();
233
273
  for (const checksum of context.materialization.verification?.physical_checksums ?? []) {
274
+ 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)) {
275
+ throw inspectionError("OBJECT_DIGEST_MISMATCH", "Producer checksum evidence is invalid");
276
+ }
234
277
  const key = `${checksum.object_ref}\0${checksum.algorithm}`;
235
278
  if (!manifestObjectRefs.has(checksum.object_ref) || evidenceKeys.has(key)) {
236
279
  throw inspectionError(
@@ -250,8 +293,8 @@ function createReportingManifestInspector(options) {
250
293
  totalBytes += file.size_bytes;
251
294
  if (totalBytes > maxTotalBytes) throw inspectionError("RESOURCE_TOO_LARGE", "Manifest exceeds total byte budget");
252
295
  assertInspectionDeadline(deadline);
253
- const read = await withinInspectionDeadline(
254
- options.reader.read({
296
+ const read = await invokeInspectionAdapter(
297
+ () => options.reader.read({
255
298
  role: "object",
256
299
  location: resource.location,
257
300
  objectRef: file.object_ref,
@@ -261,16 +304,19 @@ function createReportingManifestInspector(options) {
261
304
  context,
262
305
  signal: deadlineSignal(deadline)
263
306
  }),
264
- deadline
307
+ deadline,
308
+ "RESOURCE_READ_FAILED",
309
+ "Reporting object could not be read",
310
+ true
265
311
  );
312
+ if (!isRecord(read) || !(read.body instanceof Uint8Array)) {
313
+ throw inspectionError("RESOURCE_READ_FAILED", "Reporting object reader returned invalid bytes");
314
+ }
266
315
  if (read.body.byteLength !== file.size_bytes) {
267
- throw inspectionError("OBJECT_SIZE_MISMATCH", `Object ${file.object_ref} size does not match the manifest`);
316
+ throw inspectionError("OBJECT_SIZE_MISMATCH", "Object size does not match the manifest");
268
317
  }
269
318
  if (!constantTimeHexEqual(sha256(read.body), file.sha256)) {
270
- throw inspectionError(
271
- "OBJECT_DIGEST_MISMATCH",
272
- `Object ${file.object_ref} checksum does not match the manifest`
273
- );
319
+ throw inspectionError("OBJECT_DIGEST_MISMATCH", "Object checksum does not match the manifest");
274
320
  }
275
321
  for (const producerChecksum of context.materialization.verification?.physical_checksums?.filter(
276
322
  (checksum) => checksum.object_ref === file.object_ref
@@ -279,55 +325,55 @@ function createReportingManifestInspector(options) {
279
325
  if (!constantTimeHexEqual(producerChecksum.value, observed) || producerChecksum.algorithm === "sha256" && !constantTimeHexEqual(producerChecksum.value, file.sha256)) {
280
326
  throw inspectionError(
281
327
  "OBJECT_DIGEST_MISMATCH",
282
- `Producer checksum evidence for ${file.object_ref} does not match the delivered object`
328
+ "Producer checksum evidence does not match the delivered object"
283
329
  );
284
330
  }
285
331
  }
286
332
  let decoded;
287
333
  try {
288
- decoded = await withinInspectionDeadline(
289
- Promise.resolve(decompress(read.body, options.maxDecodedObjectBytes ?? maxObjectBytes)),
290
- deadline
334
+ decoded = await invokeInspectionAdapter(
335
+ () => decompress(read.body, maxDecodedObjectBytes),
336
+ deadline,
337
+ "MANIFEST_INVALID",
338
+ "Could not decode the compressed reporting object"
291
339
  );
292
340
  } catch (error) {
293
341
  if (error instanceof ReportingInspectionError) throw error;
294
- throw inspectionError(
295
- "MANIFEST_INVALID",
296
- `Could not decode ${manifest.compression} object ${file.object_ref}`,
297
- false,
298
- error
299
- );
342
+ throw inspectionError("MANIFEST_INVALID", "Could not decode the compressed reporting object", false, error);
300
343
  }
301
- if (decoded.byteLength > (options.maxDecodedObjectBytes ?? maxObjectBytes)) {
302
- throw inspectionError("RESOURCE_TOO_LARGE", `Decoded object ${file.object_ref} exceeds the byte budget`);
344
+ if (!(decoded instanceof Uint8Array)) {
345
+ throw inspectionError("MANIFEST_INVALID", "Reporting decompressor returned invalid bytes");
346
+ }
347
+ if (decoded.byteLength > maxDecodedObjectBytes) {
348
+ throw inspectionError("RESOURCE_TOO_LARGE", "Decoded reporting object exceeds the byte budget");
303
349
  }
304
350
  totalDecodedBytes += decoded.byteLength;
305
- if (totalDecodedBytes > (options.maxDecodedTotalBytes ?? maxTotalBytes)) {
351
+ if (totalDecodedBytes > maxDecodedTotalBytes) {
306
352
  throw inspectionError("RESOURCE_TOO_LARGE", "Decoded reporting data exceeds the aggregate byte budget");
307
353
  }
308
354
  let fileRows;
309
355
  try {
310
- fileRows = await withinInspectionDeadline(
311
- Promise.resolve(
312
- decoder(decoded, {
313
- format: manifest.format,
314
- objectRef: file.object_ref,
315
- rowSchema: schema,
316
- inspection: context,
317
- maxRows: maxRows - rows.length
318
- })
319
- ),
320
- deadline
356
+ fileRows = await invokeInspectionAdapter(
357
+ () => decoder(decoded, {
358
+ format: manifest.format,
359
+ objectRef: file.object_ref,
360
+ rowSchema: schema,
361
+ inspection: context,
362
+ maxRows: maxRows - rows.length
363
+ }),
364
+ deadline,
365
+ "MANIFEST_INVALID",
366
+ "Could not parse the reporting object"
321
367
  );
322
368
  } catch (error) {
323
369
  if (error instanceof ReportingInspectionError) throw error;
324
- throw inspectionError("MANIFEST_INVALID", `Could not parse ${manifest.format} object ${file.object_ref}`);
370
+ throw inspectionError("MANIFEST_INVALID", "Could not parse the reporting object");
371
+ }
372
+ if (!Array.isArray(fileRows) || !fileRows.every(isRecord)) {
373
+ throw inspectionError("MANIFEST_INVALID", "Reporting decoder returned invalid rows");
325
374
  }
326
375
  if (fileRows.length !== file.row_count) {
327
- throw inspectionError(
328
- "OBJECT_ROW_COUNT_MISMATCH",
329
- `Object ${file.object_ref} row count does not match the manifest`
330
- );
376
+ throw inspectionError("OBJECT_ROW_COUNT_MISMATCH", "Object row count does not match the manifest");
331
377
  }
332
378
  if (fileRows.length > maxRows - rows.length) {
333
379
  throw inspectionError("RESOURCE_TOO_LARGE", "Reporting row count exceeds the configured inspection budget");
@@ -338,7 +384,7 @@ function createReportingManifestInspector(options) {
338
384
  if (index % 256 === 0) assertInspectionDeadline(deadline);
339
385
  assertBoundedJsonData(rows[index], "ROW_SCHEMA_VIOLATION");
340
386
  assertIJson(rows[index]);
341
- if (Buffer.byteLength((0, import_jcs.canonicalize)(rows[index])) > (options.maxRowBytes ?? DEFAULT_MAX_ROW_BYTES)) {
387
+ if (Buffer.byteLength((0, import_jcs.canonicalize)(rows[index])) > maxRowBytes) {
342
388
  throw inspectionError("RESOURCE_TOO_LARGE", `Row ${index} exceeds the configured byte budget`);
343
389
  }
344
390
  if (!validateRow(rows[index])) {
@@ -353,10 +399,15 @@ function createReportingManifestInspector(options) {
353
399
  );
354
400
  }
355
401
  }
356
- const observedTotals = options.controlTotalCalculator ? await withinInspectionDeadline(
357
- Promise.resolve(options.controlTotalCalculator(rows, context.revision.control_totals, context)),
358
- deadline
402
+ const observedTotals = options.controlTotalCalculator ? await invokeInspectionAdapter(
403
+ () => options.controlTotalCalculator(rows, context.revision.control_totals, context),
404
+ deadline,
405
+ "CONTROL_TOTAL_UNSUPPORTED",
406
+ "Reporting control totals could not be calculated"
359
407
  ) : calculateNamedControlTotals(rows, context.revision.control_totals, reportDefinition, deadline);
408
+ if (!Array.isArray(observedTotals) || !observedTotals.every(isControlTotal)) {
409
+ throw inspectionError("CONTROL_TOTAL_UNSUPPORTED", "Reporting calculator returned invalid control totals");
410
+ }
360
411
  const expectedCanonicalDigest = context.revision.canonical_content_digest;
361
412
  let canonicalContentDigest;
362
413
  if (expectedCanonicalDigest && context.expected.verificationProfile === "canonical_digest") {
@@ -397,7 +448,18 @@ function createReportingManifestInspector(options) {
397
448
  );
398
449
  }
399
450
  }
400
- const commitRef = typeof options.consumerCommitRef === "function" ? options.consumerCommitRef(context) : options.consumerCommitRef;
451
+ assertInspectionDeadline(deadline);
452
+ const consumerCommitRef = options.consumerCommitRef;
453
+ const commitRef = typeof consumerCommitRef === "function" ? await invokeInspectionAdapter(
454
+ () => consumerCommitRef(context),
455
+ deadline,
456
+ "CONSUMER_COMMIT_FAILED",
457
+ "Reporting consumer commit reference could not be resolved"
458
+ ) : consumerCommitRef;
459
+ if (commitRef !== void 0 && typeof commitRef !== "string") {
460
+ throw inspectionError("CONSUMER_COMMIT_FAILED", "Reporting consumer commit reference is invalid");
461
+ }
462
+ assertInspectionDeadline(deadline);
401
463
  return {
402
464
  rowCount: rows.length,
403
465
  controlTotals: observedTotals,
@@ -409,17 +471,26 @@ function createReportingManifestInspector(options) {
409
471
  }
410
472
  async function resolveRowSchema(context, resolver, allowedOrigins, deadline) {
411
473
  assertAllowedReferenceOrigin(context.revision.schema_uri, allowedOrigins, "ROW_SCHEMA_FETCH_FAILED");
412
- const result = await withinInspectionDeadline(
413
- resolver.resolve({
474
+ const result = await invokeInspectionAdapter(
475
+ () => resolver.resolve({
414
476
  uri: context.revision.schema_uri,
415
477
  digest: `sha256:${context.revision.schema_sha256.toLowerCase()}`
416
478
  }),
417
- deadline
479
+ deadline,
480
+ "ROW_SCHEMA_FETCH_FAILED",
481
+ "Pinned row schema could not be resolved",
482
+ true
418
483
  );
484
+ assertReferenceResult(result, "ROW_SCHEMA_FETCH_FAILED");
419
485
  if (!result.ok) {
420
- throw new ReportingInspectionError("ROW_SCHEMA_FETCH_FAILED", result.error.message, result.error.retryable, {
421
- referenceCode: result.error.code
422
- });
486
+ throw new ReportingInspectionError(
487
+ "ROW_SCHEMA_FETCH_FAILED",
488
+ "Pinned row schema could not be resolved",
489
+ result.error.retryable,
490
+ {
491
+ referenceCode: result.error.code
492
+ }
493
+ );
423
494
  }
424
495
  requireContentType(result.contentType, ["application/schema+json", "application/json"], "ROW_SCHEMA_INVALID");
425
496
  const document = parsePinnedJson(result.body, "ROW_SCHEMA_INVALID");
@@ -487,7 +558,7 @@ function rejectReferenceCycles(schema, refs) {
487
558
  const edges = /* @__PURE__ */ new Map();
488
559
  for (const ref of refs) {
489
560
  const target = ref === "#" ? schema : ref.startsWith("#/") ? resolveJsonPointer(schema, ref.slice(1)) : anchors.get(ref.slice(1));
490
- if (target === void 0) throw inspectionError("ROW_SCHEMA_INVALID", `Row schema reference ${ref} is unresolved`);
561
+ if (target === void 0) throw inspectionError("ROW_SCHEMA_INVALID", "Row schema reference is unresolved");
491
562
  const childRefs = /* @__PURE__ */ new Set();
492
563
  collectRefs(target, childRefs);
493
564
  edges.set(ref, childRefs);
@@ -552,17 +623,26 @@ async function validateReportDefinition(context, resolver, allowedOrigins, deadl
552
623
  allowedOrigins,
553
624
  "REPORT_DEFINITION_FETCH_FAILED"
554
625
  );
555
- const result = await withinInspectionDeadline(
556
- resolver.resolve({
626
+ const result = await invokeInspectionAdapter(
627
+ () => resolver.resolve({
557
628
  uri: context.revision.report_definition_uri,
558
629
  digest: `sha256:${context.revision.report_definition_sha256.toLowerCase()}`
559
630
  }),
560
- deadline
631
+ deadline,
632
+ "REPORT_DEFINITION_FETCH_FAILED",
633
+ "Pinned report definition could not be resolved",
634
+ true
561
635
  );
636
+ assertReferenceResult(result, "REPORT_DEFINITION_FETCH_FAILED");
562
637
  if (!result.ok) {
563
- throw new ReportingInspectionError("REPORT_DEFINITION_FETCH_FAILED", result.error.message, result.error.retryable, {
564
- referenceCode: result.error.code
565
- });
638
+ throw new ReportingInspectionError(
639
+ "REPORT_DEFINITION_FETCH_FAILED",
640
+ "Pinned report definition could not be resolved",
641
+ result.error.retryable,
642
+ {
643
+ referenceCode: result.error.code
644
+ }
645
+ );
566
646
  }
567
647
  requireContentType(
568
648
  result.contentType,
@@ -595,17 +675,26 @@ async function validateReportDefinition(context, resolver, allowedOrigins, deadl
595
675
  async function recomputeCanonicalDigest(rows, context, resolver, allowedOrigins, deadline) {
596
676
  const expected = context.revision.canonical_content_digest;
597
677
  assertAllowedReferenceOrigin(expected.canonicalization_uri, allowedOrigins, "CANONICALIZATION_FETCH_FAILED");
598
- const result = await withinInspectionDeadline(
599
- resolver.resolve({
678
+ const result = await invokeInspectionAdapter(
679
+ () => resolver.resolve({
600
680
  uri: expected.canonicalization_uri,
601
681
  digest: `sha256:${expected.canonicalization_sha256.toLowerCase()}`
602
682
  }),
603
- deadline
683
+ deadline,
684
+ "CANONICALIZATION_FETCH_FAILED",
685
+ "Pinned canonicalization contract could not be resolved",
686
+ true
604
687
  );
688
+ assertReferenceResult(result, "CANONICALIZATION_FETCH_FAILED");
605
689
  if (!result.ok) {
606
- throw new ReportingInspectionError("CANONICALIZATION_FETCH_FAILED", result.error.message, result.error.retryable, {
607
- referenceCode: result.error.code
608
- });
690
+ throw new ReportingInspectionError(
691
+ "CANONICALIZATION_FETCH_FAILED",
692
+ "Pinned canonicalization contract could not be resolved",
693
+ result.error.retryable,
694
+ {
695
+ referenceCode: result.error.code
696
+ }
697
+ );
609
698
  }
610
699
  requireContentType(
611
700
  result.contentType,
@@ -625,6 +714,12 @@ async function recomputeCanonicalDigest(rows, context, resolver, allowedOrigins,
625
714
  for (const vector of contract.golden_vectors) {
626
715
  assertInspectionDeadline(deadline);
627
716
  const body = canonicalRows(vector.input_rows, contract.primary_keys, deadline);
717
+ if (vector.purpose === "ordering_encoding" && (Buffer.from(body).toString("utf8") === (0, import_jcs.canonicalize)(vector.input_rows) || !vector.input_rows.some((row) => JSON.stringify(row) !== (0, import_jcs.canonicalize)(row)))) {
718
+ throw inspectionError(
719
+ "CANONICALIZATION_INVALID",
720
+ "Ordering/encoding vector must exercise both row ordering and object member ordering"
721
+ );
722
+ }
628
723
  if (Buffer.from(body).toString("base64") !== vector.canonical_utf8_base64 || !constantTimeHexEqual(sha256(body), vector.sha256)) {
629
724
  throw inspectionError("CANONICALIZATION_INVALID", `Canonicalization golden vector ${vector.name} failed`);
630
725
  }
@@ -633,25 +728,41 @@ async function recomputeCanonicalDigest(rows, context, resolver, allowedOrigins,
633
728
  }
634
729
  function parseCanonicalizationContract(document, schemaSha256) {
635
730
  const allowed = ["contract_version", "media_type", "algorithm", "schema_sha256", "primary_keys", "golden_vectors"];
636
- 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) {
731
+ 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) {
637
732
  throw inspectionError(
638
733
  "CANONICALIZATION_INVALID",
639
734
  "Pinned canonicalization contract is invalid or not bound to the row schema"
640
735
  );
641
736
  }
642
- for (const vector of document.golden_vectors) {
643
- 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)) {
737
+ const named = !Array.isArray(document.golden_vectors);
738
+ let vectors;
739
+ if (named) {
740
+ const golden = document.golden_vectors;
741
+ 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"))) {
742
+ throw inspectionError("CANONICALIZATION_INVALID", "Canonicalization contract contains invalid named vectors");
743
+ }
744
+ vectors = [golden.empty_report, golden.ordering_encoding, ...golden.additional ?? []];
745
+ } else {
746
+ vectors = document.golden_vectors;
747
+ }
748
+ if (vectors.length < 2) {
749
+ throw inspectionError("CANONICALIZATION_INVALID", "Canonicalization contract requires at least two golden vectors");
750
+ }
751
+ const names = /* @__PURE__ */ new Set();
752
+ for (const vector of vectors) {
753
+ 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)) {
644
754
  throw inspectionError("CANONICALIZATION_INVALID", "Canonicalization contract contains an invalid golden vector");
645
755
  }
756
+ names.add(vector.name);
646
757
  assertBoundedJsonData(vector.input_rows, "CANONICALIZATION_INVALID");
647
758
  }
648
- if (!document.golden_vectors.some((vector) => vector.input_rows.length === 0) || !document.golden_vectors.some((vector) => vector.input_rows.length >= 2)) {
759
+ if (!vectors.some((vector) => isRecord(vector) && vector.input_rows.length === 0) || !vectors.some((vector) => isRecord(vector) && vector.input_rows.length >= 2)) {
649
760
  throw inspectionError(
650
761
  "CANONICALIZATION_INVALID",
651
762
  "Canonicalization vectors must include empty-report and ordering/encoding cases"
652
763
  );
653
764
  }
654
- return document;
765
+ return { ...document, golden_vectors: vectors };
655
766
  }
656
767
  function canonicalRows(rows, primaryKeys, deadline) {
657
768
  const encoded = rows.map((row, index) => {
@@ -674,6 +785,11 @@ function canonicalRows(rows, primaryKeys, deadline) {
674
785
  }
675
786
  return Buffer.from(`[${encoded.map((item) => item.row).join(",")}]`, "utf8");
676
787
  }
788
+ function assertReferenceResult(value, code) {
789
+ if (!isRecord(value) || value.ok !== true && value.ok !== false || (value.ok ? !(value.body instanceof Uint8Array) : !isRecord(value.error) || typeof value.error.retryable !== "boolean")) {
790
+ throw inspectionError(code, "Pinned reference resolver returned an invalid result");
791
+ }
792
+ }
677
793
  function parsePinnedJson(body, code) {
678
794
  try {
679
795
  return (0, import_strict_json.parseStrictJson)(new TextDecoder("utf-8", { fatal: true }).decode(body));
@@ -829,6 +945,9 @@ function calculateNamedControlTotals(rows, expected, reportDefinition, deadline)
829
945
  `Control total ${total.name} needs a custom calculator for its declared aggregation`
830
946
  );
831
947
  }
948
+ if (total.unit !== void 0 && metric.unit !== void 0 && metric.unit !== total.unit) {
949
+ throw inspectionError("CONTROL_TOTAL_MISMATCH", "Control-total unit does not match the pinned report definition");
950
+ }
832
951
  let sum = { coefficient: 0n, scale: 0 };
833
952
  for (let index = 0; index < rows.length; index += 1) {
834
953
  if (index % 256 === 0) assertInspectionDeadline(deadline);
@@ -862,6 +981,8 @@ function deadlineSignal(deadline) {
862
981
  return AbortSignal.timeout(Math.max(1, deadline - Date.now()));
863
982
  }
864
983
  async function withinInspectionDeadline(operation, deadline) {
984
+ void operation.catch(() => {
985
+ });
865
986
  assertInspectionDeadline(deadline);
866
987
  const remaining = deadline - Date.now();
867
988
  let timer;
@@ -885,7 +1006,7 @@ function parseDecimal(value) {
885
1006
  throw inspectionError("CONTROL_TOTAL_UNSUPPORTED", "Control-total decimal exceeds the precision bound");
886
1007
  }
887
1008
  const match = /^(-?)(\d+)(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/.exec(text);
888
- if (!match) throw inspectionError("CONTROL_TOTAL_UNSUPPORTED", `Non-decimal control value ${text}`);
1009
+ if (!match) throw inspectionError("CONTROL_TOTAL_UNSUPPORTED", "Control-total value is not a decimal");
889
1010
  const fraction = match[3] ?? "";
890
1011
  const exponent = Number(match[4] ?? 0);
891
1012
  if (!Number.isSafeInteger(exponent) || Math.abs(exponent) > 1024 || match[2].length + fraction.length > 1024) {
@@ -943,7 +1064,7 @@ function assertIJson(value, seen = /* @__PURE__ */ new Set()) {
943
1064
  const code = value.charCodeAt(index);
944
1065
  if (code >= 55296 && code <= 56319) {
945
1066
  const next = value.charCodeAt(index + 1);
946
- if (next < 56320 || next > 57343)
1067
+ if (!Number.isFinite(next) || next < 56320 || next > 57343)
947
1068
  throw inspectionError("CANONICALIZATION_INVALID", "Lone Unicode surrogate is not I-JSON");
948
1069
  index += 1;
949
1070
  } else if (code >= 56320 && code <= 57343) {
@@ -952,9 +1073,13 @@ function assertIJson(value, seen = /* @__PURE__ */ new Set()) {
952
1073
  }
953
1074
  return;
954
1075
  }
955
- if (typeof value === "number" && !Number.isFinite(value))
956
- throw inspectionError("CANONICALIZATION_INVALID", "Non-finite number is not I-JSON");
957
- if (!value || typeof value !== "object") return;
1076
+ if (value === null || typeof value === "boolean") return;
1077
+ if (typeof value === "number") {
1078
+ if (!Number.isFinite(value)) throw inspectionError("CANONICALIZATION_INVALID", "Non-finite number is not I-JSON");
1079
+ return;
1080
+ }
1081
+ if (typeof value !== "object" || !Array.isArray(value) && !isRecord(value))
1082
+ throw inspectionError("CANONICALIZATION_INVALID", "Reporting values must be JSON data");
958
1083
  if (seen.has(value)) throw inspectionError("CANONICALIZATION_INVALID", "Cyclic row is not JSON");
959
1084
  seen.add(value);
960
1085
  if (Array.isArray(value)) value.forEach((child) => assertIJson(child, seen));
@@ -993,7 +1118,7 @@ function isValidReportDefinition(value) {
993
1118
  }
994
1119
  if (!Array.isArray(value.metrics) || value.metrics.length === 0 || !value.metrics.every(
995
1120
  (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)
996
- ) || !Array.isArray(value.dimensions) || !value.dimensions.every(
1121
+ ) || new Set(value.metrics.map((metric) => metric.name)).size !== value.metrics.length || !Array.isArray(value.dimensions) || !value.dimensions.every(
997
1122
  (dimension) => typeof dimension === "string" && dimension.length > 0 && dimension.length <= 128
998
1123
  ) || new Set(value.dimensions).size !== value.dimensions.length) {
999
1124
  return false;
@@ -1089,7 +1214,7 @@ function constantTimeHexEqual(left, right) {
1089
1214
  function requireContentType(raw, allowed, code) {
1090
1215
  const mediaType = raw?.split(";", 1)[0]?.trim().toLowerCase();
1091
1216
  if (!mediaType || !allowed.includes(mediaType)) {
1092
- throw inspectionError(code, `Pinned document returned unsupported content type ${mediaType ?? "<missing>"}`);
1217
+ throw inspectionError(code, "Pinned document returned an unsupported content type");
1093
1218
  }
1094
1219
  }
1095
1220
  function assertAllowedReferenceOrigin(raw, allowedOrigins, code) {
@@ -1111,8 +1236,28 @@ function sanitizeAjvErrors(errors) {
1111
1236
  message: error.message
1112
1237
  }));
1113
1238
  }
1114
- function inspectionError(code, message, retryable = false, cause) {
1115
- return new ReportingInspectionError(code, message, retryable, void 0, cause === void 0 ? void 0 : { cause });
1239
+ function inspectionError(code, message, retryable = false, _cause) {
1240
+ return new ReportingInspectionError(code, message, retryable);
1241
+ }
1242
+ async function invokeInspectionAdapter(callback, deadline, code, message, retryable = false) {
1243
+ try {
1244
+ assertInspectionDeadline(deadline);
1245
+ const result = await withinInspectionDeadline(
1246
+ Promise.resolve().then(() => {
1247
+ assertInspectionDeadline(deadline);
1248
+ return callback();
1249
+ }),
1250
+ deadline
1251
+ );
1252
+ assertInspectionDeadline(deadline);
1253
+ return result;
1254
+ } catch (error) {
1255
+ throw error instanceof ReportingInspectionError ? new ReportingInspectionError(
1256
+ error.code,
1257
+ error.code === "INSPECTION_TIMEOUT" ? "Reporting inspection exceeded its configured deadline" : message,
1258
+ error.retryable
1259
+ ) : inspectionError(code, message, retryable);
1260
+ }
1116
1261
  }
1117
1262
  function inspectionMismatch(code, message, observation) {
1118
1263
  return new ReportingInspectionError(code, message, false, void 0, void 0, observation);