@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.
- package/dist/lib/reporting/inspection.d.mts +3 -3
- package/dist/lib/reporting/inspection.d.ts +3 -3
- package/dist/lib/reporting/inspection.js +229 -84
- package/dist/lib/reporting/inspection.mjs +229 -84
- package/dist/lib/reporting/ledger/status-ingest.js +3 -19
- package/dist/lib/reporting/ledger/status-ingest.mjs +2 -18
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/create-adcp-server.js +5 -2
- package/dist/lib/server/create-adcp-server.mjs +5 -2
- package/dist/lib/signing/canonicalize.d.mts +2 -0
- package/dist/lib/signing/canonicalize.d.ts +2 -0
- package/dist/lib/signing/canonicalize.js +1 -1
- package/dist/lib/signing/canonicalize.mjs +1 -1
- package/dist/lib/signing/verifier.js +1 -1
- package/dist/lib/signing/verifier.mjs +1 -1
- package/dist/lib/testing/storyboard/request-signing/builder.js +13 -1
- package/dist/lib/testing/storyboard/request-signing/builder.mjs +13 -1
- package/dist/lib/testing/storyboard/request-signing/grader.js +1 -0
- package/dist/lib/testing/storyboard/request-signing/grader.mjs +1 -0
- package/dist/lib/testing/storyboard/request-signing/vector-loader.js +1 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs +1 -1
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/utils/pointer-utils.d.mts +2 -0
- package/dist/lib/utils/pointer-utils.d.ts +2 -0
- package/dist/lib/utils/pointer-utils.js +43 -0
- package/dist/lib/utils/pointer-utils.mjs +19 -0
- package/dist/lib/validation/diagnostic-limits.d.mts +4 -0
- package/dist/lib/validation/diagnostic-limits.d.ts +4 -0
- package/dist/lib/validation/diagnostic-limits.js +57 -0
- package/dist/lib/validation/diagnostic-limits.mjs +32 -0
- package/dist/lib/validation/schema-errors.d.mts +3 -1
- package/dist/lib/validation/schema-errors.d.ts +3 -1
- package/dist/lib/validation/schema-errors.js +18 -3
- package/dist/lib/validation/schema-errors.mjs +18 -3
- package/dist/lib/validation/schema-validator.js +56 -4
- package/dist/lib/validation/schema-validator.mjs +56 -4
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.mjs +3 -3
- package/docs/TYPE-SUMMARY.md +2 -2
- package/docs/llms.txt +2 -2
- package/docs/migration-14.x-rc-worksheet.md +4 -4
- package/package.json +2 -2
- 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
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
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
|
|
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",
|
|
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
|
-
|
|
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
|
|
259
|
-
|
|
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
|
|
272
|
-
throw inspectionError("
|
|
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 >
|
|
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
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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",
|
|
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])) >
|
|
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
|
|
327
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
391
|
-
|
|
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",
|
|
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
|
|
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(
|
|
534
|
-
|
|
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
|
|
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(
|
|
577
|
-
|
|
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
|
|
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
|
-
|
|
613
|
-
|
|
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 (!
|
|
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",
|
|
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 === "
|
|
926
|
-
|
|
927
|
-
|
|
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,
|
|
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,
|
|
1085
|
-
return new ReportingInspectionError(code, message, retryable
|
|
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-
|
|
7
|
+
"synced_at": "2026-10-06T17:21:37.513Z"
|
|
8
8
|
}
|