@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
|
@@ -8,8 +8,12 @@ import {
|
|
|
8
8
|
buildFixtureResolutionSpecs,
|
|
9
9
|
applyFixtureBindingsToRequest,
|
|
10
10
|
FixtureBindingRegistry,
|
|
11
|
-
matchesFixtureRequirements
|
|
11
|
+
matchesFixtureRequirements,
|
|
12
|
+
matchFixtureRequirementsWithWitness
|
|
12
13
|
} from "./fixture-resolution.mjs";
|
|
14
|
+
import { readCollectionWitness } from "./collection-selection.mjs";
|
|
15
|
+
import { resolvePath } from "./path.mjs";
|
|
16
|
+
import { redactSecrets } from "../../utils/redact-secrets.mjs";
|
|
13
17
|
const CONTROLLER_SEEDING_PHASE_ID = "__controller_seeding__";
|
|
14
18
|
function buildSeedCalls(fixtures) {
|
|
15
19
|
if (!fixtures) return [];
|
|
@@ -359,6 +363,10 @@ async function runRoutedControllerSeeding(storyboard, calls, context, resolveFix
|
|
|
359
363
|
failedCount
|
|
360
364
|
};
|
|
361
365
|
}
|
|
366
|
+
function discoveryExchangeFor(catalog, productId) {
|
|
367
|
+
const index = productId === void 0 ? void 0 : catalog.pageOfProduct.get(productId);
|
|
368
|
+
return catalog.exchanges[index ?? catalog.exchanges.length - 1];
|
|
369
|
+
}
|
|
362
370
|
async function runDeclaredFixtureResolution(seedClient, discoveryClient, storyboard, options, context, calls, resolveFixtureAgent) {
|
|
363
371
|
const start = Date.now();
|
|
364
372
|
const authoringErrorResult = buildAuthoringErrorResult(storyboard, calls, context, start);
|
|
@@ -387,6 +395,11 @@ async function runDeclaredFixtureResolution(seedClient, discoveryClient, storybo
|
|
|
387
395
|
const claimedProducts = /* @__PURE__ */ new Map();
|
|
388
396
|
const claimedPricingOptions = /* @__PURE__ */ new Map();
|
|
389
397
|
const productStatuses = /* @__PURE__ */ new Map();
|
|
398
|
+
const pendingCaptures = [];
|
|
399
|
+
const retainedWitnesses = /* @__PURE__ */ new Map();
|
|
400
|
+
let captureFailure;
|
|
401
|
+
const captureFailures = /* @__PURE__ */ new Map();
|
|
402
|
+
const captureFailedHandles = /* @__PURE__ */ new Set();
|
|
390
403
|
const seedContext = { correlation_id: `${storyboardCorrelationPrefix(storyboard)}--__seeding__` };
|
|
391
404
|
let seedOptions = options;
|
|
392
405
|
let discoveryOptions = options;
|
|
@@ -519,6 +532,43 @@ async function runDeclaredFixtureResolution(seedClient, discoveryClient, storybo
|
|
|
519
532
|
const data = raw.data;
|
|
520
533
|
if (raw.success && data?.success === true) {
|
|
521
534
|
boundIds = bindSeededFixture(spec, bindings);
|
|
535
|
+
const seedSnapshot = {
|
|
536
|
+
...spec.fixture,
|
|
537
|
+
...spec.entityType === "product" ? { product_id: boundIds.product_id } : { pricing_option_id: boundIds.pricing_option_id }
|
|
538
|
+
};
|
|
539
|
+
const seedWitness = matchFixtureRequirementsWithWitness(seedSnapshot, spec.clauses).witness;
|
|
540
|
+
if (seedWitness) retainedWitnesses.set(spec.handle, seedWitness);
|
|
541
|
+
const seedCaptures = collectDeclaredCaptures(spec, seedSnapshot, seedWitness);
|
|
542
|
+
if (!seedCaptures.ok) {
|
|
543
|
+
failure = seedCaptures.detail;
|
|
544
|
+
captureFailure ??= seedCaptures.detail;
|
|
545
|
+
captureFailures.set(spec.handle, {
|
|
546
|
+
...seedCaptures.path !== void 0 && { pointer: toJsonPointer(seedCaptures.path) },
|
|
547
|
+
exchange: {
|
|
548
|
+
request: {
|
|
549
|
+
transport: "mcp",
|
|
550
|
+
operation: "comply_test_controller",
|
|
551
|
+
// The object actually handed to `callControllerRaw`,
|
|
552
|
+
// not `controllerRequest` — the call substitutes
|
|
553
|
+
// `call.params` when the bound params are not a plain
|
|
554
|
+
// record, and adds the seeding correlation context.
|
|
555
|
+
payload: redactSecrets({
|
|
556
|
+
scenario: call.scenario,
|
|
557
|
+
params: isPlainRecord(controllerRequest.params) ? controllerRequest.params : call.params,
|
|
558
|
+
context: seedContext
|
|
559
|
+
})
|
|
560
|
+
},
|
|
561
|
+
response: { transport: "mcp", payload: redactSecrets(raw.data ?? raw.error ?? null) }
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
captureFailedHandles.add(spec.handle);
|
|
565
|
+
attempts.push({ strategy, disposition: "failed", detail: seedCaptures.detail });
|
|
566
|
+
boundIds = void 0;
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
pendingCaptures.push(
|
|
570
|
+
...seedCaptures.captures.map((c) => ({ ...c, sourceTask: "comply_test_controller" }))
|
|
571
|
+
);
|
|
522
572
|
if (spec.entityType === "product") {
|
|
523
573
|
recordIdentityClaim(claimedProducts, boundIds.product_id, spec.allowReuse);
|
|
524
574
|
} else {
|
|
@@ -574,7 +624,10 @@ async function runDeclaredFixtureResolution(seedClient, discoveryClient, storybo
|
|
|
574
624
|
discovered.catalog.products,
|
|
575
625
|
bindings,
|
|
576
626
|
claimedProducts,
|
|
577
|
-
claimedPricingOptions
|
|
627
|
+
claimedPricingOptions,
|
|
628
|
+
spec.entityType === "product" ? specs.filter(
|
|
629
|
+
(candidate) => candidate.entityType === "product_pricing_option" && candidate.parentProductHandle === spec.handle
|
|
630
|
+
) : []
|
|
578
631
|
);
|
|
579
632
|
if (!selected.ok) {
|
|
580
633
|
if (selected.failed) {
|
|
@@ -597,6 +650,27 @@ async function runDeclaredFixtureResolution(seedClient, discoveryClient, storybo
|
|
|
597
650
|
}
|
|
598
651
|
boundIds = selected.boundIds;
|
|
599
652
|
chosenStrategy = strategy;
|
|
653
|
+
if (selected.witness) retainedWitnesses.set(spec.handle, selected.witness);
|
|
654
|
+
const collected = collectDeclaredCaptures(spec, selected.snapshot, selected.witness);
|
|
655
|
+
if (!collected.ok) {
|
|
656
|
+
failure = collected.detail;
|
|
657
|
+
captureFailure ??= collected.detail;
|
|
658
|
+
const failureExchange = discoveryExchangeFor(discovered.catalog, selected.boundIds.product_id);
|
|
659
|
+
captureFailures.set(spec.handle, {
|
|
660
|
+
...collected.path !== void 0 && { pointer: toJsonPointer(collected.path) },
|
|
661
|
+
...failureExchange && { exchange: failureExchange }
|
|
662
|
+
});
|
|
663
|
+
captureFailedHandles.add(spec.handle);
|
|
664
|
+
attempts.push({
|
|
665
|
+
strategy,
|
|
666
|
+
disposition: "failed",
|
|
667
|
+
detail: collected.detail,
|
|
668
|
+
response: { ...discovered.catalog.evidence, selected: selected.boundIds }
|
|
669
|
+
});
|
|
670
|
+
boundIds = void 0;
|
|
671
|
+
break;
|
|
672
|
+
}
|
|
673
|
+
pendingCaptures.push(...collected.captures.map((c) => ({ ...c, sourceTask: "get_products" })));
|
|
600
674
|
attempts.push({
|
|
601
675
|
strategy,
|
|
602
676
|
disposition: "resolved",
|
|
@@ -620,6 +694,7 @@ async function runDeclaredFixtureResolution(seedClient, discoveryClient, storybo
|
|
|
620
694
|
};
|
|
621
695
|
records.push(record);
|
|
622
696
|
if (status !== "unsatisfied") {
|
|
697
|
+
const captureFailed = status === "failed" && captureFailedHandles.has(spec.handle);
|
|
623
698
|
steps.push({
|
|
624
699
|
storyboard_id: storyboard.id,
|
|
625
700
|
step_id: call.step_id,
|
|
@@ -628,7 +703,29 @@ async function runDeclaredFixtureResolution(seedClient, discoveryClient, storybo
|
|
|
628
703
|
task: chosenStrategy === "discover" ? "get_products" : "comply_test_controller",
|
|
629
704
|
passed: status !== "failed",
|
|
630
705
|
duration_ms: Date.now() - stepStart,
|
|
631
|
-
|
|
706
|
+
// An unbuildable capture gets a `check` discriminator like the other
|
|
707
|
+
// two failure classes, so a report renderer keys on `check` for all
|
|
708
|
+
// three instead of regexing prose for this one.
|
|
709
|
+
validations: captureFailed ? [
|
|
710
|
+
{
|
|
711
|
+
check: "fixture_capture_unbuildable",
|
|
712
|
+
passed: false,
|
|
713
|
+
description: "Every declared fixture capture must be buildable from the pinned entity",
|
|
714
|
+
error: failure,
|
|
715
|
+
// The contract's analogue, `capture_path_not_resolvable`,
|
|
716
|
+
// requires the RFC 6901 form of the `context_outputs` path
|
|
717
|
+
// that failed. `null` only for the witness and size/depth
|
|
718
|
+
// arms, which genuinely have no path.
|
|
719
|
+
json_pointer: captureFailures.get(spec.handle)?.pointer ?? null,
|
|
720
|
+
expected: "a value at the declared selected-entity path, or a derivable witness",
|
|
721
|
+
actual: null,
|
|
722
|
+
// required_on_failure: null only when the check is not a
|
|
723
|
+
// response_schema check, which this never is.
|
|
724
|
+
schema_id: null,
|
|
725
|
+
schema_url: null,
|
|
726
|
+
...captureFailures.get(spec.handle)?.exchange ?? {}
|
|
727
|
+
}
|
|
728
|
+
] : [],
|
|
632
729
|
context,
|
|
633
730
|
extraction: { path: "none" },
|
|
634
731
|
...failure && { error: failure }
|
|
@@ -639,6 +736,23 @@ async function runDeclaredFixtureResolution(seedClient, discoveryClient, storybo
|
|
|
639
736
|
else passedCount++;
|
|
640
737
|
}
|
|
641
738
|
const allPassed = failedCount === 0;
|
|
739
|
+
const graphComplete = allPassed && !fixtureUnsatisfied && captureFailure === void 0 && records.every((r) => r.status === "resolved");
|
|
740
|
+
const preparedCaptures = graphComplete && pendingCaptures.length > 0 ? {
|
|
741
|
+
values: Object.fromEntries(pendingCaptures.map((entry) => [entry.key, entry.value])),
|
|
742
|
+
provenance: Object.fromEntries(
|
|
743
|
+
pendingCaptures.map((entry) => [
|
|
744
|
+
entry.key,
|
|
745
|
+
{
|
|
746
|
+
source_step_id: CONTROLLER_SEEDING_PHASE_ID,
|
|
747
|
+
source_kind: "fixture_preparation",
|
|
748
|
+
source_task: entry.sourceTask,
|
|
749
|
+
fixture_handle: entry.spec.handle,
|
|
750
|
+
...entry.spec.parentProductHandle && { fixture_product_handle: entry.spec.parentProductHandle }
|
|
751
|
+
}
|
|
752
|
+
])
|
|
753
|
+
),
|
|
754
|
+
witnesses: Object.fromEntries(retainedWitnesses)
|
|
755
|
+
} : void 0;
|
|
642
756
|
return {
|
|
643
757
|
phase: {
|
|
644
758
|
phase_id: CONTROLLER_SEEDING_PHASE_ID,
|
|
@@ -652,8 +766,76 @@ async function runDeclaredFixtureResolution(seedClient, discoveryClient, storybo
|
|
|
652
766
|
failedCount,
|
|
653
767
|
fixtureUnsatisfied,
|
|
654
768
|
bindings,
|
|
655
|
-
resolutionRecords: records
|
|
769
|
+
resolutionRecords: records,
|
|
770
|
+
...preparedCaptures && { preparedCaptures }
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
function toJsonPointer(path) {
|
|
774
|
+
if (path === void 0 || path.length === 0) return void 0;
|
|
775
|
+
const segments = path.replace(/\[(\d+)\]/g, ".$1").split(".").filter((segment) => segment.length > 0).map((segment) => segment.replace(/~/g, "~0").replace(/\//g, "~1"));
|
|
776
|
+
return segments.length > 0 ? `/${segments.join("/")}` : void 0;
|
|
777
|
+
}
|
|
778
|
+
const DISCOVERY_RETAINED_RESPONSE_PAGES = 16;
|
|
779
|
+
const CAPTURE_MAX_DEPTH = 64;
|
|
780
|
+
const CAPTURE_MAX_BYTES = 512 * 1024;
|
|
781
|
+
function describeUncapturableValue(value, key) {
|
|
782
|
+
let depth = 0;
|
|
783
|
+
const walk = (node, level) => {
|
|
784
|
+
if (level > depth) depth = level;
|
|
785
|
+
if (depth > CAPTURE_MAX_DEPTH) return false;
|
|
786
|
+
if (node === null || typeof node !== "object") return true;
|
|
787
|
+
if (Array.isArray(node)) return node.every((item) => walk(item, level + 1));
|
|
788
|
+
return Object.values(node).every((item) => walk(item, level + 1));
|
|
656
789
|
};
|
|
790
|
+
if (!walk(value, 1)) {
|
|
791
|
+
return `fixture capture "${key}" nests deeper than the ${CAPTURE_MAX_DEPTH}-level capture limit`;
|
|
792
|
+
}
|
|
793
|
+
let size;
|
|
794
|
+
try {
|
|
795
|
+
size = Buffer.byteLength(JSON.stringify(value) ?? "", "utf8");
|
|
796
|
+
} catch {
|
|
797
|
+
return `fixture capture "${key}" is not JSON-serializable and cannot be detached`;
|
|
798
|
+
}
|
|
799
|
+
if (size > CAPTURE_MAX_BYTES) {
|
|
800
|
+
return `fixture capture "${key}" serializes to ${size} bytes, over the ${CAPTURE_MAX_BYTES}-byte capture limit`;
|
|
801
|
+
}
|
|
802
|
+
return void 0;
|
|
803
|
+
}
|
|
804
|
+
function collectDeclaredCaptures(spec, snapshot, witness) {
|
|
805
|
+
if (spec.contextOutputs.length === 0) return { ok: true, captures: [] };
|
|
806
|
+
const captures = [];
|
|
807
|
+
for (const output of spec.contextOutputs) {
|
|
808
|
+
if (output.snapshot !== void 0) {
|
|
809
|
+
captures.push({ spec, key: output.key, value: redactSecrets(snapshot) });
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
812
|
+
if (output.witness !== void 0) {
|
|
813
|
+
const read = readCollectionWitness(witness, output.witness);
|
|
814
|
+
if (!read.ok) {
|
|
815
|
+
return {
|
|
816
|
+
ok: false,
|
|
817
|
+
detail: `fixture capture "${output.key}" for handle "${spec.handle}" could not be built: ${read.detail}`
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
captures.push({ spec, key: output.key, value: read.value });
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
const value = resolvePath(redactSecrets(snapshot), output.path);
|
|
824
|
+
if (value === void 0 || value === null || value === "") {
|
|
825
|
+
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}"`;
|
|
826
|
+
return {
|
|
827
|
+
ok: false,
|
|
828
|
+
path: output.path,
|
|
829
|
+
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.`
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
captures.push({ spec, key: output.key, value });
|
|
833
|
+
}
|
|
834
|
+
for (const capture of captures) {
|
|
835
|
+
const unusable = describeUncapturableValue(capture.value, capture.key);
|
|
836
|
+
if (unusable) return { ok: false, detail: unusable };
|
|
837
|
+
}
|
|
838
|
+
return { ok: true, captures };
|
|
657
839
|
}
|
|
658
840
|
function resolutionSpecKey(spec) {
|
|
659
841
|
return spec.entityType === "product" ? `seed_product\0${spec.handle}` : `seed_pricing_option\0${spec.parentProductHandle}\0${spec.handle}`;
|
|
@@ -759,6 +941,8 @@ async function discoverProductCatalog(client, options, context) {
|
|
|
759
941
|
const seenCursors = /* @__PURE__ */ new Set();
|
|
760
942
|
let cursor;
|
|
761
943
|
let pageCount = 0;
|
|
944
|
+
const exchanges = [];
|
|
945
|
+
const pageOfProduct = /* @__PURE__ */ new Map();
|
|
762
946
|
do {
|
|
763
947
|
if (pageCount >= 1e3) {
|
|
764
948
|
return { ok: false, error: "fixture discovery pagination exceeded the 1,000-page safety limit" };
|
|
@@ -772,6 +956,7 @@ async function discoverProductCatalog(client, options, context) {
|
|
|
772
956
|
ext: { adcp: { creative_wire: "canonical" } }
|
|
773
957
|
};
|
|
774
958
|
let result;
|
|
959
|
+
const pageStart = Date.now();
|
|
775
960
|
try {
|
|
776
961
|
result = await executeStoryboardTask(client, "get_products", request, { signal: options.signal });
|
|
777
962
|
} catch (err) {
|
|
@@ -781,6 +966,19 @@ async function discoverProductCatalog(client, options, context) {
|
|
|
781
966
|
};
|
|
782
967
|
}
|
|
783
968
|
pageCount++;
|
|
969
|
+
const retainResponse = exchanges.length < DISCOVERY_RETAINED_RESPONSE_PAGES;
|
|
970
|
+
const pageIndex = exchanges.push({
|
|
971
|
+
request: { transport: "mcp", operation: "get_products", payload: redactSecrets(request) },
|
|
972
|
+
response: {
|
|
973
|
+
transport: "mcp",
|
|
974
|
+
payload: retainResponse ? redactSecrets(
|
|
975
|
+
result.data ?? (result.adcp_error ? { adcp_error: result.adcp_error } : void 0) ?? result.error ?? null
|
|
976
|
+
) : {
|
|
977
|
+
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`
|
|
978
|
+
},
|
|
979
|
+
duration_ms: Date.now() - pageStart
|
|
980
|
+
}
|
|
981
|
+
}) - 1;
|
|
784
982
|
if (!result.success) {
|
|
785
983
|
return {
|
|
786
984
|
ok: false,
|
|
@@ -842,6 +1040,7 @@ async function discoverProductCatalog(client, options, context) {
|
|
|
842
1040
|
seenPricingIds.add(option.pricing_option_id);
|
|
843
1041
|
}
|
|
844
1042
|
}
|
|
1043
|
+
pageOfProduct.set(product.product_id, pageIndex);
|
|
845
1044
|
products.push(product);
|
|
846
1045
|
if (products.length > 1e5) {
|
|
847
1046
|
return { ok: false, error: "fixture discovery exceeded the 100,000-product safety limit" };
|
|
@@ -865,7 +1064,12 @@ async function discoverProductCatalog(client, options, context) {
|
|
|
865
1064
|
} while (cursor);
|
|
866
1065
|
return {
|
|
867
1066
|
ok: true,
|
|
868
|
-
catalog: {
|
|
1067
|
+
catalog: {
|
|
1068
|
+
products,
|
|
1069
|
+
evidence: { page_count: pageCount, product_count: products.length, cursors },
|
|
1070
|
+
exchanges,
|
|
1071
|
+
pageOfProduct
|
|
1072
|
+
}
|
|
869
1073
|
};
|
|
870
1074
|
}
|
|
871
1075
|
function isPlainRecord(value) {
|
|
@@ -874,19 +1078,47 @@ function isPlainRecord(value) {
|
|
|
874
1078
|
function utf8Compare(a, b) {
|
|
875
1079
|
return Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"));
|
|
876
1080
|
}
|
|
877
|
-
function selectDiscoveredFixture(spec, products, bindings, claimedProducts, claimedPricingOptions) {
|
|
1081
|
+
function selectDiscoveredFixture(spec, products, bindings, claimedProducts, claimedPricingOptions, dependentPricingSpecs = []) {
|
|
878
1082
|
if (spec.entityType === "product") {
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1083
|
+
const rejections = /* @__PURE__ */ new Map();
|
|
1084
|
+
let claimedElsewhere = 0;
|
|
1085
|
+
let strandedChild = 0;
|
|
1086
|
+
const matched = [];
|
|
1087
|
+
for (const product2 of products) {
|
|
1088
|
+
const match = matchFixtureRequirementsWithWitness(product2, spec.clauses);
|
|
1089
|
+
if (!match.matched) {
|
|
1090
|
+
const reason = match.reason ?? "did not satisfy the authored match predicates";
|
|
1091
|
+
rejections.set(reason, (rejections.get(reason) ?? 0) + 1);
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
if (!identityCanBeClaimed(claimedProducts, product2.product_id, spec.allowReuse)) {
|
|
1095
|
+
claimedElsewhere++;
|
|
1096
|
+
continue;
|
|
1097
|
+
}
|
|
1098
|
+
if (!dependentPricingSpecs.every((child) => productCanSatisfyPricingSpec(product2, child))) {
|
|
1099
|
+
strandedChild++;
|
|
1100
|
+
continue;
|
|
1101
|
+
}
|
|
1102
|
+
matched.push({ product: product2, ...match.witness && { witness: match.witness } });
|
|
1103
|
+
}
|
|
1104
|
+
matched.sort((a, b) => utf8Compare(a.product.product_id, b.product.product_id));
|
|
1105
|
+
const selected2 = matched[0]?.product;
|
|
1106
|
+
if (!selected2) {
|
|
1107
|
+
return {
|
|
1108
|
+
ok: false,
|
|
1109
|
+
failed: false,
|
|
1110
|
+
detail: describeProductNoMatch(spec, products.length, rejections, claimedElsewhere, strandedChild)
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
883
1113
|
const productId = selected2.product_id;
|
|
884
1114
|
bindings.bindProduct(spec.handle, productId);
|
|
885
1115
|
recordIdentityClaim(claimedProducts, productId, spec.allowReuse);
|
|
886
1116
|
return {
|
|
887
1117
|
ok: true,
|
|
888
1118
|
boundIds: { product_id: productId },
|
|
889
|
-
detail: `selected product_id "${productId}" by UTF-8 bytewise order
|
|
1119
|
+
detail: `selected product_id "${productId}" by UTF-8 bytewise order`,
|
|
1120
|
+
snapshot: selected2,
|
|
1121
|
+
...matched[0].witness && { witness: matched[0].witness }
|
|
890
1122
|
};
|
|
891
1123
|
}
|
|
892
1124
|
const parentHandle = spec.parentProductHandle;
|
|
@@ -921,7 +1153,7 @@ function selectDiscoveredFixture(spec, products, bindings, claimedProducts, clai
|
|
|
921
1153
|
return {
|
|
922
1154
|
ok: false,
|
|
923
1155
|
failed: false,
|
|
924
|
-
detail:
|
|
1156
|
+
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(", ")}` : "")
|
|
925
1157
|
};
|
|
926
1158
|
}
|
|
927
1159
|
const pricingOptionId = selected.pricing_option_id;
|
|
@@ -930,9 +1162,38 @@ function selectDiscoveredFixture(spec, products, bindings, claimedProducts, clai
|
|
|
930
1162
|
return {
|
|
931
1163
|
ok: true,
|
|
932
1164
|
boundIds: { product_id: sellerProductId, pricing_option_id: pricingOptionId },
|
|
933
|
-
detail: `selected pricing option "${sellerProductId}\0${pricingOptionId}" by UTF-8 bytewise order
|
|
1165
|
+
detail: `selected pricing option "${sellerProductId}\0${pricingOptionId}" by UTF-8 bytewise order`,
|
|
1166
|
+
// The scoped price comes out of the same pinned product graph, so a
|
|
1167
|
+
// captured currency / fixed_price / min_spend_per_package describes the
|
|
1168
|
+
// option the buy will actually reference.
|
|
1169
|
+
snapshot: selected
|
|
934
1170
|
};
|
|
935
1171
|
}
|
|
1172
|
+
function describeProductNoMatch(spec, candidateCount, rejections, claimedElsewhere, strandedChild) {
|
|
1173
|
+
const parts = [
|
|
1174
|
+
`no discovered product satisfied fixture handle "${spec.handle}" \u2014 ${candidateCount} candidate(s) enumerated from the complete paginated catalog`
|
|
1175
|
+
];
|
|
1176
|
+
const top = [...rejections].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([reason, count]) => `${count}x ${reason}`);
|
|
1177
|
+
if (top.length > 0) parts.push(`rejected by the authored match: ${top.join(" | ")}`);
|
|
1178
|
+
if (claimedElsewhere > 0) {
|
|
1179
|
+
parts.push(
|
|
1180
|
+
`${claimedElsewhere} otherwise-matching product(s) were already pinned to another handle \u2014 set allow_reuse: true on this declaration to share one seller entity`
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
if (strandedChild > 0) {
|
|
1184
|
+
parts.push(
|
|
1185
|
+
`${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`
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
return parts.join(". ");
|
|
1189
|
+
}
|
|
1190
|
+
function productCanSatisfyPricingSpec(product, child) {
|
|
1191
|
+
if (child.strategies.length !== 1 || child.strategies[0] !== "discover") return true;
|
|
1192
|
+
const options = Array.isArray(product.pricing_options) ? product.pricing_options : [];
|
|
1193
|
+
return options.filter(isPlainRecord).some(
|
|
1194
|
+
(option) => typeof option.pricing_option_id === "string" && option.pricing_option_id.length > 0 && matchesFixtureRequirements(option, child.clauses)
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
936
1197
|
function identityCanBeClaimed(claims, identity, allowReuse) {
|
|
937
1198
|
const existingAllowsReuse = claims.get(identity);
|
|
938
1199
|
return existingAllowsReuse === void 0 || existingAllowsReuse && allowReuse;
|