@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
|
@@ -48,6 +48,7 @@ __export(runner_exports, {
|
|
|
48
48
|
getFirstStepPreview: () => getFirstStepPreview,
|
|
49
49
|
listStrictOnlyFailures: () => listStrictOnlyFailures,
|
|
50
50
|
prepareGovernedStoryboardWireArgs: () => prepareGovernedStoryboardWireArgs,
|
|
51
|
+
prepareStoryboardFixtures: () => prepareStoryboardFixtures,
|
|
51
52
|
resolveCapabilityPath: () => resolveCapabilityPath,
|
|
52
53
|
runStoryboard: () => runStoryboard,
|
|
53
54
|
runStoryboardStep: () => runStoryboardStep,
|
|
@@ -69,6 +70,8 @@ var import_rawResponseCapture = require('../../protocols/rawResponseCapture.js')
|
|
|
69
70
|
var import_task_map = require('./task-map.js');
|
|
70
71
|
var import_functional_dispatch = require('./request-signing/functional-dispatch.js');
|
|
71
72
|
var import_context = require('./context.js');
|
|
73
|
+
var import_protected_context = require('./protected-context.js');
|
|
74
|
+
var import_prepared_fixtures = require('./prepared-fixtures.js');
|
|
72
75
|
var import_rejection_hints = require('./rejection-hints.js');
|
|
73
76
|
var import_shape_drift_hints = require('./shape-drift-hints.js');
|
|
74
77
|
var import_strict_validation_hints = require('./strict-validation-hints.js');
|
|
@@ -79,6 +82,7 @@ var import_rate_limit_trip = require('./rate-limit-trip.js');
|
|
|
79
82
|
var import_redact_secrets = require('../../utils/redact-secrets.js');
|
|
80
83
|
var import_property_list_reference = require('./property-list-reference.js');
|
|
81
84
|
var import_response_unwrapper = require('../../utils/response-unwrapper.js');
|
|
85
|
+
var import_errors = require('../../errors/index.js');
|
|
82
86
|
var import_envelope_status_compat = require('../../utils/envelope-status-compat.js');
|
|
83
87
|
var import_test_controller = require('../test-controller.js');
|
|
84
88
|
var import_constants = require('../../upstream-recorder/constants.js');
|
|
@@ -123,12 +127,35 @@ function getPreparedTrustedMatchPublisherAuth(options) {
|
|
|
123
127
|
function withPreparedTrustedMatchPublisherAuth(options, prepared) {
|
|
124
128
|
return { ...options, [PREPARED_TRUSTED_MATCH_PUBLISHER_AUTH]: prepared };
|
|
125
129
|
}
|
|
130
|
+
function tryProtectedContextPatch(context, patch, options) {
|
|
131
|
+
try {
|
|
132
|
+
(0, import_protected_context.applyProtectedContextPatch)(context, patch, options);
|
|
133
|
+
return void 0;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (!(error instanceof import_protected_context.ProtectedContextViolation)) throw error;
|
|
136
|
+
return {
|
|
137
|
+
check: "protected_context_violation",
|
|
138
|
+
passed: false,
|
|
139
|
+
description: "Reserved fixture-preparation context keys are immutable for the rest of the run",
|
|
140
|
+
error: error.message,
|
|
141
|
+
json_pointer: null,
|
|
142
|
+
expected: "no write, delete or metadata change to the __adcp_fixture_ namespace",
|
|
143
|
+
actual: error.keys.join(", "),
|
|
144
|
+
// required_on_failure, and null outside `response_schema`. Omitting
|
|
145
|
+
// the keys is not the same as recording null — a consumer cannot
|
|
146
|
+
// tell "no schema applies" from "the runner forgot".
|
|
147
|
+
schema_id: null,
|
|
148
|
+
schema_url: null
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
126
152
|
const SKIP_DETAILS = {
|
|
127
153
|
not_applicable: "Not applicable: agent did not declare the protocol or specialism this storyboard targets.",
|
|
128
154
|
no_phases: "Storyboard has no executable phases (placeholder).",
|
|
129
155
|
prerequisite_failed: "Skipped: a prerequisite step or contract did not pass.",
|
|
130
156
|
missing_tool: "Skipped: agent did not advertise the required tool.",
|
|
131
157
|
missing_test_controller: "Skipped: deterministic_testing phase requires comply_test_controller, which the agent did not advertise.",
|
|
158
|
+
fixture_unsatisfied: "Skipped: every enabled strategy for a declared fixture handle was exhausted without finding or creating a qualifying entity.",
|
|
132
159
|
fixture_unavailable: "Skipped: the runner test kit cannot synthesize a valid input for the seller-declared contract.",
|
|
133
160
|
unsatisfied_contract: "Skipped: test-kit contract is out of scope for this grading run.",
|
|
134
161
|
requirement_unmet: "Skipped: a requires: tag named a runtime requirement that is not available on this run (see RunnerSkipResult.requirement).",
|
|
@@ -490,6 +517,17 @@ function responseNotApplicableContextKeys(step, gate) {
|
|
|
490
517
|
if (gate.context_keys?.length) return gate.context_keys;
|
|
491
518
|
return (step.context_outputs ?? []).filter((o) => o.path === "pagination.cursor").map((o) => o.key).filter((key) => typeof key === "string" && key.length > 0);
|
|
492
519
|
}
|
|
520
|
+
const FIXTURE_CAPTURE_STEP_ID = "__fixture_capture__";
|
|
521
|
+
function describeFixtureCaptureTask(captures, refusedKeys) {
|
|
522
|
+
for (const key of refusedKeys) {
|
|
523
|
+
const task = captures?.provenance?.[key]?.source_task;
|
|
524
|
+
if (typeof task === "string" && task.length > 0) return task;
|
|
525
|
+
}
|
|
526
|
+
const distinct = new Set(
|
|
527
|
+
Object.values(captures?.provenance ?? {}).map((entry) => entry.source_task).filter((task) => typeof task === "string" && task.length > 0)
|
|
528
|
+
);
|
|
529
|
+
return distinct.size === 1 ? [...distinct][0] : import_loader.VALIDATION_ONLY_TASK;
|
|
530
|
+
}
|
|
493
531
|
function responseDerivedContextResult(runState) {
|
|
494
532
|
const entries = runState.responseDerivedNotApplicableContextKeys;
|
|
495
533
|
return entries && entries.size > 0 ? { response_derived_not_applicable_context_keys: Object.fromEntries(entries) } : {};
|
|
@@ -781,27 +819,53 @@ async function runStoryboard(agentUrlOrUrls, storyboard, options = {}) {
|
|
|
781
819
|
"Credential-isolated property-list references currently require MCP; A2A probe coverage is unavailable."
|
|
782
820
|
);
|
|
783
821
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
typeof agentUrlOrUrls === "string" ? agentUrlOrUrls : agentUrlOrUrls[0],
|
|
787
|
-
options
|
|
788
|
-
);
|
|
789
|
-
const discovered = await (0, import_client.getOrDiscoverProfile)(resolution.client, options);
|
|
790
|
-
options = {
|
|
791
|
-
...options,
|
|
792
|
-
_profile: discovered.step.passed ? discovered.profile : void 0,
|
|
793
|
-
agentTools: discovered.step.passed ? discovered.profile.tools : void 0
|
|
794
|
-
};
|
|
795
|
-
}
|
|
822
|
+
storyboard = (0, import_loader.normalizeValidationOnlyTasks)(storyboard);
|
|
823
|
+
(0, import_loader.validateStoryboardShape)(storyboard);
|
|
796
824
|
const schemaRoot = getRunSchemaRoot(options);
|
|
797
|
-
const
|
|
798
|
-
options
|
|
799
|
-
(
|
|
800
|
-
|
|
825
|
+
const gatedBody = async () => {
|
|
826
|
+
const metadataRefusal = describeReservedCallerOptions(options);
|
|
827
|
+
if (metadataRefusal !== void 0) {
|
|
828
|
+
return denyPreparedFixtures(
|
|
829
|
+
options,
|
|
830
|
+
buildPreparedRefusalRunResult(resolveBindingAgentUrls(agentUrlOrUrls, options), storyboard, {
|
|
831
|
+
code: "unauthorized_metadata",
|
|
832
|
+
detail: metadataRefusal
|
|
833
|
+
}),
|
|
834
|
+
"unauthorized_metadata"
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
if (options.prepared_fixtures !== void 0) {
|
|
838
|
+
const urls = resolveBindingAgentUrls(agentUrlOrUrls, options);
|
|
839
|
+
const gate = await gatePreparedFixtures(storyboard, urls, options, void 0, "run");
|
|
840
|
+
if (gate.used && "refusal" in gate) {
|
|
841
|
+
return denyPreparedFixtures(
|
|
842
|
+
options,
|
|
843
|
+
buildPreparedRefusalRunResult(urls, storyboard, gate.refusal),
|
|
844
|
+
gate.refusal.code
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
if ((0, import_client.hasTestClientScope)() && options._client && !options.agents) {
|
|
849
|
+
const resolution = (0, import_client.getOrCreateClientResolution)(
|
|
850
|
+
typeof agentUrlOrUrls === "string" ? agentUrlOrUrls : agentUrlOrUrls[0],
|
|
851
|
+
options
|
|
852
|
+
);
|
|
853
|
+
const discovered = await (0, import_client.getOrDiscoverProfile)(resolution.client, options);
|
|
854
|
+
options = {
|
|
855
|
+
...options,
|
|
856
|
+
_profile: discovered.step.passed ? discovered.profile : void 0,
|
|
857
|
+
agentTools: discovered.step.passed ? discovered.profile.tools : void 0
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
return (0, import_schema_loader.withServedReleaseSchemaRoots)(
|
|
861
|
+
options.servedReleaseSchemaRoots,
|
|
862
|
+
() => runStoryboardBody(agentUrlOrUrls, storyboard, options)
|
|
863
|
+
);
|
|
864
|
+
};
|
|
801
865
|
if (schemaRoot) {
|
|
802
|
-
return await (0, import_schema_loader.withExternalSchemaRoot)(schemaRoot.adcpVersion, schemaRoot.schemaRoot,
|
|
866
|
+
return await (0, import_schema_loader.withExternalSchemaRoot)(schemaRoot.adcpVersion, schemaRoot.schemaRoot, gatedBody);
|
|
803
867
|
}
|
|
804
|
-
return await
|
|
868
|
+
return await gatedBody();
|
|
805
869
|
},
|
|
806
870
|
{ isolate: !(0, import_client.hasTestClientScope)() }
|
|
807
871
|
)
|
|
@@ -1562,10 +1626,380 @@ function runnerNoticeKey(notice) {
|
|
|
1562
1626
|
function collectStepNotices(phases) {
|
|
1563
1627
|
return phases.flatMap((phase) => phase.steps.flatMap((step) => step.notices ?? []));
|
|
1564
1628
|
}
|
|
1629
|
+
const CREATIVE_PROBE_CONTEXT_SUFFIXES = [
|
|
1630
|
+
"capability_id",
|
|
1631
|
+
"target_format_id",
|
|
1632
|
+
"message",
|
|
1633
|
+
"creative_manifest",
|
|
1634
|
+
"creative_id",
|
|
1635
|
+
"transformer_id",
|
|
1636
|
+
"config"
|
|
1637
|
+
];
|
|
1638
|
+
function createRunContext(storyboard, options) {
|
|
1639
|
+
const protectedWriter = (0, import_protected_context.createProtectedContextWriter)();
|
|
1640
|
+
const context = {};
|
|
1641
|
+
(0, import_protected_context.registerProtectedContext)(context, protectedWriter);
|
|
1642
|
+
(0, import_protected_context.applyProtectedContextPatch)(
|
|
1643
|
+
context,
|
|
1644
|
+
{ set: { ...storyboard.context } },
|
|
1645
|
+
{ origin: `[${storyboard.id}] storyboard.context` }
|
|
1646
|
+
);
|
|
1647
|
+
(0, import_protected_context.applyProtectedContextPatch)(context, { set: { ...options.context } }, { origin: "options.context" });
|
|
1648
|
+
if (storyboard.context) (0, import_context.forwardAliasCache)(storyboard.context, context);
|
|
1649
|
+
if (options.context) (0, import_context.forwardAliasCache)(options.context, context);
|
|
1650
|
+
return { context, protectedWriter };
|
|
1651
|
+
}
|
|
1652
|
+
function describeReservedCallerMetadata(map, location) {
|
|
1653
|
+
if (!map) return void 0;
|
|
1654
|
+
const offending = Object.keys(map).filter(
|
|
1655
|
+
(key) => (0, import_protected_context.isProtectedContextKey)(key) || key === "__proto__" || key === "constructor" || key === "prototype"
|
|
1656
|
+
);
|
|
1657
|
+
if (offending.length === 0) return void 0;
|
|
1658
|
+
return `${location}: cannot carry reserved or prototype-sensitive key(s) ${offending.join(", ")}; fixture-preparation metadata is not caller-supplied. Rename the key to anything not starting with "__adcp_fixture_" and not named __proto__, constructor or prototype \u2014 or, if you meant to reuse a prepared graph, pass it as options.prepared_fixtures with an authorizer instead of threading its context back.`;
|
|
1659
|
+
}
|
|
1660
|
+
function describeReservedCallerOptions(options) {
|
|
1661
|
+
return describeReservedCallerMetadata(options.context, "options.context") ?? describeReservedCallerMetadata(options.context_provenance, "options.context_provenance") ?? describeReservedCallerMetadata(
|
|
1662
|
+
options.response_derived_not_applicable_context_keys,
|
|
1663
|
+
"options.response_derived_not_applicable_context_keys"
|
|
1664
|
+
);
|
|
1665
|
+
}
|
|
1666
|
+
function describeStepAuthDirective(directive) {
|
|
1667
|
+
if (directive === "none") return "none";
|
|
1668
|
+
if (typeof directive !== "object") return JSON.stringify(directive);
|
|
1669
|
+
const parts = [JSON.stringify(directive.type)];
|
|
1670
|
+
for (const [key, value] of Object.entries(directive).sort(
|
|
1671
|
+
([a], [b]) => a < b ? -1 : 1
|
|
1672
|
+
)) {
|
|
1673
|
+
if (key === "type") continue;
|
|
1674
|
+
parts.push(JSON.stringify([key, value]));
|
|
1675
|
+
}
|
|
1676
|
+
return parts.join(";");
|
|
1677
|
+
}
|
|
1678
|
+
function resolveBindingAgentUrls(agentUrlOrUrls, options) {
|
|
1679
|
+
if (options.agents) return Object.values(options.agents).map((entry) => entry.url);
|
|
1680
|
+
return Array.isArray(agentUrlOrUrls) ? agentUrlOrUrls : [agentUrlOrUrls];
|
|
1681
|
+
}
|
|
1682
|
+
async function gatePreparedFixtures(storyboard, agentUrls, options, step, gate = "pass") {
|
|
1683
|
+
const outcome = await (0, import_prepared_fixtures.resolvePreparedFixtures)({
|
|
1684
|
+
supplied: options.prepared_fixtures,
|
|
1685
|
+
// The run context is built here so the binding can report the account
|
|
1686
|
+
// the step's request will ACTUALLY carry. Without it the standalone
|
|
1687
|
+
// entry gate — the one `runStoryboardStep` uses — reported no
|
|
1688
|
+
// `request_account_scope` at all, and a caller-supplied or
|
|
1689
|
+
// context-substituted foreign account reached the seller while the
|
|
1690
|
+
// authorizer saw nothing to refuse. Building the context performs no
|
|
1691
|
+
// I/O and is what the run would build moments later anyway.
|
|
1692
|
+
current: buildCurrentPreparedBinding(
|
|
1693
|
+
storyboard,
|
|
1694
|
+
agentUrls,
|
|
1695
|
+
options,
|
|
1696
|
+
step,
|
|
1697
|
+
options,
|
|
1698
|
+
step !== void 0 ? createRunContext(storyboard, options).context : void 0
|
|
1699
|
+
),
|
|
1700
|
+
...options.prepared_fixtures_authorizer && { authorizer: options.prepared_fixtures_authorizer },
|
|
1701
|
+
...step !== void 0 && { stepId: step.id },
|
|
1702
|
+
gate
|
|
1703
|
+
});
|
|
1704
|
+
if (outcome === void 0) return { used: false };
|
|
1705
|
+
if (!outcome.ok) return { used: true, refusal: outcome.refusal };
|
|
1706
|
+
return { used: true, state: outcome.state };
|
|
1707
|
+
}
|
|
1708
|
+
function hydratePreparedCaptures(state, context, protectedWriter, contextProvenance) {
|
|
1709
|
+
const captures = state.captures;
|
|
1710
|
+
if (Object.keys(captures).length === 0) return void 0;
|
|
1711
|
+
return tryProtectedContextPatch(
|
|
1712
|
+
context,
|
|
1713
|
+
{ set: captures, setProvenance: state.captureProvenance },
|
|
1714
|
+
{ writer: protectedWriter, sinks: { provenance: contextProvenance }, origin: "prepared fixture state" }
|
|
1715
|
+
);
|
|
1716
|
+
}
|
|
1717
|
+
async function prepareStoryboardFixtures(agentUrl, storyboard, options = {}) {
|
|
1718
|
+
options = applyReusableProfileOptions(options);
|
|
1719
|
+
return (0, import_property_list_reference.withPropertyListCredentialRedaction)(
|
|
1720
|
+
() => (0, import_protocols.withMCPConnectionScope)(
|
|
1721
|
+
async () => {
|
|
1722
|
+
storyboard = (0, import_loader.normalizeValidationOnlyTasks)(storyboard);
|
|
1723
|
+
(0, import_loader.validateStoryboardShape)(storyboard);
|
|
1724
|
+
options = applyStoryboardVersionOptions(storyboard, options);
|
|
1725
|
+
options = (0, import_functional_dispatch.applyFunctionalRequestSigning)(options, {
|
|
1726
|
+
...options.complianceDir && { complianceDir: options.complianceDir },
|
|
1727
|
+
...options.adcpVersion && { version: options.adcpVersion }
|
|
1728
|
+
});
|
|
1729
|
+
options = (0, import_test_kit.resolveDeclaredTestKit)(storyboard, options);
|
|
1730
|
+
options = (0, import_native_a2a_compliance.applyNativeA2AComplianceTransportOptions)(options);
|
|
1731
|
+
const schemaRoot = getRunSchemaRoot(options);
|
|
1732
|
+
const body = () => (0, import_schema_loader.withServedReleaseSchemaRoots)(
|
|
1733
|
+
options.servedReleaseSchemaRoots,
|
|
1734
|
+
() => prepareStoryboardFixturesBody(agentUrl, storyboard, options)
|
|
1735
|
+
);
|
|
1736
|
+
if (schemaRoot) {
|
|
1737
|
+
return await (0, import_schema_loader.withExternalSchemaRoot)(schemaRoot.adcpVersion, schemaRoot.schemaRoot, body);
|
|
1738
|
+
}
|
|
1739
|
+
return await body();
|
|
1740
|
+
},
|
|
1741
|
+
{ isolate: !(0, import_client.hasTestClientScope)() }
|
|
1742
|
+
)
|
|
1743
|
+
);
|
|
1744
|
+
}
|
|
1745
|
+
function resolvePreparationRouteOptions(agentUrl, options) {
|
|
1746
|
+
const agents = options.agents;
|
|
1747
|
+
if (!agents) return options;
|
|
1748
|
+
const match = Object.entries(agents).find(([, entry2]) => entry2?.url === agentUrl);
|
|
1749
|
+
if (!match) return options;
|
|
1750
|
+
const [, entry] = match;
|
|
1751
|
+
return {
|
|
1752
|
+
...options,
|
|
1753
|
+
...entry?.auth !== void 0 && { auth: entry.auth },
|
|
1754
|
+
...entry?.transport !== void 0 && { protocol: entry.transport }
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
async function prepareStoryboardFixturesBody(agentUrl, storyboard, options) {
|
|
1758
|
+
const agentUrls = resolveBindingAgentUrls(agentUrl, options);
|
|
1759
|
+
const metadataRefusal = describeReservedCallerOptions(options);
|
|
1760
|
+
if (metadataRefusal !== void 0) {
|
|
1761
|
+
return buildPreparedFixtures(storyboard, agentUrls, options, void 0, {
|
|
1762
|
+
status: "failed",
|
|
1763
|
+
detail: metadataRefusal
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
if (options.prepared_fixtures !== void 0) {
|
|
1767
|
+
return buildPreparedFixtures(storyboard, agentUrls, options, void 0, {
|
|
1768
|
+
status: "failed",
|
|
1769
|
+
detail: "prepareStoryboardFixtures() does not accept prepared_fixtures: it produces prepared state, it does not consume it. Passing state here authorizes nothing \u2014 the authorizer is never consulted \u2014 and preparation would re-resolve the graph against the seller anyway. Pass the state to runStoryboard() or runStoryboardStep() instead, with prepared_fixtures_authorizer. See docs/guides/COLLECTION-FIXTURE-FOUNDATION.md section 4."
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1772
|
+
const callerOwnsClients = (0, import_client.hasTestClientScope)();
|
|
1773
|
+
const preparationOptions = resolvePreparationRouteOptions(agentUrls[0], options);
|
|
1774
|
+
const resolution = (0, import_client.getOrCreateClientResolution)(agentUrls[0], preparationOptions);
|
|
1775
|
+
const client = resolution.client;
|
|
1776
|
+
const bindingOptions = options;
|
|
1777
|
+
options = preparationOptions;
|
|
1778
|
+
try {
|
|
1779
|
+
let profile = options._profile;
|
|
1780
|
+
if (!profile && !resolution.reusedShared) {
|
|
1781
|
+
const discovered = await (0, import_client.getOrDiscoverProfile)(client, options);
|
|
1782
|
+
if (discovered.step.passed === false) {
|
|
1783
|
+
return buildPreparedFixtures(storyboard, agentUrls, bindingOptions, void 0, {
|
|
1784
|
+
status: "failed",
|
|
1785
|
+
detail: discovered.step.error ?? "agent discovery failed before fixture preparation"
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
profile = discovered.profile;
|
|
1789
|
+
}
|
|
1790
|
+
if (profile && (!options._profile || !options.agentTools)) {
|
|
1791
|
+
options = {
|
|
1792
|
+
...options,
|
|
1793
|
+
...!options._profile && { _profile: profile },
|
|
1794
|
+
...!options.agentTools && { agentTools: (0, import_agent_routing.normalizeAgentToolNames)(profile.tools) }
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
|
+
const context = createRunContext(storyboard, options).context;
|
|
1798
|
+
const seeding = await (0, import_seeding.runControllerSeeding)(client, storyboard, options, context);
|
|
1799
|
+
if (!seeding) {
|
|
1800
|
+
return buildPreparedFixtures(storyboard, agentUrls, bindingOptions, void 0, { status: "resolved" });
|
|
1801
|
+
}
|
|
1802
|
+
const disposition = seeding.missingController ? { status: "unsupported", detail: "agent does not advertise comply_test_controller" } : seeding.seedUnsupported ? { status: "unsupported", detail: "agent did not advertise a required seed_* scenario" } : seeding.failedCount > 0 ? {
|
|
1803
|
+
status: "failed",
|
|
1804
|
+
detail: seeding.phase.steps.find((step) => step.error)?.error ?? "fixture resolution failed during preparation"
|
|
1805
|
+
} : seeding.fixtureUnsatisfied === true ? { status: "unsatisfied", detail: describeUnsatisfiedFixtures(seeding.resolutionRecords) } : { status: "resolved" };
|
|
1806
|
+
return buildPreparedFixtures(storyboard, agentUrls, bindingOptions, seeding, disposition);
|
|
1807
|
+
} finally {
|
|
1808
|
+
if (!callerOwnsClients) await (0, import_protocols.closeScopedConnections)(options.protocol);
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
function describeUnsatisfiedFixtures(records) {
|
|
1812
|
+
const unsatisfied = (records ?? []).filter((record) => record.status === "unsatisfied");
|
|
1813
|
+
if (unsatisfied.length === 0) return "a declared strategy ladder exhausted without a binding";
|
|
1814
|
+
const names = unsatisfied.map(
|
|
1815
|
+
(record) => record.fixture_type === "pricing_option" && record.product_handle ? `pricing_option:${record.product_handle}/${record.handle}` : `${record.fixture_type}:${record.handle}`
|
|
1816
|
+
);
|
|
1817
|
+
return `fixture_unsatisfied: ${names.join(", ")}`;
|
|
1818
|
+
}
|
|
1819
|
+
function buildPreparedFixtures(storyboard, agentUrls, options, seeding, disposition) {
|
|
1820
|
+
const captures = disposition.status === "resolved" ? seeding?.preparedCaptures?.values ?? {} : {};
|
|
1821
|
+
const captureProvenance = disposition.status === "resolved" ? seeding?.preparedCaptures?.provenance ?? {} : {};
|
|
1822
|
+
const witnesses = disposition.status === "resolved" ? seeding?.preparedCaptures?.witnesses ?? {} : {};
|
|
1823
|
+
const unsatisfied = (seeding?.resolutionRecords ?? []).filter((record) => record.status === "unsatisfied");
|
|
1824
|
+
return (0, import_prepared_fixtures.createPreparedStoryboardFixtures)({
|
|
1825
|
+
binding: buildCurrentPreparedBinding(storyboard, agentUrls, options),
|
|
1826
|
+
...seeding?.bindings && { bindings: seeding.bindings },
|
|
1827
|
+
resolutions: seeding?.resolutionRecords ?? [],
|
|
1828
|
+
coverageGaps: unsatisfied.length > 0 ? [
|
|
1829
|
+
{
|
|
1830
|
+
reason: "fixture_unsatisfied",
|
|
1831
|
+
detail: "detail" in disposition ? disposition.detail : "a declared strategy ladder exhausted without a binding",
|
|
1832
|
+
fixtures: unsatisfied.map((record) => ({
|
|
1833
|
+
fixture_type: record.fixture_type,
|
|
1834
|
+
handle: record.handle,
|
|
1835
|
+
...record.product_handle && { product_handle: record.product_handle },
|
|
1836
|
+
requirements: record.requirements
|
|
1837
|
+
}))
|
|
1838
|
+
}
|
|
1839
|
+
] : [],
|
|
1840
|
+
captures,
|
|
1841
|
+
captureProvenance,
|
|
1842
|
+
witnesses,
|
|
1843
|
+
disposition,
|
|
1844
|
+
...seeding?.phase && { phase: seeding.phase }
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
function buildCurrentPreparedBinding(storyboard, agentUrls, options, step, runOptions, context, selectedRoute, runnerVars) {
|
|
1848
|
+
return (0, import_prepared_fixtures.buildPreparedBinding)({
|
|
1849
|
+
storyboard,
|
|
1850
|
+
agentUrls,
|
|
1851
|
+
...options.adcpVersion && { adcpVersion: options.adcpVersion },
|
|
1852
|
+
...(runOptions ?? options).agents && {
|
|
1853
|
+
agentRouteMap: Object.entries((runOptions ?? options).agents ?? {}).map(
|
|
1854
|
+
([key, entry]) => [key, entry?.url ?? ""]
|
|
1855
|
+
)
|
|
1856
|
+
},
|
|
1857
|
+
accountScope: resolveBindingAccountScope(storyboard, options),
|
|
1858
|
+
brandScope: resolveBindingBrandScope(storyboard, options),
|
|
1859
|
+
...step !== void 0 && { ...resolveBindingRequestAccountScope(step, context, options, runnerVars) },
|
|
1860
|
+
sandbox: !(options.disable_sandbox === true || options.sandbox === false),
|
|
1861
|
+
...options.prepared_fixtures_principal !== void 0 && { principal: options.prepared_fixtures_principal },
|
|
1862
|
+
// The diffed credential always comes from the RUN options. A routed
|
|
1863
|
+
// dispatch flattens `agents[key].auth` into a bare `auth` on its
|
|
1864
|
+
// per-step view, so deriving it from `assignment.options` would make an
|
|
1865
|
+
// unchanged routed credential read as a mismatch and refuse every step
|
|
1866
|
+
// of every routed run. The step's own view feeds the separate,
|
|
1867
|
+
// undiffed `step_credential_fingerprint` instead.
|
|
1868
|
+
credential: resolveBindingCredential(runOptions ?? options),
|
|
1869
|
+
...(() => {
|
|
1870
|
+
if (step === void 0) return {};
|
|
1871
|
+
const stepCredential = resolveBindingCredential(options, step);
|
|
1872
|
+
if (stepCredential === resolveBindingCredential(runOptions ?? options)) return {};
|
|
1873
|
+
return {
|
|
1874
|
+
stepCredential,
|
|
1875
|
+
// The fingerprint is an HMAC under a per-process key, so a host can
|
|
1876
|
+
// observe that it changed but never what it is. This companion is the
|
|
1877
|
+
// value an authorizer can actually branch on.
|
|
1878
|
+
stepAuthKind: step.auth === void 0 ? "routed" : step.auth === "none" ? "none" : "override"
|
|
1879
|
+
};
|
|
1880
|
+
})(),
|
|
1881
|
+
...step !== void 0 && runOptions !== void 0 && (runOptions.agents || selectedRoute) ? resolveBindingStepRoute(step, runOptions, selectedRoute) : {},
|
|
1882
|
+
// Never mint one here: `current.run_id` is handed to the authorizer as
|
|
1883
|
+
// "identity of the preparation run", and a fresh UUID per lookup is
|
|
1884
|
+
// noise for a host trying to correlate on it.
|
|
1885
|
+
runId: options.prepared_fixtures_run_id ?? ""
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1888
|
+
function resolveBindingStepRoute(step, runOptions, selected) {
|
|
1889
|
+
if (typeof selected?.agentUrl === "string" && selected.agentUrl.length > 0) {
|
|
1890
|
+
return { stepRoute: `${selected.agentKey ?? "#default"}@${(0, import_oauth_metadata_graph.redactOAuthUrlForOutput)(selected.agentUrl)}` };
|
|
1891
|
+
}
|
|
1892
|
+
const agents = runOptions.agents ?? {};
|
|
1893
|
+
const key = step.agent;
|
|
1894
|
+
if (typeof key !== "string" || !Object.hasOwn(agents, key)) return {};
|
|
1895
|
+
const url = agents[key]?.url;
|
|
1896
|
+
if (typeof url !== "string") return {};
|
|
1897
|
+
return { stepRoute: `${key}@${(0, import_oauth_metadata_graph.redactOAuthUrlForOutput)(url)}` };
|
|
1898
|
+
}
|
|
1899
|
+
function resolveBindingRequestAccountScope(step, context, options, runnerVars) {
|
|
1900
|
+
if (!context) return {};
|
|
1901
|
+
let account;
|
|
1902
|
+
try {
|
|
1903
|
+
const resolvedTask = (0, import_agent_routing.resolveTaskName)(step, options) ?? step.task;
|
|
1904
|
+
const effectiveStep = resolvedTask === step.task ? step : { ...step, task: resolvedTask };
|
|
1905
|
+
account = buildStepRequest(step, effectiveStep, context, options, runnerVars).account;
|
|
1906
|
+
} catch {
|
|
1907
|
+
return {};
|
|
1908
|
+
}
|
|
1909
|
+
if (account === void 0 || account === null) return {};
|
|
1910
|
+
return { requestAccountScope: canonicalizeScope(account) };
|
|
1911
|
+
}
|
|
1912
|
+
function resolveBindingBrandScope(storyboard, options) {
|
|
1913
|
+
const merged = { ...storyboard.context, ...options.context };
|
|
1914
|
+
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1915
|
+
const contextAccount = record(merged.account);
|
|
1916
|
+
const brand = record(merged.brand) ?? record(contextAccount?.brand) ?? (0, import_client2.resolveBrand)(options);
|
|
1917
|
+
return canonicalizeScope(brand ?? {});
|
|
1918
|
+
}
|
|
1919
|
+
function canonicalizeScope(value) {
|
|
1920
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
|
|
1921
|
+
if (Array.isArray(value)) return `[${value.map(canonicalizeScope).join(",")}]`;
|
|
1922
|
+
return `{${Object.entries(value).filter(([, nested]) => nested !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, nested]) => `${JSON.stringify(key)}:${canonicalizeScope(nested)}`).join(",")}}`;
|
|
1923
|
+
}
|
|
1924
|
+
function resolveBindingAccountScope(storyboard, options) {
|
|
1925
|
+
const merged = { ...storyboard.context, ...options.context };
|
|
1926
|
+
const contextAccount = merged.account !== null && typeof merged.account === "object" && !Array.isArray(merged.account) ? merged.account : void 0;
|
|
1927
|
+
const account = contextAccount ?? (0, import_client2.resolveAccount)(options);
|
|
1928
|
+
return canonicalizeScope(account ?? {});
|
|
1929
|
+
}
|
|
1930
|
+
function resolveBindingCredential(options, step) {
|
|
1931
|
+
const parts = [];
|
|
1932
|
+
if (step?.auth !== void 0) {
|
|
1933
|
+
parts.push(`step_auth:${JSON.stringify(describeStepAuthDirective(step.auth))}`);
|
|
1934
|
+
}
|
|
1935
|
+
const headerEntries = Object.entries(options.headers ?? {}).sort(([a], [b]) => {
|
|
1936
|
+
const left = a.toLowerCase();
|
|
1937
|
+
const right = b.toLowerCase();
|
|
1938
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
1939
|
+
});
|
|
1940
|
+
for (const [name, value] of headerEntries) {
|
|
1941
|
+
if (value !== void 0 && value !== null)
|
|
1942
|
+
parts.push(`header:${JSON.stringify([name.toLowerCase(), String(value)])}`);
|
|
1943
|
+
}
|
|
1944
|
+
const describeAuth = (auth, label) => {
|
|
1945
|
+
if (!auth) return;
|
|
1946
|
+
if (auth.type === "bearer") parts.push(`${label}:bearer:${JSON.stringify(auth.token)}`);
|
|
1947
|
+
else if (auth.type === "basic") parts.push(`${label}:basic:${JSON.stringify([auth.username, auth.password])}`);
|
|
1948
|
+
else if (auth.type === "oauth") {
|
|
1949
|
+
parts.push(
|
|
1950
|
+
`${label}:oauth:${JSON.stringify([
|
|
1951
|
+
auth.client?.client_id ?? null,
|
|
1952
|
+
typeof auth.tokens?.refresh_token === "string" ? auth.tokens.refresh_token : null,
|
|
1953
|
+
typeof auth.tokens?.refresh_token === "string" ? null : auth.tokens ?? null
|
|
1954
|
+
])}`
|
|
1955
|
+
);
|
|
1956
|
+
} else {
|
|
1957
|
+
parts.push(
|
|
1958
|
+
`${label}:oauth_cc:${JSON.stringify([
|
|
1959
|
+
auth.credentials?.client_id ?? null,
|
|
1960
|
+
auth.credentials?.client_secret ?? null
|
|
1961
|
+
])}`
|
|
1962
|
+
);
|
|
1963
|
+
}
|
|
1964
|
+
};
|
|
1965
|
+
describeAuth(options.auth, "run");
|
|
1966
|
+
for (const [key, entry] of Object.entries(options.agents ?? {}).sort(([a], [b]) => a < b ? -1 : 1)) {
|
|
1967
|
+
describeAuth(entry?.auth, `agent:${JSON.stringify([key, entry?.url ?? null])}`);
|
|
1968
|
+
}
|
|
1969
|
+
const kit = options.test_kit;
|
|
1970
|
+
if (kit) {
|
|
1971
|
+
for (const [key, value] of Object.entries(kit).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
|
|
1972
|
+
if (key === "auth") {
|
|
1973
|
+
for (const [authField, authValue] of Object.entries(value ?? {}).sort(
|
|
1974
|
+
([a], [b]) => a < b ? -1 : a > b ? 1 : 0
|
|
1975
|
+
)) {
|
|
1976
|
+
if (authField === "probe_task") continue;
|
|
1977
|
+
if (authValue !== void 0 && authValue !== null) {
|
|
1978
|
+
parts.push(`test_kit:${JSON.stringify([`auth.${authField}`, authValue])}`);
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
continue;
|
|
1982
|
+
}
|
|
1983
|
+
if (value !== void 0 && value !== null) parts.push(`test_kit:${JSON.stringify([key, value])}`);
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
return parts.length > 0 ? parts.join("\0") : "none";
|
|
1987
|
+
}
|
|
1565
1988
|
async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOffset, preSeeded) {
|
|
1566
1989
|
const start = Date.now();
|
|
1567
1990
|
const isMultiInstance = agentUrls.length > 1;
|
|
1568
1991
|
const useRouting = options.agents !== void 0;
|
|
1992
|
+
let preparedGate = { used: false };
|
|
1993
|
+
if (options.prepared_fixtures !== void 0) {
|
|
1994
|
+
preparedGate = await gatePreparedFixtures(storyboard, agentUrls, options);
|
|
1995
|
+
if (preparedGate.used && "refusal" in preparedGate) {
|
|
1996
|
+
return denyPreparedFixtures(
|
|
1997
|
+
options,
|
|
1998
|
+
buildPreparedRefusalRunResult(agentUrls, storyboard, preparedGate.refusal),
|
|
1999
|
+
preparedGate.refusal.code
|
|
2000
|
+
);
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
1569
2003
|
let clients;
|
|
1570
2004
|
let routingContext;
|
|
1571
2005
|
let profile;
|
|
@@ -1632,7 +2066,7 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
1632
2066
|
}
|
|
1633
2067
|
}
|
|
1634
2068
|
}
|
|
1635
|
-
if (preSeeded === void 0) {
|
|
2069
|
+
if (preSeeded === void 0 && !preparedGate.used) {
|
|
1636
2070
|
const resetTargets = routingContext && options.agents ? [...routingContext.profiles.entries()].map(([key, profile2]) => ({
|
|
1637
2071
|
client: routingContext.clients.get(key),
|
|
1638
2072
|
options: (0, import_agent_routing.routedAgentOptions)(options.agents[key], options, profile2)
|
|
@@ -1667,7 +2101,9 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
1667
2101
|
const routedControllerRoutes = /* @__PURE__ */ new Map();
|
|
1668
2102
|
const routedErrors = /* @__PURE__ */ new Map();
|
|
1669
2103
|
const reportedRoutingErrors = /* @__PURE__ */ new Set();
|
|
1670
|
-
|
|
2104
|
+
const runContext = createRunContext(storyboard, options);
|
|
2105
|
+
const protectedWriter = runContext.protectedWriter;
|
|
2106
|
+
let context = runContext.context;
|
|
1671
2107
|
const routingFailedStep = (step, phaseId, err) => {
|
|
1672
2108
|
const detail = (0, import_oauth_metadata_graph.redactOAuthUrlsInText)(
|
|
1673
2109
|
err instanceof import_agent_routing.RoutingError ? err.message : err?.message ?? String(err)
|
|
@@ -1864,8 +2300,6 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
1864
2300
|
};
|
|
1865
2301
|
}
|
|
1866
2302
|
}
|
|
1867
|
-
if (storyboard.context) (0, import_context.forwardAliasCache)(storyboard.context, context);
|
|
1868
|
-
if (options.context) (0, import_context.forwardAliasCache)(options.context, context);
|
|
1869
2303
|
const contributions = /* @__PURE__ */ new Set();
|
|
1870
2304
|
const contributionSources = /* @__PURE__ */ new Map();
|
|
1871
2305
|
const priorStepResults = /* @__PURE__ */ new Map();
|
|
@@ -2023,6 +2457,7 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
2023
2457
|
skippedCount++;
|
|
2024
2458
|
}
|
|
2025
2459
|
let seedingPhaseResult = null;
|
|
2460
|
+
let probeViolationStep;
|
|
2026
2461
|
let seedingFailed = false;
|
|
2027
2462
|
let creativeBuildCatalogFailed = false;
|
|
2028
2463
|
let seedingMissingController = false;
|
|
@@ -2109,18 +2544,38 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
2109
2544
|
]
|
|
2110
2545
|
};
|
|
2111
2546
|
} else if (resolution.status === "resolved") {
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2547
|
+
const probeViolation = tryProtectedContextPatch(
|
|
2548
|
+
context,
|
|
2549
|
+
{
|
|
2550
|
+
unset: CREATIVE_PROBE_CONTEXT_SUFFIXES.map((key) => `${handle}_${key}`),
|
|
2551
|
+
set: resolution.context
|
|
2552
|
+
},
|
|
2553
|
+
{ origin: `creative_build_probe '${handle}'` }
|
|
2554
|
+
);
|
|
2555
|
+
if (probeViolation) {
|
|
2556
|
+
seedingFailed = true;
|
|
2557
|
+
failedCount += 1;
|
|
2558
|
+
probeViolationStep = {
|
|
2559
|
+
storyboard_id: storyboard.id,
|
|
2560
|
+
phase_id: "fixture_resolution",
|
|
2561
|
+
step_id: `creative_build_probe_context.${handle}`,
|
|
2562
|
+
title: "Publish creative build probe context",
|
|
2563
|
+
task: "get_adcp_capabilities",
|
|
2564
|
+
passed: false,
|
|
2565
|
+
duration_ms: 0,
|
|
2566
|
+
validations: [probeViolation],
|
|
2567
|
+
context: withoutProtectedCaptures(context),
|
|
2568
|
+
extraction: { path: "none" },
|
|
2569
|
+
error: probeViolation.error
|
|
2570
|
+
};
|
|
2571
|
+
seedingPhaseResult = {
|
|
2572
|
+
phase_id: "fixture_resolution",
|
|
2573
|
+
phase_title: "Creative build fixture resolution",
|
|
2574
|
+
passed: false,
|
|
2575
|
+
duration_ms: 0,
|
|
2576
|
+
steps: [probeViolationStep]
|
|
2577
|
+
};
|
|
2122
2578
|
}
|
|
2123
|
-
Object.assign(context, resolution.context);
|
|
2124
2579
|
options = { ...options, contracts: [.../* @__PURE__ */ new Set([...options.contracts ?? [], "creative_build_probe_runner"])] };
|
|
2125
2580
|
} else {
|
|
2126
2581
|
fixtureUnsatisfied = true;
|
|
@@ -2132,10 +2587,45 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
2132
2587
|
};
|
|
2133
2588
|
}
|
|
2134
2589
|
}
|
|
2590
|
+
let preparedFixtureState;
|
|
2591
|
+
let preparedRefused;
|
|
2592
|
+
{
|
|
2593
|
+
const prepared = preparedGate;
|
|
2594
|
+
if (prepared.used && "state" in prepared) {
|
|
2595
|
+
const hydrationViolation = hydratePreparedCaptures(prepared.state, context, protectedWriter, contextProvenance);
|
|
2596
|
+
if (hydrationViolation) {
|
|
2597
|
+
if (!callerOwnsClients) await (0, import_protocols.closeScopedConnections)(options.protocol);
|
|
2598
|
+
return buildDiscoveryFailedResult(agentUrls, storyboard, {
|
|
2599
|
+
step: "Publish prepared fixture captures",
|
|
2600
|
+
passed: false,
|
|
2601
|
+
duration_ms: 0,
|
|
2602
|
+
error: hydrationViolation.error
|
|
2603
|
+
});
|
|
2604
|
+
}
|
|
2605
|
+
preparedFixtureState = prepared.state;
|
|
2606
|
+
fixtureBindings = (0, import_prepared_fixtures.preparedBindingRegistryFor)(prepared.state);
|
|
2607
|
+
fixtureResolutionRecords = prepared.state.resolutions;
|
|
2608
|
+
const disposition = prepared.state.disposition;
|
|
2609
|
+
if (disposition.status === "failed") {
|
|
2610
|
+
if (!callerOwnsClients) await (0, import_protocols.closeScopedConnections)(options.protocol);
|
|
2611
|
+
return buildDiscoveryFailedResult(agentUrls, storyboard, {
|
|
2612
|
+
step: "Reuse prepared fixture state",
|
|
2613
|
+
passed: false,
|
|
2614
|
+
duration_ms: 0,
|
|
2615
|
+
error: disposition.detail
|
|
2616
|
+
});
|
|
2617
|
+
}
|
|
2618
|
+
if (disposition.status === "unsatisfied") {
|
|
2619
|
+
fixtureUnsatisfied = true;
|
|
2620
|
+
fixtureCoverageGap = prepared.state.coverageGaps[0] ?? fixtureCoverageGap;
|
|
2621
|
+
}
|
|
2622
|
+
if (disposition.status === "unsupported") seedingMissingController = true;
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2135
2625
|
{
|
|
2136
2626
|
let seeding;
|
|
2137
2627
|
try {
|
|
2138
|
-
seeding = creativeAssetFixtureGap || fixtureUnsatisfied || seedingFailed || skipControllerSeedingForPhaseGates ? null : preSeeded !== void 0 ? preSeeded.result : await (0, import_seeding.runControllerSeeding)(
|
|
2628
|
+
seeding = preparedFixtureState !== void 0 || creativeAssetFixtureGap || fixtureUnsatisfied || seedingFailed || skipControllerSeedingForPhaseGates ? null : preSeeded !== void 0 ? preSeeded.result : await (0, import_seeding.runControllerSeeding)(
|
|
2139
2629
|
clients[0],
|
|
2140
2630
|
storyboard,
|
|
2141
2631
|
options,
|
|
@@ -2189,6 +2679,10 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
2189
2679
|
const attach = preSeeded === void 0 || preSeeded.attach;
|
|
2190
2680
|
if (attach) {
|
|
2191
2681
|
seedingPhaseResult = seeding.phase;
|
|
2682
|
+
if (probeViolationStep && !seeding.phase.steps.some((entry) => entry.step_id === probeViolationStep.step_id)) {
|
|
2683
|
+
seeding.phase.steps.unshift({ ...probeViolationStep, phase_id: seeding.phase.phase_id });
|
|
2684
|
+
seeding.phase.passed = false;
|
|
2685
|
+
}
|
|
2192
2686
|
passedCount += seeding.passedCount;
|
|
2193
2687
|
failedCount += seeding.failedCount;
|
|
2194
2688
|
if (seeding.missingController || seeding.seedUnsupported || seeding.fixtureUnsatisfied) {
|
|
@@ -2196,6 +2690,56 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
2196
2690
|
}
|
|
2197
2691
|
}
|
|
2198
2692
|
fixtureBindings = seeding.bindings;
|
|
2693
|
+
if (seeding.preparedCaptures) {
|
|
2694
|
+
try {
|
|
2695
|
+
(0, import_protected_context.applyProtectedContextPatch)(
|
|
2696
|
+
context,
|
|
2697
|
+
{ set: seeding.preparedCaptures.values, setProvenance: seeding.preparedCaptures.provenance },
|
|
2698
|
+
{
|
|
2699
|
+
writer: protectedWriter,
|
|
2700
|
+
sinks: { provenance: contextProvenance },
|
|
2701
|
+
origin: "fixture preparation"
|
|
2702
|
+
}
|
|
2703
|
+
);
|
|
2704
|
+
} catch (error) {
|
|
2705
|
+
if (!(error instanceof import_protected_context.ProtectedContextViolation)) throw error;
|
|
2706
|
+
seedingFailed = true;
|
|
2707
|
+
seeding.phase.passed = false;
|
|
2708
|
+
const alreadyReported = seeding.phase.steps.some((entry) => entry.step_id === FIXTURE_CAPTURE_STEP_ID);
|
|
2709
|
+
if (attach && !alreadyReported) {
|
|
2710
|
+
seeding.phase.steps.push({
|
|
2711
|
+
storyboard_id: storyboard.id,
|
|
2712
|
+
step_id: FIXTURE_CAPTURE_STEP_ID,
|
|
2713
|
+
phase_id: seeding.phase.phase_id,
|
|
2714
|
+
title: "Publish fixture captures",
|
|
2715
|
+
task: describeFixtureCaptureTask(seeding.preparedCaptures, error.keys),
|
|
2716
|
+
passed: false,
|
|
2717
|
+
duration_ms: 0,
|
|
2718
|
+
validations: [
|
|
2719
|
+
{
|
|
2720
|
+
check: "fixture_capture_unbuildable",
|
|
2721
|
+
passed: false,
|
|
2722
|
+
description: "Every declared fixture capture must be detachable from the pinned entity",
|
|
2723
|
+
error: error.message,
|
|
2724
|
+
json_pointer: null,
|
|
2725
|
+
expected: "a capture the runner can deep-clone and freeze",
|
|
2726
|
+
actual: error.keys.join(", "),
|
|
2727
|
+
schema_id: null,
|
|
2728
|
+
schema_url: null
|
|
2729
|
+
}
|
|
2730
|
+
],
|
|
2731
|
+
// The commit failed, so nothing reserved is in `context` yet —
|
|
2732
|
+
// but the projection is applied unconditionally so a partially
|
|
2733
|
+
// applied patch can never surface a protected value in a
|
|
2734
|
+
// report.
|
|
2735
|
+
context: withoutProtectedCaptures(context),
|
|
2736
|
+
extraction: { path: "none" },
|
|
2737
|
+
error: error.message
|
|
2738
|
+
});
|
|
2739
|
+
failedCount += 1;
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2199
2743
|
fixtureResolutionRecords = fixtureResolutionRecords ? [...fixtureResolutionRecords, ...seeding.resolutionRecords ?? []] : seeding.resolutionRecords;
|
|
2200
2744
|
fixtureUnsatisfied = seeding.fixtureUnsatisfied === true && seeding.failedCount === 0;
|
|
2201
2745
|
if (fixtureUnsatisfied) {
|
|
@@ -2214,12 +2758,13 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
2214
2758
|
...record.product_handle && { product_handle: record.product_handle },
|
|
2215
2759
|
requirements: record.requirements
|
|
2216
2760
|
}));
|
|
2217
|
-
const names = fixtures.map(
|
|
2218
|
-
(fixture) => fixture.fixture_type === "pricing_option" ? `pricing_option "${fixture.product_handle}/${fixture.handle}"` : `product "${fixture.handle}"`
|
|
2219
|
-
);
|
|
2220
2761
|
fixtureCoverageGap = {
|
|
2221
2762
|
reason: "fixture_unsatisfied",
|
|
2222
|
-
|
|
2763
|
+
// The SAME spelling the disposition uses. Two renderings of one
|
|
2764
|
+
// reason over the same data meant a consumer parsing the
|
|
2765
|
+
// documented `<fixture_type>:<handle>` grammar matched on one
|
|
2766
|
+
// field and not the other.
|
|
2767
|
+
detail: describeUnsatisfiedFixtures(fixtureResolutionRecords),
|
|
2223
2768
|
fixtures
|
|
2224
2769
|
};
|
|
2225
2770
|
}
|
|
@@ -2430,7 +2975,9 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
2430
2975
|
if (!routingContext && phaseContainsOauthMetadataProbe(phase) && !agentAdvertisesOauth(profile)) {
|
|
2431
2976
|
phaseAbsent = true;
|
|
2432
2977
|
}
|
|
2978
|
+
const priorPhaseContext = context;
|
|
2433
2979
|
context = { ...context };
|
|
2980
|
+
(0, import_protected_context.forwardProtectedContext)(priorPhaseContext, context);
|
|
2434
2981
|
if (seedingMissingController || seedingUnsupported || seedingFailed) {
|
|
2435
2982
|
const cascadeSkip = seedingMissingController ? {
|
|
2436
2983
|
skip_reason: "missing_test_controller",
|
|
@@ -2602,7 +3149,7 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
2602
3149
|
);
|
|
2603
3150
|
const missingKeys = /* @__PURE__ */ new Set([
|
|
2604
3151
|
...(effectiveRequest.ok ? findUnresolvedContextVars(effectiveRequest.request) : []).filter((v) => hardKeys.has(v.key)).map((v) => v.key),
|
|
2605
|
-
...(step.context_inputs ?? []).filter((input) => !(input.key
|
|
3152
|
+
...(step.context_inputs ?? []).filter((input) => !Object.hasOwn(context, input.key) && hardKeys.has(input.key)).map((input) => input.key)
|
|
2606
3153
|
]);
|
|
2607
3154
|
if (missingKeys.size > 0) {
|
|
2608
3155
|
capabilityUnavailable = false;
|
|
@@ -2663,6 +3210,78 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
2663
3210
|
missingPrerequisiteContextKeysByPhase
|
|
2664
3211
|
);
|
|
2665
3212
|
}
|
|
3213
|
+
if (preparedFixtureState !== void 0) {
|
|
3214
|
+
const recheck = await (0, import_prepared_fixtures.resolvePreparedFixtures)({
|
|
3215
|
+
supplied: options.prepared_fixtures,
|
|
3216
|
+
// The EFFECTIVE scope for this step: the routed options actually
|
|
3217
|
+
// dispatching it, plus its own auth directive.
|
|
3218
|
+
current: buildCurrentPreparedBinding(
|
|
3219
|
+
storyboard,
|
|
3220
|
+
agentUrls,
|
|
3221
|
+
assignment.options ?? options,
|
|
3222
|
+
step,
|
|
3223
|
+
options,
|
|
3224
|
+
context,
|
|
3225
|
+
{
|
|
3226
|
+
...assignment.agentKey !== void 0 && { agentKey: assignment.agentKey },
|
|
3227
|
+
...assignment.agentUrl !== void 0 && { agentUrl: assignment.agentUrl }
|
|
3228
|
+
},
|
|
3229
|
+
runnerVars
|
|
3230
|
+
),
|
|
3231
|
+
...options.prepared_fixtures_authorizer && { authorizer: options.prepared_fixtures_authorizer },
|
|
3232
|
+
stepId: step.id,
|
|
3233
|
+
gate: "step"
|
|
3234
|
+
});
|
|
3235
|
+
if (recheck !== void 0 && !recheck.ok) {
|
|
3236
|
+
const refusalResult = {
|
|
3237
|
+
storyboard_id: storyboard.id,
|
|
3238
|
+
step_id: step.id,
|
|
3239
|
+
phase_id: phase.id,
|
|
3240
|
+
title: step.title,
|
|
3241
|
+
task: step.task,
|
|
3242
|
+
passed: false,
|
|
3243
|
+
duration_ms: 0,
|
|
3244
|
+
validations: [
|
|
3245
|
+
{
|
|
3246
|
+
check: "prepared_fixtures_authorization",
|
|
3247
|
+
passed: false,
|
|
3248
|
+
// One description for all three gates. Three spellings of
|
|
3249
|
+
// the same check read to a report renderer as three kinds.
|
|
3250
|
+
description: "Prepared fixture state is re-validated and re-authorized on every lookup",
|
|
3251
|
+
error: recheck.refusal.detail,
|
|
3252
|
+
json_pointer: null,
|
|
3253
|
+
expected: "an authorized prepared state bound to this exact step scope",
|
|
3254
|
+
actual: recheck.refusal.code,
|
|
3255
|
+
schema_id: null,
|
|
3256
|
+
schema_url: null,
|
|
3257
|
+
// Explicit nulls, not omission: this is a PRE-WIRE
|
|
3258
|
+
// refusal, and an absent key cannot be told apart from a
|
|
3259
|
+
// runner that forgot to record one.
|
|
3260
|
+
request: null,
|
|
3261
|
+
response: null
|
|
3262
|
+
}
|
|
3263
|
+
],
|
|
3264
|
+
// UNREDACTED. This result is never returned to the refused
|
|
3265
|
+
// caller — it goes to the host's authorized audit scope, where
|
|
3266
|
+
// withholding the evidence would defeat the point.
|
|
3267
|
+
context,
|
|
3268
|
+
extraction: { path: "none" },
|
|
3269
|
+
error: recheck.refusal.detail
|
|
3270
|
+
};
|
|
3271
|
+
stepResults.push(refusalResult);
|
|
3272
|
+
preparedRefused = recheck.refusal.code;
|
|
3273
|
+
failedCount++;
|
|
3274
|
+
phasePassed = false;
|
|
3275
|
+
countedAsFailed.add(refusalResult);
|
|
3276
|
+
priorStepResults.set(step.id, refusalResult);
|
|
3277
|
+
if (step.stateful) phaseStatefulCascades.set(phase.id, null);
|
|
3278
|
+
if (isMultiInstance || useRouting) {
|
|
3279
|
+
refusalResult.agent_url = (0, import_oauth_metadata_graph.redactOAuthUrlForOutput)(assignment.agentUrl);
|
|
3280
|
+
refusalResult.agent_index = assignment.instanceIndex + 1;
|
|
3281
|
+
}
|
|
3282
|
+
continue;
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
2666
3285
|
const rawResult = routedCapabilityDetail !== void 0 ? buildPhaseCapabilitySkippedSteps(
|
|
2667
3286
|
storyboard,
|
|
2668
3287
|
{ ...phase, steps: [step] },
|
|
@@ -2706,7 +3325,10 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
2706
3325
|
});
|
|
2707
3326
|
continue;
|
|
2708
3327
|
}
|
|
2709
|
-
const raw = await spec.onStep(assertionContexts.get(spec.id),
|
|
3328
|
+
const raw = await spec.onStep(assertionContexts.get(spec.id), {
|
|
3329
|
+
...result2,
|
|
3330
|
+
context: withoutProtectedCaptures(result2.context ?? {})
|
|
3331
|
+
});
|
|
2710
3332
|
for (const r of raw) {
|
|
2711
3333
|
const full = { ...r, assertion_id: spec.id, scope: "step", step_id: step.id };
|
|
2712
3334
|
assertionResults.push(full);
|
|
@@ -2984,6 +3606,8 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
2984
3606
|
...assertionResults.length > 0 ? { assertions: assertionResults } : {},
|
|
2985
3607
|
strict_validation_summary: strictSummary,
|
|
2986
3608
|
notices,
|
|
3609
|
+
// `fixture_resolutions` carries `seller_ids`, so it is withheld on the
|
|
3610
|
+
// same condition as the context projection above.
|
|
2987
3611
|
...fixtureResolutionRecords && { fixture_resolutions: fixtureResolutionRecords },
|
|
2988
3612
|
...fixtureCoverageGap && { coverage_gaps: [fixtureCoverageGap] },
|
|
2989
3613
|
...routingContext && routingContext.discoveryFailures.length > 0 ? {
|
|
@@ -2998,14 +3622,26 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
2998
3622
|
await (0, import_protocols.closeScopedConnections)(options.protocol);
|
|
2999
3623
|
}
|
|
3000
3624
|
if (webhookReceiver) await webhookReceiver.close();
|
|
3625
|
+
if (preparedRefused !== void 0) {
|
|
3626
|
+
await emitPreparedFixturesAudit(options, result);
|
|
3627
|
+
throw new import_errors.PreparedFixturesDeniedError(preparedRefused);
|
|
3628
|
+
}
|
|
3001
3629
|
return result;
|
|
3002
3630
|
}
|
|
3003
3631
|
async function runMultiPass(agentUrls, storyboard, options) {
|
|
3004
3632
|
const start = Date.now();
|
|
3633
|
+
if (options.prepared_fixtures !== void 0) {
|
|
3634
|
+
const gate = await gatePreparedFixtures(storyboard, agentUrls, options, void 0, "run");
|
|
3635
|
+
if (gate.used && "refusal" in gate) {
|
|
3636
|
+
return denyPreparedFixtures(
|
|
3637
|
+
options,
|
|
3638
|
+
buildPreparedRefusalRunResult(agentUrls, storyboard, gate.refusal),
|
|
3639
|
+
gate.refusal.code
|
|
3640
|
+
);
|
|
3641
|
+
}
|
|
3642
|
+
}
|
|
3005
3643
|
const preSeedClients = agentUrls.map((url) => (0, import_client.getOrCreateClientResolution)(url, options).client);
|
|
3006
|
-
const preSeedContext =
|
|
3007
|
-
if (storyboard.context) (0, import_context.forwardAliasCache)(storyboard.context, preSeedContext);
|
|
3008
|
-
if (options.context) (0, import_context.forwardAliasCache)(options.context, preSeedContext);
|
|
3644
|
+
const preSeedContext = createRunContext(storyboard, options).context;
|
|
3009
3645
|
let preSeedProfile = options._profile;
|
|
3010
3646
|
if (!preSeedProfile) {
|
|
3011
3647
|
const discovered = await (0, import_client.getOrDiscoverProfile)(preSeedClients[0], options);
|
|
@@ -3022,13 +3658,15 @@ async function runMultiPass(agentUrls, storyboard, options) {
|
|
|
3022
3658
|
...options.agentTools ? {} : { agentTools: (0, import_agent_routing.normalizeAgentToolNames)(preSeedProfile.tools) }
|
|
3023
3659
|
};
|
|
3024
3660
|
}
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3661
|
+
if (options.prepared_fixtures === void 0) {
|
|
3662
|
+
const resetFailure = await resetControllerStates(
|
|
3663
|
+
preSeedClients.map((client) => ({ client, options })),
|
|
3664
|
+
storyboard
|
|
3665
|
+
);
|
|
3666
|
+
if (resetFailure) {
|
|
3667
|
+
await (0, import_protocols.closeScopedConnections)(options.protocol);
|
|
3668
|
+
return buildDiscoveryFailedResult(agentUrls, storyboard, resetFailure);
|
|
3669
|
+
}
|
|
3032
3670
|
}
|
|
3033
3671
|
if (storyboardCapabilityPredicates(storyboard).length > 0) {
|
|
3034
3672
|
const unmetDetail = evaluateStoryboardCapabilityGates(
|
|
@@ -3083,7 +3721,13 @@ async function runMultiPass(agentUrls, storyboard, options) {
|
|
|
3083
3721
|
},
|
|
3084
3722
|
preSeedExcludedPhaseIds
|
|
3085
3723
|
);
|
|
3086
|
-
const preSeededResult =
|
|
3724
|
+
const preSeededResult = (
|
|
3725
|
+
// A pinned graph is authoritative across every pass. Re-seeding would
|
|
3726
|
+
// issue fresh `seed_*` mutations that replace the very seller state the
|
|
3727
|
+
// prepared captures describe, while the passes keep the original
|
|
3728
|
+
// bindings — the reset is guarded above for the same reason.
|
|
3729
|
+
options.prepared_fixtures !== void 0 || preSeedFixtureGap || allExecutablePhasesCapabilitySkipped(storyboard, phaseCapabilitySkipDetails) ? null : await (0, import_seeding.runControllerSeeding)(preSeedClients[0], storyboard, options, preSeedContext)
|
|
3730
|
+
);
|
|
3087
3731
|
const passes = [];
|
|
3088
3732
|
const passResults = [];
|
|
3089
3733
|
for (let passIdx = 0; passIdx < agentUrls.length; passIdx++) {
|
|
@@ -3136,6 +3780,10 @@ async function runMultiPass(agentUrls, storyboard, options) {
|
|
|
3136
3780
|
tested_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3137
3781
|
...schemasDedup.length > 0 ? { schemas_used: schemasDedup } : {},
|
|
3138
3782
|
...assertionsAgg.length > 0 ? { assertions: assertionsAgg } : {},
|
|
3783
|
+
// `fixture_resolutions` carries `seller_ids`, so the aggregate withholds
|
|
3784
|
+
// it on the same condition the single-pass result does. Projecting
|
|
3785
|
+
// `phases` and `context` but republishing the pinned seller ids here
|
|
3786
|
+
// withheld the copies and published the original.
|
|
3139
3787
|
...first.fixture_resolutions ? { fixture_resolutions: first.fixture_resolutions } : {},
|
|
3140
3788
|
...first.coverage_gaps ? { coverage_gaps: first.coverage_gaps } : {},
|
|
3141
3789
|
notices: noticesDedup
|
|
@@ -3265,8 +3913,200 @@ async function runStoryboardStep(agentUrl, storyboard, stepId, options = {}) {
|
|
|
3265
3913
|
)
|
|
3266
3914
|
);
|
|
3267
3915
|
}
|
|
3916
|
+
function withoutProtectedCaptures(context) {
|
|
3917
|
+
const projected = {};
|
|
3918
|
+
for (const [key, value] of Object.entries(context)) {
|
|
3919
|
+
if (!(0, import_protected_context.isProtectedContextKey)(key)) projected[key] = value;
|
|
3920
|
+
}
|
|
3921
|
+
return projected;
|
|
3922
|
+
}
|
|
3923
|
+
async function denyPreparedFixtures(options, record, refusalCode) {
|
|
3924
|
+
await emitPreparedFixturesAudit(options, record);
|
|
3925
|
+
throw new import_errors.PreparedFixturesDeniedError(refusalCode);
|
|
3926
|
+
}
|
|
3927
|
+
async function emitPreparedFixturesAudit(options, record) {
|
|
3928
|
+
const sink = options.prepared_fixtures_audit_sink;
|
|
3929
|
+
if (!sink) return;
|
|
3930
|
+
try {
|
|
3931
|
+
await sink(record);
|
|
3932
|
+
} catch {
|
|
3933
|
+
}
|
|
3934
|
+
}
|
|
3935
|
+
function buildPreparedRefusalRunResult(agentUrls, storyboard, refusal) {
|
|
3936
|
+
const base = buildDiscoveryFailedResult(agentUrls, storyboard, {
|
|
3937
|
+
step: "Authorize prepared fixture state",
|
|
3938
|
+
passed: false,
|
|
3939
|
+
duration_ms: 0,
|
|
3940
|
+
error: refusal.detail
|
|
3941
|
+
});
|
|
3942
|
+
const validation = {
|
|
3943
|
+
check: "prepared_fixtures_authorization",
|
|
3944
|
+
passed: false,
|
|
3945
|
+
description: "Prepared fixture state is re-validated and re-authorized on every lookup",
|
|
3946
|
+
error: refusal.detail,
|
|
3947
|
+
json_pointer: null,
|
|
3948
|
+
expected: "an authorized prepared state bound to this exact run scope",
|
|
3949
|
+
actual: refusal.code,
|
|
3950
|
+
schema_id: null,
|
|
3951
|
+
schema_url: null,
|
|
3952
|
+
// Explicit nulls. This gate precedes all seller contact, so the
|
|
3953
|
+
// exchange genuinely did not happen — and an omitted key cannot be
|
|
3954
|
+
// told apart from a runner that forgot to record one.
|
|
3955
|
+
request: null,
|
|
3956
|
+
response: null
|
|
3957
|
+
};
|
|
3958
|
+
return {
|
|
3959
|
+
...base,
|
|
3960
|
+
phases: base.phases.map((phase) => ({
|
|
3961
|
+
...phase,
|
|
3962
|
+
steps: phase.steps.map((step) => ({ ...step, validations: [...step.validations, validation] }))
|
|
3963
|
+
}))
|
|
3964
|
+
};
|
|
3965
|
+
}
|
|
3966
|
+
function buildPreparedRefusalStepResult(storyboard, found, context, refusal, contributions) {
|
|
3967
|
+
return {
|
|
3968
|
+
storyboard_id: storyboard.id,
|
|
3969
|
+
step_id: found.step.id,
|
|
3970
|
+
phase_id: found.phaseId,
|
|
3971
|
+
title: found.step.title,
|
|
3972
|
+
task: found.step.task,
|
|
3973
|
+
passed: false,
|
|
3974
|
+
duration_ms: 0,
|
|
3975
|
+
validations: [
|
|
3976
|
+
{
|
|
3977
|
+
check: "prepared_fixtures_authorization",
|
|
3978
|
+
passed: false,
|
|
3979
|
+
description: "Prepared fixture state is re-validated and re-authorized on every lookup",
|
|
3980
|
+
error: refusal.detail,
|
|
3981
|
+
json_pointer: null,
|
|
3982
|
+
// `step scope`, matching the per-step gate. This builds a STEP
|
|
3983
|
+
// result; saying `run scope` here made the published grammar
|
|
3984
|
+
// disagree with itself depending on which gate refused.
|
|
3985
|
+
expected: "an authorized prepared state bound to this exact step scope",
|
|
3986
|
+
actual: refusal.code,
|
|
3987
|
+
schema_id: null,
|
|
3988
|
+
schema_url: null,
|
|
3989
|
+
request: null,
|
|
3990
|
+
response: null
|
|
3991
|
+
}
|
|
3992
|
+
],
|
|
3993
|
+
// UNREDACTED: this record is the host's audit evidence, never the
|
|
3994
|
+
// refused caller's report.
|
|
3995
|
+
context,
|
|
3996
|
+
extraction: { path: "none" },
|
|
3997
|
+
contributions: Array.from(contributions),
|
|
3998
|
+
error: refusal.detail
|
|
3999
|
+
};
|
|
4000
|
+
}
|
|
4001
|
+
function buildPreparedDispositionStepResult(storyboard, found, context, disposition, contributions) {
|
|
4002
|
+
const detail = "detail" in disposition ? disposition.detail : "fixture preparation did not resolve";
|
|
4003
|
+
if (disposition.status === "failed") {
|
|
4004
|
+
return {
|
|
4005
|
+
storyboard_id: storyboard.id,
|
|
4006
|
+
step_id: found.step.id,
|
|
4007
|
+
phase_id: found.phaseId,
|
|
4008
|
+
title: found.step.title,
|
|
4009
|
+
task: found.step.task,
|
|
4010
|
+
passed: false,
|
|
4011
|
+
duration_ms: 0,
|
|
4012
|
+
// A failed step MUST carry at least one failing validation
|
|
4013
|
+
// (runner-output-contract.yaml, `step_result`). The `error`-only
|
|
4014
|
+
// carve-out beside that rule is for TRANSPORT failures, and a failed
|
|
4015
|
+
// preparation is discovery, schema validation or setup — none of
|
|
4016
|
+
// which is transport. Without this the caller saw a red step with no
|
|
4017
|
+
// `check` discriminator and had to regex the prose, which is the one
|
|
4018
|
+
// thing the synthesized kinds exist to avoid.
|
|
4019
|
+
validations: [
|
|
4020
|
+
{
|
|
4021
|
+
check: "fixture_capture_unbuildable",
|
|
4022
|
+
passed: false,
|
|
4023
|
+
description: "Fixture preparation must resolve before a step can reuse it",
|
|
4024
|
+
error: detail,
|
|
4025
|
+
json_pointer: null,
|
|
4026
|
+
expected: "a fixture graph that resolved during preparation",
|
|
4027
|
+
actual: "preparation_failed",
|
|
4028
|
+
schema_id: null,
|
|
4029
|
+
schema_url: null
|
|
4030
|
+
}
|
|
4031
|
+
],
|
|
4032
|
+
context: withoutProtectedCaptures(context),
|
|
4033
|
+
extraction: { path: "none" },
|
|
4034
|
+
contributions: Array.from(contributions),
|
|
4035
|
+
error: detail
|
|
4036
|
+
};
|
|
4037
|
+
}
|
|
4038
|
+
return {
|
|
4039
|
+
storyboard_id: storyboard.id,
|
|
4040
|
+
step_id: found.step.id,
|
|
4041
|
+
phase_id: found.phaseId,
|
|
4042
|
+
title: found.step.title,
|
|
4043
|
+
task: found.step.task,
|
|
4044
|
+
passed: true,
|
|
4045
|
+
skipped: true,
|
|
4046
|
+
skip_reason: disposition.status === "unsatisfied" ? "fixture_unsatisfied" : "missing_test_controller",
|
|
4047
|
+
// Match the full runner: reporting a missing controller as a
|
|
4048
|
+
// branch-substitutable `not_applicable` would let a consumer credit a
|
|
4049
|
+
// peer path that does not exist.
|
|
4050
|
+
//
|
|
4051
|
+
// `fixture_unsatisfied` is now the canonical reason too, not a
|
|
4052
|
+
// detailed one mapped down to `not_applicable`. The published contract
|
|
4053
|
+
// lists it as a canonical `skip_result.reason`, and "our other code
|
|
4054
|
+
// path spells it differently" was SDK precedent, not a spec argument —
|
|
4055
|
+
// which the repo's own `spec → mock → SDK` triage order resolves
|
|
4056
|
+
// against the SDK. Promoting it in `RunnerSkipReason` fixed both paths
|
|
4057
|
+
// at once, so the two no longer disagree.
|
|
4058
|
+
skip: {
|
|
4059
|
+
reason: disposition.status === "unsatisfied" ? "fixture_unsatisfied" : "missing_test_controller",
|
|
4060
|
+
detail
|
|
4061
|
+
},
|
|
4062
|
+
duration_ms: 0,
|
|
4063
|
+
validations: [],
|
|
4064
|
+
context: withoutProtectedCaptures(context),
|
|
4065
|
+
extraction: { path: "none" },
|
|
4066
|
+
contributions: Array.from(contributions)
|
|
4067
|
+
};
|
|
4068
|
+
}
|
|
3268
4069
|
async function runStoryboardStepBody(agentUrl, storyboard, stepId, options) {
|
|
3269
4070
|
(0, import_test_kit.validateTestKit)(options.test_kit);
|
|
4071
|
+
const metadataRefusal = describeReservedCallerOptions(options);
|
|
4072
|
+
if (metadataRefusal !== void 0) {
|
|
4073
|
+
const target = flattenSteps(storyboard).find((entry) => entry.step.id === stepId);
|
|
4074
|
+
if (!target) throw new Error(`Step "${stepId}" not found in storyboard "${storyboard.id}".`);
|
|
4075
|
+
return denyPreparedFixtures(
|
|
4076
|
+
options,
|
|
4077
|
+
buildPreparedRefusalStepResult(
|
|
4078
|
+
storyboard,
|
|
4079
|
+
target,
|
|
4080
|
+
{},
|
|
4081
|
+
{ code: "unauthorized_metadata", detail: metadataRefusal },
|
|
4082
|
+
options.contributions ?? []
|
|
4083
|
+
),
|
|
4084
|
+
"unauthorized_metadata"
|
|
4085
|
+
);
|
|
4086
|
+
}
|
|
4087
|
+
let standaloneGate = { used: false };
|
|
4088
|
+
if (options.prepared_fixtures !== void 0) {
|
|
4089
|
+
const target = flattenSteps(storyboard).find((entry) => entry.step.id === stepId);
|
|
4090
|
+
if (!target) {
|
|
4091
|
+
throw new Error(
|
|
4092
|
+
`Step "${stepId}" not found in storyboard "${storyboard.id}". Available steps: ${flattenSteps(storyboard).map((entry) => entry.step.id).join(", ")}`
|
|
4093
|
+
);
|
|
4094
|
+
}
|
|
4095
|
+
standaloneGate = await gatePreparedFixtures(storyboard, [agentUrl], options, target.step, "step");
|
|
4096
|
+
if (standaloneGate.used && "refusal" in standaloneGate) {
|
|
4097
|
+
return denyPreparedFixtures(
|
|
4098
|
+
options,
|
|
4099
|
+
buildPreparedRefusalStepResult(
|
|
4100
|
+
storyboard,
|
|
4101
|
+
target,
|
|
4102
|
+
createRunContext(storyboard, options).context,
|
|
4103
|
+
standaloneGate.refusal,
|
|
4104
|
+
options.contributions ?? []
|
|
4105
|
+
),
|
|
4106
|
+
standaloneGate.refusal.code
|
|
4107
|
+
);
|
|
4108
|
+
}
|
|
4109
|
+
}
|
|
3270
4110
|
if (storyboard.requires?.[0] === "property_list_reference_runner" && storyboardCapabilityPredicates(storyboard).length === 0 && propertyListReferenceNeedsMcp(options)) {
|
|
3271
4111
|
const found2 = flattenSteps(storyboard).find((entry) => entry.step.id === stepId);
|
|
3272
4112
|
if (!found2) throw new Error(`Step "${stepId}" not found in storyboard "${storyboard.id}".`);
|
|
@@ -3286,7 +4126,9 @@ async function runStoryboardStepBody(agentUrl, storyboard, stepId, options) {
|
|
|
3286
4126
|
},
|
|
3287
4127
|
duration_ms: 0,
|
|
3288
4128
|
validations: [],
|
|
3289
|
-
|
|
4129
|
+
// Validated the same way as the executing path so a reserved key in
|
|
4130
|
+
// caller context fails closed on the early-return branch too.
|
|
4131
|
+
context: createRunContext(storyboard, options).context,
|
|
3290
4132
|
extraction: { path: "none" }
|
|
3291
4133
|
};
|
|
3292
4134
|
}
|
|
@@ -3306,9 +4148,9 @@ async function runStoryboardStepBody(agentUrl, storyboard, stepId, options) {
|
|
|
3306
4148
|
} else {
|
|
3307
4149
|
profile = options._profile;
|
|
3308
4150
|
}
|
|
3309
|
-
const
|
|
3310
|
-
|
|
3311
|
-
|
|
4151
|
+
const standaloneRunContext = createRunContext(storyboard, options);
|
|
4152
|
+
const protectedWriter = standaloneRunContext.protectedWriter;
|
|
4153
|
+
const context = standaloneRunContext.context;
|
|
3312
4154
|
const allSteps = flattenSteps(storyboard);
|
|
3313
4155
|
const found = allSteps.find((s) => s.step.id === stepId);
|
|
3314
4156
|
if (!found) {
|
|
@@ -3389,7 +4231,11 @@ async function runStoryboardStepBody(agentUrl, storyboard, stepId, options) {
|
|
|
3389
4231
|
passed: true,
|
|
3390
4232
|
skipped: true,
|
|
3391
4233
|
skip_reason: "fixture_unsatisfied",
|
|
3392
|
-
|
|
4234
|
+
// Canonical too, now that `fixture_unsatisfied` is in
|
|
4235
|
+
// `RunnerSkipReason`. Both unsatisfied paths spell it the same way,
|
|
4236
|
+
// which is what the contract asks for and what the previous
|
|
4237
|
+
// `not_applicable` mapping obscured.
|
|
4238
|
+
skip: { reason: "fixture_unsatisfied", detail: resolution.detail },
|
|
3393
4239
|
duration_ms: 0,
|
|
3394
4240
|
validations: [],
|
|
3395
4241
|
context,
|
|
@@ -3397,18 +4243,30 @@ async function runStoryboardStepBody(agentUrl, storyboard, stepId, options) {
|
|
|
3397
4243
|
contributions: Array.from(options.contributions ?? [])
|
|
3398
4244
|
};
|
|
3399
4245
|
}
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
4246
|
+
const probeViolation = tryProtectedContextPatch(
|
|
4247
|
+
context,
|
|
4248
|
+
{
|
|
4249
|
+
unset: CREATIVE_PROBE_CONTEXT_SUFFIXES.map((key) => `${storyboard.creative_build_probe.context_prefix}_${key}`),
|
|
4250
|
+
set: resolution.context
|
|
4251
|
+
},
|
|
4252
|
+
{ origin: `creative_build_probe '${storyboard.creative_build_probe.context_prefix}'` }
|
|
4253
|
+
);
|
|
4254
|
+
if (probeViolation) {
|
|
4255
|
+
return {
|
|
4256
|
+
storyboard_id: storyboard.id,
|
|
4257
|
+
step_id: found.step.id,
|
|
4258
|
+
phase_id: found.phaseId,
|
|
4259
|
+
title: found.step.title,
|
|
4260
|
+
task: found.step.task,
|
|
4261
|
+
passed: false,
|
|
4262
|
+
duration_ms: 0,
|
|
4263
|
+
validations: [probeViolation],
|
|
4264
|
+
context: withoutProtectedCaptures(context),
|
|
4265
|
+
extraction: { path: "none" },
|
|
4266
|
+
contributions: Array.from(options.contributions ?? []),
|
|
4267
|
+
error: probeViolation.error
|
|
4268
|
+
};
|
|
4269
|
+
}
|
|
3412
4270
|
options = { ...options, contracts: [.../* @__PURE__ */ new Set([...options.contracts ?? [], "creative_build_probe_runner"])] };
|
|
3413
4271
|
}
|
|
3414
4272
|
const injectedReceiver = options._webhookReceiver;
|
|
@@ -3448,8 +4306,35 @@ async function runStoryboardStepBody(agentUrl, storyboard, stepId, options) {
|
|
|
3448
4306
|
agentProfile: profile,
|
|
3449
4307
|
agentLibraryVersion: profile?.library_version,
|
|
3450
4308
|
storyboardRequiresRequestSigner: resolveStoryboardRequires(storyboard, options).includes("request_signer"),
|
|
3451
|
-
storyboardRequiresPublisherAuthRunner: storyboard.requires?.includes("trusted_match_publisher_auth_runner") === true
|
|
4309
|
+
storyboardRequiresPublisherAuthRunner: storyboard.requires?.includes("trusted_match_publisher_auth_runner") === true,
|
|
4310
|
+
protectedWriter
|
|
3452
4311
|
};
|
|
4312
|
+
const prepared = standaloneGate;
|
|
4313
|
+
if (prepared.used && "state" in prepared) {
|
|
4314
|
+
const disposition = prepared.state.disposition;
|
|
4315
|
+
if (disposition.status !== "resolved") {
|
|
4316
|
+
return buildPreparedDispositionStepResult(storyboard, found, context, disposition, contributions);
|
|
4317
|
+
}
|
|
4318
|
+
const hydrationViolation = hydratePreparedCaptures(prepared.state, context, protectedWriter, contextProvenance);
|
|
4319
|
+
if (hydrationViolation) {
|
|
4320
|
+
return {
|
|
4321
|
+
storyboard_id: storyboard.id,
|
|
4322
|
+
step_id: found.step.id,
|
|
4323
|
+
phase_id: found.phaseId,
|
|
4324
|
+
title: found.step.title,
|
|
4325
|
+
task: found.step.task,
|
|
4326
|
+
passed: false,
|
|
4327
|
+
duration_ms: 0,
|
|
4328
|
+
validations: [hydrationViolation],
|
|
4329
|
+
context: withoutProtectedCaptures(context),
|
|
4330
|
+
extraction: { path: "none" },
|
|
4331
|
+
contributions: Array.from(contributions),
|
|
4332
|
+
error: hydrationViolation.error
|
|
4333
|
+
};
|
|
4334
|
+
}
|
|
4335
|
+
runState.fixtureBindings = (0, import_prepared_fixtures.preparedBindingRegistryFor)(prepared.state);
|
|
4336
|
+
runState.preparedFixtures = prepared.state;
|
|
4337
|
+
}
|
|
3453
4338
|
let prerequisiteFailure;
|
|
3454
4339
|
if (storyboard.id === "media_buy_seller/acceptance_policy_discovery" && found.step.id === "get_contextual_products" && options.acceptancePolicyDiscovery?.enabled !== false) {
|
|
3455
4340
|
const capabilityStep = allSteps.find((value) => value.step.id === "get_acceptance_policy_capability");
|
|
@@ -3505,7 +4390,11 @@ async function runStoryboardStepBody(agentUrl, storyboard, stepId, options) {
|
|
|
3505
4390
|
contributions.add(found.step.contributes_to);
|
|
3506
4391
|
}
|
|
3507
4392
|
}
|
|
3508
|
-
return {
|
|
4393
|
+
return {
|
|
4394
|
+
...result,
|
|
4395
|
+
context: withoutProtectedCaptures(result.context ?? {}),
|
|
4396
|
+
contributions: Array.from(contributions)
|
|
4397
|
+
};
|
|
3509
4398
|
} finally {
|
|
3510
4399
|
await Promise.all([
|
|
3511
4400
|
...!clientResolution.reusedShared ? [(0, import_protocols.closeScopedConnections)(options.protocol)] : [],
|
|
@@ -3515,7 +4404,7 @@ async function runStoryboardStepBody(agentUrl, storyboard, stepId, options) {
|
|
|
3515
4404
|
}
|
|
3516
4405
|
function unresolvedStepContextVars(step, request, context, runState) {
|
|
3517
4406
|
const unavailableInputs = (step.context_inputs ?? []).filter(
|
|
3518
|
-
(input) => !(input.key
|
|
4407
|
+
(input) => !Object.hasOwn(context, input.key) && (runState.capabilityUnavailableContextKeys?.has(input.key) === true || runState.missingPrerequisiteContextKeys?.has(input.key) === true)
|
|
3519
4408
|
);
|
|
3520
4409
|
return [
|
|
3521
4410
|
...findUnresolvedContextVars(request),
|
|
@@ -3808,7 +4697,7 @@ async function executeStep(client, step, storyboardId, phaseId, context, allStep
|
|
|
3808
4697
|
synthesized.push({
|
|
3809
4698
|
check: "unresolved_substitution",
|
|
3810
4699
|
passed: false,
|
|
3811
|
-
description: v.token === import_creative_assets.BUILD_ASSETS_FROM_FORMAT_DIRECTIVE ? `request directive "${v.token}" did not resolve \u2014 the test kit could not populate every seller-required asset at ${v.key}` : `request token "${v.token}" did not resolve \u2014 prior step did not populate context.${v.key}`,
|
|
4700
|
+
description: v.token === import_creative_assets.BUILD_ASSETS_FROM_FORMAT_DIRECTIVE ? `request directive "${v.token}" did not resolve \u2014 the test kit could not populate every seller-required asset at ${v.key}` : DOTTED_CONTEXT_TOKEN_RE.test(v.token) ? `request token "${v.token}" is not a supported substitution \u2014 $context.<key> matches a whole key only and cannot drill into a captured object. Capture the field directly with context_outputs: [{ key: ..., path: ${suggestedCapturePath(v.token)} }], or branch on the snapshot with a phase skip_if.` : `request token "${v.token}" did not resolve \u2014 prior step did not populate context.${v.key}`,
|
|
3812
4701
|
json_pointer: null,
|
|
3813
4702
|
expected: v.token,
|
|
3814
4703
|
actual: null,
|
|
@@ -4327,21 +5216,38 @@ async function executeStep(client, step, storyboardId, phaseId, context, allStep
|
|
|
4327
5216
|
allSteps
|
|
4328
5217
|
);
|
|
4329
5218
|
if (responseDerivedSkip && !step.expect_error) {
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
5219
|
+
const unavailableViolation = tryProtectedContextPatch(
|
|
5220
|
+
context,
|
|
5221
|
+
{
|
|
5222
|
+
markUnavailable: Object.fromEntries(
|
|
5223
|
+
responseDerivedSkip.contextKeys.map((key) => [key, responseDerivedSkip.detail])
|
|
5224
|
+
)
|
|
5225
|
+
},
|
|
5226
|
+
{
|
|
5227
|
+
...runState.responseDerivedNotApplicableContextKeys && {
|
|
5228
|
+
sinks: { unavailable: runState.responseDerivedNotApplicableContextKeys }
|
|
5229
|
+
},
|
|
5230
|
+
origin: `step '${step.id}' not_applicable_if response gate`
|
|
5231
|
+
}
|
|
5232
|
+
);
|
|
4333
5233
|
const next2 = getNextStepPreview(step.id, allSteps, context, runState.runnerVars);
|
|
4334
5234
|
return {
|
|
4335
5235
|
step_id: step.id,
|
|
4336
5236
|
phase_id: phaseId,
|
|
4337
5237
|
title: step.title,
|
|
4338
5238
|
task: step.task,
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
5239
|
+
// A refused metadata write is a failure, not a skip: the gate did not
|
|
5240
|
+
// actually mark anything unavailable, so reporting `not_applicable`
|
|
5241
|
+
// would claim a disposition the runner never reached.
|
|
5242
|
+
passed: unavailableViolation === void 0,
|
|
5243
|
+
...unavailableViolation === void 0 && {
|
|
5244
|
+
skipped: true,
|
|
5245
|
+
skip_reason: "not_applicable",
|
|
5246
|
+
skip: buildSkip("not_applicable", responseDerivedSkip.detail)
|
|
5247
|
+
},
|
|
4343
5248
|
duration_ms: stepResult.duration_ms,
|
|
4344
|
-
validations: [],
|
|
5249
|
+
validations: unavailableViolation ? [unavailableViolation] : [],
|
|
5250
|
+
...unavailableViolation && { error: unavailableViolation.error },
|
|
4345
5251
|
context,
|
|
4346
5252
|
...responseDerivedContextResult(runState),
|
|
4347
5253
|
response: (0, import_redact_secrets.redactSecrets)(taskResult?.data),
|
|
@@ -4581,33 +5487,60 @@ async function executeStep(client, step, storyboardId, phaseId, context, allStep
|
|
|
4581
5487
|
}
|
|
4582
5488
|
const updatedContext = { ...context };
|
|
4583
5489
|
(0, import_context.forwardAliasCache)(context, updatedContext);
|
|
5490
|
+
(0, import_protected_context.forwardProtectedContext)(context, updatedContext);
|
|
5491
|
+
const protectedSinks = {
|
|
5492
|
+
...runState.contextProvenance && { provenance: runState.contextProvenance },
|
|
5493
|
+
...runState.responseDerivedNotApplicableContextKeys && {
|
|
5494
|
+
unavailable: runState.responseDerivedNotApplicableContextKeys
|
|
5495
|
+
}
|
|
5496
|
+
};
|
|
5497
|
+
const protectedViolations = [];
|
|
5498
|
+
const commitContextPatch = (patch, origin) => {
|
|
5499
|
+
try {
|
|
5500
|
+
(0, import_protected_context.applyProtectedContextPatch)(updatedContext, patch, { sinks: protectedSinks, origin });
|
|
5501
|
+
} catch (error) {
|
|
5502
|
+
if (!(error instanceof import_protected_context.ProtectedContextViolation)) throw error;
|
|
5503
|
+
protectedViolations.push({
|
|
5504
|
+
check: "protected_context_violation",
|
|
5505
|
+
passed: false,
|
|
5506
|
+
description: "Reserved fixture-preparation context keys are immutable for the rest of the run",
|
|
5507
|
+
error: error.message,
|
|
5508
|
+
json_pointer: null,
|
|
5509
|
+
expected: "no write, delete or metadata change to the __adcp_fixture_ namespace",
|
|
5510
|
+
actual: error.keys.join(", "),
|
|
5511
|
+
schema_id: null,
|
|
5512
|
+
schema_url: null
|
|
5513
|
+
});
|
|
5514
|
+
}
|
|
5515
|
+
};
|
|
4584
5516
|
const hasData = taskResult?.data !== void 0 && taskResult?.data !== null;
|
|
4585
5517
|
if (passed && hasData && taskResult && !(effectiveStep.task === "sync_accounts" && request.dry_run === true)) {
|
|
4586
5518
|
const extracted = (0, import_context.extractContextWithProvenance)(effectiveStep.task, taskResult.data, step.id);
|
|
5519
|
+
const conventionUnset = [];
|
|
4587
5520
|
for (const group of extracted.clearGroups ?? []) {
|
|
4588
5521
|
if (group.when && !group.when.values.includes(updatedContext[group.when.key])) continue;
|
|
4589
5522
|
for (const key of group.keys) {
|
|
4590
5523
|
const provenance = runState.contextProvenance?.get(key);
|
|
4591
5524
|
if (provenance && provenance.source_kind !== "convention") continue;
|
|
4592
|
-
|
|
4593
|
-
runState.responseDerivedNotApplicableContextKeys?.delete(key);
|
|
4594
|
-
runState.contextProvenance?.delete(key);
|
|
4595
|
-
}
|
|
4596
|
-
}
|
|
4597
|
-
Object.assign(updatedContext, extracted.values);
|
|
4598
|
-
for (const key of Object.keys(extracted.values)) {
|
|
4599
|
-
runState.responseDerivedNotApplicableContextKeys?.delete(key);
|
|
4600
|
-
}
|
|
4601
|
-
if (runState.contextProvenance) {
|
|
4602
|
-
for (const [key, entry] of Object.entries(extracted.provenance)) {
|
|
4603
|
-
runState.contextProvenance.set(key, entry);
|
|
5525
|
+
conventionUnset.push(key);
|
|
4604
5526
|
}
|
|
4605
5527
|
}
|
|
5528
|
+
commitContextPatch(
|
|
5529
|
+
{
|
|
5530
|
+
set: extracted.values,
|
|
5531
|
+
unset: conventionUnset,
|
|
5532
|
+
setProvenance: extracted.provenance,
|
|
5533
|
+
unsetProvenance: conventionUnset,
|
|
5534
|
+
clearUnavailable: [...conventionUnset, ...Object.keys(extracted.values)]
|
|
5535
|
+
},
|
|
5536
|
+
`step '${step.id}' convention extraction for ${effectiveStep.task}`
|
|
5537
|
+
);
|
|
4606
5538
|
}
|
|
4607
5539
|
if (step.context_outputs?.length) {
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
5540
|
+
commitContextPatch(
|
|
5541
|
+
{ clearUnavailable: step.context_outputs.map((output) => output.key).filter((key) => typeof key === "string") },
|
|
5542
|
+
`step '${step.id}' context_outputs availability reset`
|
|
5543
|
+
);
|
|
4611
5544
|
const taskCompletionResolution = await resolveTaskCompletionOutputs(
|
|
4612
5545
|
taskResult,
|
|
4613
5546
|
step.context_outputs,
|
|
@@ -4624,15 +5557,14 @@ async function executeStep(client, step, storyboardId, phaseId, context, allStep
|
|
|
4624
5557
|
effectiveStep.task,
|
|
4625
5558
|
updatedContext
|
|
4626
5559
|
);
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
}
|
|
5560
|
+
commitContextPatch(
|
|
5561
|
+
{
|
|
5562
|
+
set: explicit.values,
|
|
5563
|
+
setProvenance: explicit.provenance,
|
|
5564
|
+
clearUnavailable: Object.keys(explicit.values)
|
|
5565
|
+
},
|
|
5566
|
+
`step '${step.id}' context_outputs capture`
|
|
5567
|
+
);
|
|
4636
5568
|
if (explicit.failures && explicit.failures.length > 0) {
|
|
4637
5569
|
for (const failure of explicit.failures) {
|
|
4638
5570
|
const wasTaskCompletion = step.context_outputs.some(
|
|
@@ -4669,6 +5601,7 @@ async function executeStep(client, step, storyboardId, phaseId, context, allStep
|
|
|
4669
5601
|
}
|
|
4670
5602
|
}
|
|
4671
5603
|
}
|
|
5604
|
+
if (protectedViolations.length > 0) validations.push(...protectedViolations);
|
|
4672
5605
|
const allValidationsPassedFinal = validations.every((v) => !(0, import_validations.validationFailsStep)(v));
|
|
4673
5606
|
const stepFailed = !(passed && allValidationsPassedFinal);
|
|
4674
5607
|
const contextRejectionHints = stepFailed && runState.contextProvenance ? (0, import_rejection_hints.detectContextRejectionHints)(taskResult, request, context, runState.contextProvenance, effectiveStep.task) : [];
|
|
@@ -4699,9 +5632,21 @@ async function executeStep(client, step, storyboardId, phaseId, context, allStep
|
|
|
4699
5632
|
// modern `response_record.payload` path is already redacted.
|
|
4700
5633
|
response: (0, import_redact_secrets.redactSecrets)(taskResult?.data),
|
|
4701
5634
|
validations,
|
|
5635
|
+
// NOT projected here: the full runner threads this map from step to
|
|
5636
|
+
// step, so stripping the reserved namespace would break the very
|
|
5637
|
+
// substitution the captures exist for. The STANDALONE entry point
|
|
5638
|
+
// projects it on the way out — see `runStoryboardStepBody` — because
|
|
5639
|
+
// there the map is a thread-back channel the next call refuses.
|
|
4702
5640
|
context: updatedContext,
|
|
4703
5641
|
...runState.contextProvenance && runState.contextProvenance.size > 0 && {
|
|
4704
|
-
|
|
5642
|
+
// Reserved-namespace provenance is withheld from the echo: the
|
|
5643
|
+
// standalone contract threads this map back in on the NEXT call,
|
|
5644
|
+
// where `assertUnreservedCallerMetadata` refuses it. Emitting
|
|
5645
|
+
// something we then reject would break the hosted step loop this
|
|
5646
|
+
// feature exists for.
|
|
5647
|
+
context_provenance: Object.fromEntries(
|
|
5648
|
+
[...runState.contextProvenance].filter(([key]) => !(0, import_protected_context.isProtectedContextKey)(key))
|
|
5649
|
+
)
|
|
4705
5650
|
},
|
|
4706
5651
|
...responseDerivedContextResult(runState),
|
|
4707
5652
|
error: step.expect_error || schemaRejectionIsAdvisory ? void 0 : truncateError(stepResult.error || taskResult?.error),
|
|
@@ -6715,12 +7660,22 @@ function collectUpstreamIdentifierDigests(validations, requestPayload, sampleReq
|
|
|
6715
7660
|
function sha256Hex(value) {
|
|
6716
7661
|
return (0, import_node_crypto.createHash)("sha256").update(value, "utf8").digest("hex");
|
|
6717
7662
|
}
|
|
7663
|
+
const DOTTED_CONTEXT_TOKEN_RE = /^\$context\.(\w+)((?:\.\w+|\[\d+\])+)$/;
|
|
7664
|
+
function suggestedCapturePath(token) {
|
|
7665
|
+
const match = token.match(DOTTED_CONTEXT_TOKEN_RE);
|
|
7666
|
+
const accessor = match?.[2] ?? "";
|
|
7667
|
+
return accessor.startsWith(".") ? accessor.slice(1) : accessor;
|
|
7668
|
+
}
|
|
6718
7669
|
function findUnresolvedContextVars(obj) {
|
|
6719
7670
|
const vars = [];
|
|
6720
7671
|
const walk = (val) => {
|
|
6721
7672
|
if (typeof val === "string") {
|
|
6722
7673
|
const match = val.match(/^\$context\.(\w+)$/);
|
|
6723
7674
|
if (match?.[1]) vars.push({ key: match[1], token: val });
|
|
7675
|
+
else {
|
|
7676
|
+
const dotted = val.match(DOTTED_CONTEXT_TOKEN_RE);
|
|
7677
|
+
if (dotted?.[1]) vars.push({ key: dotted[1], token: val });
|
|
7678
|
+
}
|
|
6724
7679
|
} else if (Array.isArray(val)) {
|
|
6725
7680
|
val.forEach(walk);
|
|
6726
7681
|
} else if (val !== null && typeof val === "object") {
|
|
@@ -6921,6 +7876,7 @@ function getFirstStepPreview(storyboard, context = {}) {
|
|
|
6921
7876
|
getFirstStepPreview,
|
|
6922
7877
|
listStrictOnlyFailures,
|
|
6923
7878
|
prepareGovernedStoryboardWireArgs,
|
|
7879
|
+
prepareStoryboardFixtures,
|
|
6924
7880
|
resolveCapabilityPath,
|
|
6925
7881
|
runStoryboard,
|
|
6926
7882
|
runStoryboardStep,
|