@adcp/sdk 16.1.0 → 16.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/AGENTS.md +2 -0
- package/dist/lib/core/AgentClient.js +17 -1
- package/dist/lib/core/AgentClient.mjs +17 -1
- package/dist/lib/core/SingleAgentClient.js +13 -0
- package/dist/lib/core/SingleAgentClient.mjs +13 -0
- package/dist/lib/core/TaskExecutor.js +88 -13
- package/dist/lib/core/TaskExecutor.mjs +89 -14
- package/dist/lib/errors/index.d.mts +34 -0
- package/dist/lib/errors/index.d.ts +34 -0
- package/dist/lib/errors/index.js +11 -0
- package/dist/lib/errors/index.mjs +10 -0
- package/dist/lib/governance/authorization.d.mts +66 -0
- package/dist/lib/governance/authorization.d.ts +66 -0
- package/dist/lib/governance/authorization.js +181 -37
- package/dist/lib/governance/authorization.mjs +183 -36
- package/dist/lib/governance/index.d.mts +2 -2
- package/dist/lib/governance/index.d.ts +2 -2
- package/dist/lib/governance/index.js +4 -2
- package/dist/lib/governance/index.mjs +3 -1
- package/dist/lib/identity-3.3/index.d.mts +1 -0
- package/dist/lib/identity-3.3/index.d.ts +1 -0
- package/dist/lib/identity-3.3/index.js +3 -1
- package/dist/lib/identity-3.3/index.mjs +1 -0
- package/dist/lib/identity-3.3/jws-artifacts.d.mts +147 -0
- package/dist/lib/identity-3.3/jws-artifacts.d.ts +147 -0
- package/dist/lib/identity-3.3/jws-artifacts.js +258 -0
- package/dist/lib/identity-3.3/jws-artifacts.mjs +234 -0
- package/dist/lib/identity-3.3/revocation.d.mts +7 -0
- package/dist/lib/identity-3.3/revocation.d.ts +7 -0
- package/dist/lib/identity-3.3/revocation.js +13 -0
- package/dist/lib/identity-3.3/revocation.mjs +13 -0
- package/dist/lib/index.d.mts +3 -3
- package/dist/lib/index.d.ts +3 -3
- package/dist/lib/index.js +4 -0
- package/dist/lib/index.mjs +4 -0
- package/dist/lib/media-buy/compatibility.d.mts +1 -0
- package/dist/lib/media-buy/compatibility.d.ts +1 -0
- package/dist/lib/media-buy/compatibility.js +33 -10
- package/dist/lib/media-buy/compatibility.mjs +32 -14
- package/dist/lib/negotiation/buyer.js +11 -5
- package/dist/lib/negotiation/buyer.mjs +11 -5
- package/dist/lib/negotiation/types.d.mts +4 -2
- package/dist/lib/negotiation/types.d.ts +4 -2
- package/dist/lib/negotiation/verification.d.mts +15 -6
- package/dist/lib/negotiation/verification.d.ts +15 -6
- package/dist/lib/negotiation/verification.js +146 -27
- package/dist/lib/negotiation/verification.mjs +146 -28
- package/dist/lib/negotiation/version.d.mts +6 -0
- package/dist/lib/negotiation/version.d.ts +6 -0
- package/dist/lib/negotiation/version.js +60 -0
- package/dist/lib/negotiation/version.mjs +33 -0
- package/dist/lib/registry/types.generated.d.mts +24 -1
- package/dist/lib/registry/types.generated.d.ts +24 -1
- package/dist/lib/server/create-adcp-server.js +1 -1
- package/dist/lib/server/create-adcp-server.mjs +1 -1
- package/dist/lib/server/error-arm-tools.d.mts +10 -10
- package/dist/lib/server/error-arm-tools.d.ts +10 -10
- package/dist/lib/server/error-arm-tools.js +22 -58
- package/dist/lib/server/error-arm-tools.mjs +30 -65
- package/dist/lib/server/index.d.mts +2 -2
- package/dist/lib/server/index.d.ts +2 -2
- package/dist/lib/server/index.js +2 -0
- package/dist/lib/server/index.mjs +2 -0
- package/dist/lib/signing/web-bot-auth.js +5 -0
- package/dist/lib/signing/web-bot-auth.mjs +4 -0
- package/dist/lib/testing/compliance/comply.js +17 -0
- package/dist/lib/testing/compliance/comply.mjs +17 -0
- package/dist/lib/testing/index.d.mts +3 -0
- package/dist/lib/testing/index.d.ts +3 -0
- package/dist/lib/testing/index.js +44 -2
- package/dist/lib/testing/index.mjs +42 -0
- package/dist/lib/testing/storyboard/collection-selection.d.mts +259 -0
- package/dist/lib/testing/storyboard/collection-selection.d.ts +259 -0
- package/dist/lib/testing/storyboard/collection-selection.js +384 -0
- package/dist/lib/testing/storyboard/collection-selection.mjs +348 -0
- package/dist/lib/testing/storyboard/context.js +12 -6
- package/dist/lib/testing/storyboard/context.mjs +12 -6
- package/dist/lib/testing/storyboard/fixture-resolution.d.mts +39 -1
- package/dist/lib/testing/storyboard/fixture-resolution.d.ts +39 -1
- package/dist/lib/testing/storyboard/fixture-resolution.js +216 -12
- package/dist/lib/testing/storyboard/fixture-resolution.mjs +218 -12
- package/dist/lib/testing/storyboard/index.d.mts +9 -2
- package/dist/lib/testing/storyboard/index.d.ts +9 -2
- package/dist/lib/testing/storyboard/index.js +51 -2
- package/dist/lib/testing/storyboard/index.mjs +49 -1
- package/dist/lib/testing/storyboard/prepared-fixtures.d.mts +471 -0
- package/dist/lib/testing/storyboard/prepared-fixtures.d.ts +471 -0
- package/dist/lib/testing/storyboard/prepared-fixtures.js +434 -0
- package/dist/lib/testing/storyboard/prepared-fixtures.mjs +389 -0
- package/dist/lib/testing/storyboard/protected-context.d.mts +127 -0
- package/dist/lib/testing/storyboard/protected-context.d.ts +127 -0
- package/dist/lib/testing/storyboard/protected-context.js +301 -0
- package/dist/lib/testing/storyboard/protected-context.mjs +267 -0
- package/dist/lib/testing/storyboard/rejection-hints.js +13 -4
- package/dist/lib/testing/storyboard/rejection-hints.mjs +13 -4
- package/dist/lib/testing/storyboard/runner.d.mts +24 -0
- package/dist/lib/testing/storyboard/runner.d.ts +24 -0
- package/dist/lib/testing/storyboard/runner.js +1061 -105
- package/dist/lib/testing/storyboard/runner.mjs +1075 -106
- package/dist/lib/testing/storyboard/seeding.d.mts +15 -1
- package/dist/lib/testing/storyboard/seeding.d.ts +15 -1
- package/dist/lib/testing/storyboard/seeding.js +272 -12
- package/dist/lib/testing/storyboard/seeding.mjs +274 -13
- package/dist/lib/testing/storyboard/types.d.mts +207 -10
- package/dist/lib/testing/storyboard/types.d.ts +207 -10
- package/dist/lib/testing/storyboard/types.js +1 -1
- package/dist/lib/testing/storyboard/types.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/testing/storyboard/validations.js +95 -0
- package/dist/lib/testing/storyboard/validations.mjs +95 -0
- package/dist/lib/validation/bundled-schema-store.js +5 -0
- package/dist/lib/validation/bundled-schema-store.mjs +4 -0
- package/dist/lib/validation/schema-loader.js +19 -2
- package/dist/lib/validation/schema-loader.mjs +22 -3
- 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/guides/COLLECTION-FIXTURE-FOUNDATION.md +850 -0
- package/docs/guides/PROPOSAL-TERMS-VERIFICATION.md +66 -0
- package/docs/llms.txt +4 -3
- package/docs/migration-14.x-rc-worksheet.md +4 -4
- package/docs/migration-agent-identity-3.3.md +51 -6
- package/package.json +6 -4
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { type TestClient } from '../client.mjs';
|
|
24
24
|
import { FixtureBindingRegistry } from './fixture-resolution.mjs';
|
|
25
|
-
import
|
|
25
|
+
import { type CollectionSelectionWitness } from './collection-selection.mjs';
|
|
26
|
+
import type { ContextProvenanceEntry, FixtureResolutionRecord, Storyboard, StoryboardContext, StoryboardFixtures, StoryboardPhaseResult, StoryboardRunOptions } from './types.mjs';
|
|
26
27
|
/** Synthetic phase id used in `StoryboardResult.phases[]` for the seed pass. */
|
|
27
28
|
export declare const CONTROLLER_SEEDING_PHASE_ID = "__controller_seeding__";
|
|
28
29
|
/** Seed scenario names. Kept local — the server-side `SEED_SCENARIOS`
|
|
@@ -75,6 +76,19 @@ export interface ControllerSeedingResult {
|
|
|
75
76
|
bindings?: FixtureBindingRegistry;
|
|
76
77
|
/** Machine-readable AdCP 3.2 resolution evidence. */
|
|
77
78
|
resolutionRecords?: FixtureResolutionRecord[];
|
|
79
|
+
/**
|
|
80
|
+
* Declaration-local captures from the SAME selected graph, published only
|
|
81
|
+
* when every declared fixture resolved. The runner commits them through the
|
|
82
|
+
* protected-context funnel; they are never merged by plain assignment.
|
|
83
|
+
*/
|
|
84
|
+
preparedCaptures?: PreparedFixtureCaptures;
|
|
85
|
+
}
|
|
86
|
+
/** Reserved-namespace values and provenance produced by fixture preparation. */
|
|
87
|
+
export interface PreparedFixtureCaptures {
|
|
88
|
+
values: Record<string, unknown>;
|
|
89
|
+
provenance: Record<string, ContextProvenanceEntry>;
|
|
90
|
+
/** Per-handle witness retained for the entity that was actually selected. */
|
|
91
|
+
witnesses: Record<string, CollectionSelectionWitness>;
|
|
78
92
|
}
|
|
79
93
|
/**
|
|
80
94
|
* Fire every seed call for this storyboard. Returns `null` when seeding is
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { type TestClient } from '../client';
|
|
24
24
|
import { FixtureBindingRegistry } from './fixture-resolution';
|
|
25
|
-
import
|
|
25
|
+
import { type CollectionSelectionWitness } from './collection-selection';
|
|
26
|
+
import type { ContextProvenanceEntry, FixtureResolutionRecord, Storyboard, StoryboardContext, StoryboardFixtures, StoryboardPhaseResult, StoryboardRunOptions } from './types';
|
|
26
27
|
/** Synthetic phase id used in `StoryboardResult.phases[]` for the seed pass. */
|
|
27
28
|
export declare const CONTROLLER_SEEDING_PHASE_ID = "__controller_seeding__";
|
|
28
29
|
/** Seed scenario names. Kept local — the server-side `SEED_SCENARIOS`
|
|
@@ -75,6 +76,19 @@ export interface ControllerSeedingResult {
|
|
|
75
76
|
bindings?: FixtureBindingRegistry;
|
|
76
77
|
/** Machine-readable AdCP 3.2 resolution evidence. */
|
|
77
78
|
resolutionRecords?: FixtureResolutionRecord[];
|
|
79
|
+
/**
|
|
80
|
+
* Declaration-local captures from the SAME selected graph, published only
|
|
81
|
+
* when every declared fixture resolved. The runner commits them through the
|
|
82
|
+
* protected-context funnel; they are never merged by plain assignment.
|
|
83
|
+
*/
|
|
84
|
+
preparedCaptures?: PreparedFixtureCaptures;
|
|
85
|
+
}
|
|
86
|
+
/** Reserved-namespace values and provenance produced by fixture preparation. */
|
|
87
|
+
export interface PreparedFixtureCaptures {
|
|
88
|
+
values: Record<string, unknown>;
|
|
89
|
+
provenance: Record<string, ContextProvenanceEntry>;
|
|
90
|
+
/** Per-handle witness retained for the entity that was actually selected. */
|
|
91
|
+
witnesses: Record<string, CollectionSelectionWitness>;
|
|
78
92
|
}
|
|
79
93
|
/**
|
|
80
94
|
* Fire every seed call for this storyboard. Returns `null` when seeding is
|
|
@@ -30,6 +30,9 @@ var import_envelope_status_compat = require('../../utils/envelope-status-compat.
|
|
|
30
30
|
var import_test_controller = require('../test-controller.js');
|
|
31
31
|
var import_task_map = require('./task-map.js');
|
|
32
32
|
var import_fixture_resolution = require('./fixture-resolution.js');
|
|
33
|
+
var import_collection_selection = require('./collection-selection.js');
|
|
34
|
+
var import_path = require('./path.js');
|
|
35
|
+
var import_redact_secrets = require('../../utils/redact-secrets.js');
|
|
33
36
|
const CONTROLLER_SEEDING_PHASE_ID = "__controller_seeding__";
|
|
34
37
|
function buildSeedCalls(fixtures) {
|
|
35
38
|
if (!fixtures) return [];
|
|
@@ -379,6 +382,10 @@ async function runRoutedControllerSeeding(storyboard, calls, context, resolveFix
|
|
|
379
382
|
failedCount
|
|
380
383
|
};
|
|
381
384
|
}
|
|
385
|
+
function discoveryExchangeFor(catalog, productId) {
|
|
386
|
+
const index = productId === void 0 ? void 0 : catalog.pageOfProduct.get(productId);
|
|
387
|
+
return catalog.exchanges[index ?? catalog.exchanges.length - 1];
|
|
388
|
+
}
|
|
382
389
|
async function runDeclaredFixtureResolution(seedClient, discoveryClient, storyboard, options, context, calls, resolveFixtureAgent) {
|
|
383
390
|
const start = Date.now();
|
|
384
391
|
const authoringErrorResult = buildAuthoringErrorResult(storyboard, calls, context, start);
|
|
@@ -407,6 +414,11 @@ async function runDeclaredFixtureResolution(seedClient, discoveryClient, storybo
|
|
|
407
414
|
const claimedProducts = /* @__PURE__ */ new Map();
|
|
408
415
|
const claimedPricingOptions = /* @__PURE__ */ new Map();
|
|
409
416
|
const productStatuses = /* @__PURE__ */ new Map();
|
|
417
|
+
const pendingCaptures = [];
|
|
418
|
+
const retainedWitnesses = /* @__PURE__ */ new Map();
|
|
419
|
+
let captureFailure;
|
|
420
|
+
const captureFailures = /* @__PURE__ */ new Map();
|
|
421
|
+
const captureFailedHandles = /* @__PURE__ */ new Set();
|
|
410
422
|
const seedContext = { correlation_id: `${storyboardCorrelationPrefix(storyboard)}--__seeding__` };
|
|
411
423
|
let seedOptions = options;
|
|
412
424
|
let discoveryOptions = options;
|
|
@@ -539,6 +551,43 @@ async function runDeclaredFixtureResolution(seedClient, discoveryClient, storybo
|
|
|
539
551
|
const data = raw.data;
|
|
540
552
|
if (raw.success && data?.success === true) {
|
|
541
553
|
boundIds = bindSeededFixture(spec, bindings);
|
|
554
|
+
const seedSnapshot = {
|
|
555
|
+
...spec.fixture,
|
|
556
|
+
...spec.entityType === "product" ? { product_id: boundIds.product_id } : { pricing_option_id: boundIds.pricing_option_id }
|
|
557
|
+
};
|
|
558
|
+
const seedWitness = (0, import_fixture_resolution.matchFixtureRequirementsWithWitness)(seedSnapshot, spec.clauses).witness;
|
|
559
|
+
if (seedWitness) retainedWitnesses.set(spec.handle, seedWitness);
|
|
560
|
+
const seedCaptures = collectDeclaredCaptures(spec, seedSnapshot, seedWitness);
|
|
561
|
+
if (!seedCaptures.ok) {
|
|
562
|
+
failure = seedCaptures.detail;
|
|
563
|
+
captureFailure ??= seedCaptures.detail;
|
|
564
|
+
captureFailures.set(spec.handle, {
|
|
565
|
+
...seedCaptures.path !== void 0 && { pointer: toJsonPointer(seedCaptures.path) },
|
|
566
|
+
exchange: {
|
|
567
|
+
request: {
|
|
568
|
+
transport: "mcp",
|
|
569
|
+
operation: "comply_test_controller",
|
|
570
|
+
// The object actually handed to `callControllerRaw`,
|
|
571
|
+
// not `controllerRequest` — the call substitutes
|
|
572
|
+
// `call.params` when the bound params are not a plain
|
|
573
|
+
// record, and adds the seeding correlation context.
|
|
574
|
+
payload: (0, import_redact_secrets.redactSecrets)({
|
|
575
|
+
scenario: call.scenario,
|
|
576
|
+
params: isPlainRecord(controllerRequest.params) ? controllerRequest.params : call.params,
|
|
577
|
+
context: seedContext
|
|
578
|
+
})
|
|
579
|
+
},
|
|
580
|
+
response: { transport: "mcp", payload: (0, import_redact_secrets.redactSecrets)(raw.data ?? raw.error ?? null) }
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
captureFailedHandles.add(spec.handle);
|
|
584
|
+
attempts.push({ strategy, disposition: "failed", detail: seedCaptures.detail });
|
|
585
|
+
boundIds = void 0;
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
pendingCaptures.push(
|
|
589
|
+
...seedCaptures.captures.map((c) => ({ ...c, sourceTask: "comply_test_controller" }))
|
|
590
|
+
);
|
|
542
591
|
if (spec.entityType === "product") {
|
|
543
592
|
recordIdentityClaim(claimedProducts, boundIds.product_id, spec.allowReuse);
|
|
544
593
|
} else {
|
|
@@ -594,7 +643,10 @@ async function runDeclaredFixtureResolution(seedClient, discoveryClient, storybo
|
|
|
594
643
|
discovered.catalog.products,
|
|
595
644
|
bindings,
|
|
596
645
|
claimedProducts,
|
|
597
|
-
claimedPricingOptions
|
|
646
|
+
claimedPricingOptions,
|
|
647
|
+
spec.entityType === "product" ? specs.filter(
|
|
648
|
+
(candidate) => candidate.entityType === "product_pricing_option" && candidate.parentProductHandle === spec.handle
|
|
649
|
+
) : []
|
|
598
650
|
);
|
|
599
651
|
if (!selected.ok) {
|
|
600
652
|
if (selected.failed) {
|
|
@@ -617,6 +669,27 @@ async function runDeclaredFixtureResolution(seedClient, discoveryClient, storybo
|
|
|
617
669
|
}
|
|
618
670
|
boundIds = selected.boundIds;
|
|
619
671
|
chosenStrategy = strategy;
|
|
672
|
+
if (selected.witness) retainedWitnesses.set(spec.handle, selected.witness);
|
|
673
|
+
const collected = collectDeclaredCaptures(spec, selected.snapshot, selected.witness);
|
|
674
|
+
if (!collected.ok) {
|
|
675
|
+
failure = collected.detail;
|
|
676
|
+
captureFailure ??= collected.detail;
|
|
677
|
+
const failureExchange = discoveryExchangeFor(discovered.catalog, selected.boundIds.product_id);
|
|
678
|
+
captureFailures.set(spec.handle, {
|
|
679
|
+
...collected.path !== void 0 && { pointer: toJsonPointer(collected.path) },
|
|
680
|
+
...failureExchange && { exchange: failureExchange }
|
|
681
|
+
});
|
|
682
|
+
captureFailedHandles.add(spec.handle);
|
|
683
|
+
attempts.push({
|
|
684
|
+
strategy,
|
|
685
|
+
disposition: "failed",
|
|
686
|
+
detail: collected.detail,
|
|
687
|
+
response: { ...discovered.catalog.evidence, selected: selected.boundIds }
|
|
688
|
+
});
|
|
689
|
+
boundIds = void 0;
|
|
690
|
+
break;
|
|
691
|
+
}
|
|
692
|
+
pendingCaptures.push(...collected.captures.map((c) => ({ ...c, sourceTask: "get_products" })));
|
|
620
693
|
attempts.push({
|
|
621
694
|
strategy,
|
|
622
695
|
disposition: "resolved",
|
|
@@ -640,6 +713,7 @@ async function runDeclaredFixtureResolution(seedClient, discoveryClient, storybo
|
|
|
640
713
|
};
|
|
641
714
|
records.push(record);
|
|
642
715
|
if (status !== "unsatisfied") {
|
|
716
|
+
const captureFailed = status === "failed" && captureFailedHandles.has(spec.handle);
|
|
643
717
|
steps.push({
|
|
644
718
|
storyboard_id: storyboard.id,
|
|
645
719
|
step_id: call.step_id,
|
|
@@ -648,7 +722,29 @@ async function runDeclaredFixtureResolution(seedClient, discoveryClient, storybo
|
|
|
648
722
|
task: chosenStrategy === "discover" ? "get_products" : "comply_test_controller",
|
|
649
723
|
passed: status !== "failed",
|
|
650
724
|
duration_ms: Date.now() - stepStart,
|
|
651
|
-
|
|
725
|
+
// An unbuildable capture gets a `check` discriminator like the other
|
|
726
|
+
// two failure classes, so a report renderer keys on `check` for all
|
|
727
|
+
// three instead of regexing prose for this one.
|
|
728
|
+
validations: captureFailed ? [
|
|
729
|
+
{
|
|
730
|
+
check: "fixture_capture_unbuildable",
|
|
731
|
+
passed: false,
|
|
732
|
+
description: "Every declared fixture capture must be buildable from the pinned entity",
|
|
733
|
+
error: failure,
|
|
734
|
+
// The contract's analogue, `capture_path_not_resolvable`,
|
|
735
|
+
// requires the RFC 6901 form of the `context_outputs` path
|
|
736
|
+
// that failed. `null` only for the witness and size/depth
|
|
737
|
+
// arms, which genuinely have no path.
|
|
738
|
+
json_pointer: captureFailures.get(spec.handle)?.pointer ?? null,
|
|
739
|
+
expected: "a value at the declared selected-entity path, or a derivable witness",
|
|
740
|
+
actual: null,
|
|
741
|
+
// required_on_failure: null only when the check is not a
|
|
742
|
+
// response_schema check, which this never is.
|
|
743
|
+
schema_id: null,
|
|
744
|
+
schema_url: null,
|
|
745
|
+
...captureFailures.get(spec.handle)?.exchange ?? {}
|
|
746
|
+
}
|
|
747
|
+
] : [],
|
|
652
748
|
context,
|
|
653
749
|
extraction: { path: "none" },
|
|
654
750
|
...failure && { error: failure }
|
|
@@ -659,6 +755,23 @@ async function runDeclaredFixtureResolution(seedClient, discoveryClient, storybo
|
|
|
659
755
|
else passedCount++;
|
|
660
756
|
}
|
|
661
757
|
const allPassed = failedCount === 0;
|
|
758
|
+
const graphComplete = allPassed && !fixtureUnsatisfied && captureFailure === void 0 && records.every((r) => r.status === "resolved");
|
|
759
|
+
const preparedCaptures = graphComplete && pendingCaptures.length > 0 ? {
|
|
760
|
+
values: Object.fromEntries(pendingCaptures.map((entry) => [entry.key, entry.value])),
|
|
761
|
+
provenance: Object.fromEntries(
|
|
762
|
+
pendingCaptures.map((entry) => [
|
|
763
|
+
entry.key,
|
|
764
|
+
{
|
|
765
|
+
source_step_id: CONTROLLER_SEEDING_PHASE_ID,
|
|
766
|
+
source_kind: "fixture_preparation",
|
|
767
|
+
source_task: entry.sourceTask,
|
|
768
|
+
fixture_handle: entry.spec.handle,
|
|
769
|
+
...entry.spec.parentProductHandle && { fixture_product_handle: entry.spec.parentProductHandle }
|
|
770
|
+
}
|
|
771
|
+
])
|
|
772
|
+
),
|
|
773
|
+
witnesses: Object.fromEntries(retainedWitnesses)
|
|
774
|
+
} : void 0;
|
|
662
775
|
return {
|
|
663
776
|
phase: {
|
|
664
777
|
phase_id: CONTROLLER_SEEDING_PHASE_ID,
|
|
@@ -672,8 +785,76 @@ async function runDeclaredFixtureResolution(seedClient, discoveryClient, storybo
|
|
|
672
785
|
failedCount,
|
|
673
786
|
fixtureUnsatisfied,
|
|
674
787
|
bindings,
|
|
675
|
-
resolutionRecords: records
|
|
788
|
+
resolutionRecords: records,
|
|
789
|
+
...preparedCaptures && { preparedCaptures }
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
function toJsonPointer(path) {
|
|
793
|
+
if (path === void 0 || path.length === 0) return void 0;
|
|
794
|
+
const segments = path.replace(/\[(\d+)\]/g, ".$1").split(".").filter((segment) => segment.length > 0).map((segment) => segment.replace(/~/g, "~0").replace(/\//g, "~1"));
|
|
795
|
+
return segments.length > 0 ? `/${segments.join("/")}` : void 0;
|
|
796
|
+
}
|
|
797
|
+
const DISCOVERY_RETAINED_RESPONSE_PAGES = 16;
|
|
798
|
+
const CAPTURE_MAX_DEPTH = 64;
|
|
799
|
+
const CAPTURE_MAX_BYTES = 512 * 1024;
|
|
800
|
+
function describeUncapturableValue(value, key) {
|
|
801
|
+
let depth = 0;
|
|
802
|
+
const walk = (node, level) => {
|
|
803
|
+
if (level > depth) depth = level;
|
|
804
|
+
if (depth > CAPTURE_MAX_DEPTH) return false;
|
|
805
|
+
if (node === null || typeof node !== "object") return true;
|
|
806
|
+
if (Array.isArray(node)) return node.every((item) => walk(item, level + 1));
|
|
807
|
+
return Object.values(node).every((item) => walk(item, level + 1));
|
|
676
808
|
};
|
|
809
|
+
if (!walk(value, 1)) {
|
|
810
|
+
return `fixture capture "${key}" nests deeper than the ${CAPTURE_MAX_DEPTH}-level capture limit`;
|
|
811
|
+
}
|
|
812
|
+
let size;
|
|
813
|
+
try {
|
|
814
|
+
size = Buffer.byteLength(JSON.stringify(value) ?? "", "utf8");
|
|
815
|
+
} catch {
|
|
816
|
+
return `fixture capture "${key}" is not JSON-serializable and cannot be detached`;
|
|
817
|
+
}
|
|
818
|
+
if (size > CAPTURE_MAX_BYTES) {
|
|
819
|
+
return `fixture capture "${key}" serializes to ${size} bytes, over the ${CAPTURE_MAX_BYTES}-byte capture limit`;
|
|
820
|
+
}
|
|
821
|
+
return void 0;
|
|
822
|
+
}
|
|
823
|
+
function collectDeclaredCaptures(spec, snapshot, witness) {
|
|
824
|
+
if (spec.contextOutputs.length === 0) return { ok: true, captures: [] };
|
|
825
|
+
const captures = [];
|
|
826
|
+
for (const output of spec.contextOutputs) {
|
|
827
|
+
if (output.snapshot !== void 0) {
|
|
828
|
+
captures.push({ spec, key: output.key, value: (0, import_redact_secrets.redactSecrets)(snapshot) });
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
if (output.witness !== void 0) {
|
|
832
|
+
const read = (0, import_collection_selection.readCollectionWitness)(witness, output.witness);
|
|
833
|
+
if (!read.ok) {
|
|
834
|
+
return {
|
|
835
|
+
ok: false,
|
|
836
|
+
detail: `fixture capture "${output.key}" for handle "${spec.handle}" could not be built: ${read.detail}`
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
captures.push({ spec, key: output.key, value: read.value });
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
const value = (0, import_path.resolvePath)((0, import_redact_secrets.redactSecrets)(snapshot), output.path);
|
|
843
|
+
if (value === void 0 || value === null || value === "") {
|
|
844
|
+
const pinned = spec.entityType === "product" ? `product "${String(snapshot.product_id ?? spec.handle)}"` : `pricing option "${String(snapshot.pricing_option_id ?? spec.handle)}" on product "${spec.parentProductHandle}"`;
|
|
845
|
+
return {
|
|
846
|
+
ok: false,
|
|
847
|
+
path: output.path,
|
|
848
|
+
detail: `fixture capture "${output.key}" (handle "${spec.handle}", pinned ${pinned}) found no usable value at selected-entity path "${output.path}" \u2014 resolved to ${value === void 0 ? "absent" : JSON.stringify(value)}. Either the path does not exist on this entity shape (top-level keys: ${Object.keys(snapshot).join(", ")}), or this seller does not populate it. If the field is OPTIONAL in the schema (fixed_price and min_spend_per_package both are), capture the whole entity with { key: "${output.key}", snapshot: selected_entity } so absence stays absence. Add a match predicate for "${output.path}" only when carrying that field is a real eligibility requirement \u2014 gating on it stops a conformant seller qualifying at all.`
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
captures.push({ spec, key: output.key, value });
|
|
852
|
+
}
|
|
853
|
+
for (const capture of captures) {
|
|
854
|
+
const unusable = describeUncapturableValue(capture.value, capture.key);
|
|
855
|
+
if (unusable) return { ok: false, detail: unusable };
|
|
856
|
+
}
|
|
857
|
+
return { ok: true, captures };
|
|
677
858
|
}
|
|
678
859
|
function resolutionSpecKey(spec) {
|
|
679
860
|
return spec.entityType === "product" ? `seed_product\0${spec.handle}` : `seed_pricing_option\0${spec.parentProductHandle}\0${spec.handle}`;
|
|
@@ -779,6 +960,8 @@ async function discoverProductCatalog(client, options, context) {
|
|
|
779
960
|
const seenCursors = /* @__PURE__ */ new Set();
|
|
780
961
|
let cursor;
|
|
781
962
|
let pageCount = 0;
|
|
963
|
+
const exchanges = [];
|
|
964
|
+
const pageOfProduct = /* @__PURE__ */ new Map();
|
|
782
965
|
do {
|
|
783
966
|
if (pageCount >= 1e3) {
|
|
784
967
|
return { ok: false, error: "fixture discovery pagination exceeded the 1,000-page safety limit" };
|
|
@@ -792,6 +975,7 @@ async function discoverProductCatalog(client, options, context) {
|
|
|
792
975
|
ext: { adcp: { creative_wire: "canonical" } }
|
|
793
976
|
};
|
|
794
977
|
let result;
|
|
978
|
+
const pageStart = Date.now();
|
|
795
979
|
try {
|
|
796
980
|
result = await (0, import_task_map.executeStoryboardTask)(client, "get_products", request, { signal: options.signal });
|
|
797
981
|
} catch (err) {
|
|
@@ -801,6 +985,19 @@ async function discoverProductCatalog(client, options, context) {
|
|
|
801
985
|
};
|
|
802
986
|
}
|
|
803
987
|
pageCount++;
|
|
988
|
+
const retainResponse = exchanges.length < DISCOVERY_RETAINED_RESPONSE_PAGES;
|
|
989
|
+
const pageIndex = exchanges.push({
|
|
990
|
+
request: { transport: "mcp", operation: "get_products", payload: (0, import_redact_secrets.redactSecrets)(request) },
|
|
991
|
+
response: {
|
|
992
|
+
transport: "mcp",
|
|
993
|
+
payload: retainResponse ? (0, import_redact_secrets.redactSecrets)(
|
|
994
|
+
result.data ?? (result.adcp_error ? { adcp_error: result.adcp_error } : void 0) ?? result.error ?? null
|
|
995
|
+
) : {
|
|
996
|
+
response_not_retained: `page ${exchanges.length + 1} is past the runner's ${DISCOVERY_RETAINED_RESPONSE_PAGES}-page discovery-evidence budget; the request for this page is recorded in full`
|
|
997
|
+
},
|
|
998
|
+
duration_ms: Date.now() - pageStart
|
|
999
|
+
}
|
|
1000
|
+
}) - 1;
|
|
804
1001
|
if (!result.success) {
|
|
805
1002
|
return {
|
|
806
1003
|
ok: false,
|
|
@@ -862,6 +1059,7 @@ async function discoverProductCatalog(client, options, context) {
|
|
|
862
1059
|
seenPricingIds.add(option.pricing_option_id);
|
|
863
1060
|
}
|
|
864
1061
|
}
|
|
1062
|
+
pageOfProduct.set(product.product_id, pageIndex);
|
|
865
1063
|
products.push(product);
|
|
866
1064
|
if (products.length > 1e5) {
|
|
867
1065
|
return { ok: false, error: "fixture discovery exceeded the 100,000-product safety limit" };
|
|
@@ -885,7 +1083,12 @@ async function discoverProductCatalog(client, options, context) {
|
|
|
885
1083
|
} while (cursor);
|
|
886
1084
|
return {
|
|
887
1085
|
ok: true,
|
|
888
|
-
catalog: {
|
|
1086
|
+
catalog: {
|
|
1087
|
+
products,
|
|
1088
|
+
evidence: { page_count: pageCount, product_count: products.length, cursors },
|
|
1089
|
+
exchanges,
|
|
1090
|
+
pageOfProduct
|
|
1091
|
+
}
|
|
889
1092
|
};
|
|
890
1093
|
}
|
|
891
1094
|
function isPlainRecord(value) {
|
|
@@ -894,19 +1097,47 @@ function isPlainRecord(value) {
|
|
|
894
1097
|
function utf8Compare(a, b) {
|
|
895
1098
|
return Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"));
|
|
896
1099
|
}
|
|
897
|
-
function selectDiscoveredFixture(spec, products, bindings, claimedProducts, claimedPricingOptions) {
|
|
1100
|
+
function selectDiscoveredFixture(spec, products, bindings, claimedProducts, claimedPricingOptions, dependentPricingSpecs = []) {
|
|
898
1101
|
if (spec.entityType === "product") {
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1102
|
+
const rejections = /* @__PURE__ */ new Map();
|
|
1103
|
+
let claimedElsewhere = 0;
|
|
1104
|
+
let strandedChild = 0;
|
|
1105
|
+
const matched = [];
|
|
1106
|
+
for (const product2 of products) {
|
|
1107
|
+
const match = (0, import_fixture_resolution.matchFixtureRequirementsWithWitness)(product2, spec.clauses);
|
|
1108
|
+
if (!match.matched) {
|
|
1109
|
+
const reason = match.reason ?? "did not satisfy the authored match predicates";
|
|
1110
|
+
rejections.set(reason, (rejections.get(reason) ?? 0) + 1);
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
if (!identityCanBeClaimed(claimedProducts, product2.product_id, spec.allowReuse)) {
|
|
1114
|
+
claimedElsewhere++;
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
if (!dependentPricingSpecs.every((child) => productCanSatisfyPricingSpec(product2, child))) {
|
|
1118
|
+
strandedChild++;
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
matched.push({ product: product2, ...match.witness && { witness: match.witness } });
|
|
1122
|
+
}
|
|
1123
|
+
matched.sort((a, b) => utf8Compare(a.product.product_id, b.product.product_id));
|
|
1124
|
+
const selected2 = matched[0]?.product;
|
|
1125
|
+
if (!selected2) {
|
|
1126
|
+
return {
|
|
1127
|
+
ok: false,
|
|
1128
|
+
failed: false,
|
|
1129
|
+
detail: describeProductNoMatch(spec, products.length, rejections, claimedElsewhere, strandedChild)
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
903
1132
|
const productId = selected2.product_id;
|
|
904
1133
|
bindings.bindProduct(spec.handle, productId);
|
|
905
1134
|
recordIdentityClaim(claimedProducts, productId, spec.allowReuse);
|
|
906
1135
|
return {
|
|
907
1136
|
ok: true,
|
|
908
1137
|
boundIds: { product_id: productId },
|
|
909
|
-
detail: `selected product_id "${productId}" by UTF-8 bytewise order
|
|
1138
|
+
detail: `selected product_id "${productId}" by UTF-8 bytewise order`,
|
|
1139
|
+
snapshot: selected2,
|
|
1140
|
+
...matched[0].witness && { witness: matched[0].witness }
|
|
910
1141
|
};
|
|
911
1142
|
}
|
|
912
1143
|
const parentHandle = spec.parentProductHandle;
|
|
@@ -941,7 +1172,7 @@ function selectDiscoveredFixture(spec, products, bindings, claimedProducts, clai
|
|
|
941
1172
|
return {
|
|
942
1173
|
ok: false,
|
|
943
1174
|
failed: false,
|
|
944
|
-
detail:
|
|
1175
|
+
detail: `no pricing option on bound product "${sellerProductId}" satisfied fixture handle "${parentHandle}/${spec.handle}" \u2014 ${options.length} option(s) on the pinned product` + (options.length > 0 ? `, pricing_option_id(s): ${options.filter(isPlainRecord).map((option) => String(option.pricing_option_id)).slice(0, 10).join(", ")}` : "")
|
|
945
1176
|
};
|
|
946
1177
|
}
|
|
947
1178
|
const pricingOptionId = selected.pricing_option_id;
|
|
@@ -950,9 +1181,38 @@ function selectDiscoveredFixture(spec, products, bindings, claimedProducts, clai
|
|
|
950
1181
|
return {
|
|
951
1182
|
ok: true,
|
|
952
1183
|
boundIds: { product_id: sellerProductId, pricing_option_id: pricingOptionId },
|
|
953
|
-
detail: `selected pricing option "${sellerProductId}\0${pricingOptionId}" by UTF-8 bytewise order
|
|
1184
|
+
detail: `selected pricing option "${sellerProductId}\0${pricingOptionId}" by UTF-8 bytewise order`,
|
|
1185
|
+
// The scoped price comes out of the same pinned product graph, so a
|
|
1186
|
+
// captured currency / fixed_price / min_spend_per_package describes the
|
|
1187
|
+
// option the buy will actually reference.
|
|
1188
|
+
snapshot: selected
|
|
954
1189
|
};
|
|
955
1190
|
}
|
|
1191
|
+
function describeProductNoMatch(spec, candidateCount, rejections, claimedElsewhere, strandedChild) {
|
|
1192
|
+
const parts = [
|
|
1193
|
+
`no discovered product satisfied fixture handle "${spec.handle}" \u2014 ${candidateCount} candidate(s) enumerated from the complete paginated catalog`
|
|
1194
|
+
];
|
|
1195
|
+
const top = [...rejections].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([reason, count]) => `${count}x ${reason}`);
|
|
1196
|
+
if (top.length > 0) parts.push(`rejected by the authored match: ${top.join(" | ")}`);
|
|
1197
|
+
if (claimedElsewhere > 0) {
|
|
1198
|
+
parts.push(
|
|
1199
|
+
`${claimedElsewhere} otherwise-matching product(s) were already pinned to another handle \u2014 set allow_reuse: true on this declaration to share one seller entity`
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1202
|
+
if (strandedChild > 0) {
|
|
1203
|
+
parts.push(
|
|
1204
|
+
`${strandedChild} matched but carried no pricing option the declared child pricing_options could bind \u2014 loosen the child match, or give the child a seed strategy`
|
|
1205
|
+
);
|
|
1206
|
+
}
|
|
1207
|
+
return parts.join(". ");
|
|
1208
|
+
}
|
|
1209
|
+
function productCanSatisfyPricingSpec(product, child) {
|
|
1210
|
+
if (child.strategies.length !== 1 || child.strategies[0] !== "discover") return true;
|
|
1211
|
+
const options = Array.isArray(product.pricing_options) ? product.pricing_options : [];
|
|
1212
|
+
return options.filter(isPlainRecord).some(
|
|
1213
|
+
(option) => typeof option.pricing_option_id === "string" && option.pricing_option_id.length > 0 && (0, import_fixture_resolution.matchesFixtureRequirements)(option, child.clauses)
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
956
1216
|
function identityCanBeClaimed(claims, identity, allowReuse) {
|
|
957
1217
|
const existingAllowsReuse = claims.get(identity);
|
|
958
1218
|
return existingAllowsReuse === void 0 || existingAllowsReuse && allowReuse;
|