@adcp/sdk 8.1.0-beta.1 → 8.1.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/README.md +16 -1
  2. package/bin/adcp.js +75 -21
  3. package/dist/lib/conformance/oracle.d.ts.map +1 -1
  4. package/dist/lib/conformance/oracle.js +8 -1
  5. package/dist/lib/conformance/oracle.js.map +1 -1
  6. package/dist/lib/conformance/schemaArbitrary.js +135 -9
  7. package/dist/lib/conformance/schemaArbitrary.js.map +1 -1
  8. package/dist/lib/core/AgentClient.d.ts +1 -1
  9. package/dist/lib/core/AgentClient.d.ts.map +1 -1
  10. package/dist/lib/core/AgentClient.js.map +1 -1
  11. package/dist/lib/core/GovernanceMiddleware.d.ts +2 -1
  12. package/dist/lib/core/GovernanceMiddleware.d.ts.map +1 -1
  13. package/dist/lib/core/GovernanceMiddleware.js +9 -2
  14. package/dist/lib/core/GovernanceMiddleware.js.map +1 -1
  15. package/dist/lib/core/SingleAgentClient.d.ts +6 -0
  16. package/dist/lib/core/SingleAgentClient.d.ts.map +1 -1
  17. package/dist/lib/core/SingleAgentClient.js +2 -3
  18. package/dist/lib/core/SingleAgentClient.js.map +1 -1
  19. package/dist/lib/core/TaskExecutor.d.ts +1 -0
  20. package/dist/lib/core/TaskExecutor.d.ts.map +1 -1
  21. package/dist/lib/core/TaskExecutor.js +5 -1
  22. package/dist/lib/core/TaskExecutor.js.map +1 -1
  23. package/dist/lib/protocols/index.d.ts +10 -2
  24. package/dist/lib/protocols/index.d.ts.map +1 -1
  25. package/dist/lib/protocols/index.js +7 -6
  26. package/dist/lib/protocols/index.js.map +1 -1
  27. package/dist/lib/schemas/index.d.ts +68 -10
  28. package/dist/lib/schemas/index.d.ts.map +1 -1
  29. package/dist/lib/schemas/index.js +57 -16
  30. package/dist/lib/schemas/index.js.map +1 -1
  31. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  32. package/dist/lib/server/create-adcp-server.d.ts +59 -53
  33. package/dist/lib/server/create-adcp-server.d.ts.map +1 -1
  34. package/dist/lib/server/create-adcp-server.js +62 -10
  35. package/dist/lib/server/create-adcp-server.js.map +1 -1
  36. package/dist/lib/server/decisioning/account.d.ts +22 -6
  37. package/dist/lib/server/decisioning/account.d.ts.map +1 -1
  38. package/dist/lib/server/decisioning/account.js.map +1 -1
  39. package/dist/lib/server/decisioning/capabilities.d.ts +8 -0
  40. package/dist/lib/server/decisioning/capabilities.d.ts.map +1 -1
  41. package/dist/lib/server/decisioning/index.d.ts +12 -11
  42. package/dist/lib/server/decisioning/index.d.ts.map +1 -1
  43. package/dist/lib/server/decisioning/index.js.map +1 -1
  44. package/dist/lib/server/decisioning/list-helpers.d.ts +3 -2
  45. package/dist/lib/server/decisioning/list-helpers.d.ts.map +1 -1
  46. package/dist/lib/server/decisioning/list-helpers.js +0 -1
  47. package/dist/lib/server/decisioning/list-helpers.js.map +1 -1
  48. package/dist/lib/server/decisioning/manifest-helpers.d.ts +8 -2
  49. package/dist/lib/server/decisioning/manifest-helpers.d.ts.map +1 -1
  50. package/dist/lib/server/decisioning/manifest-helpers.js +8 -2
  51. package/dist/lib/server/decisioning/manifest-helpers.js.map +1 -1
  52. package/dist/lib/server/decisioning/proposal/dispatch.d.ts +3 -2
  53. package/dist/lib/server/decisioning/proposal/dispatch.d.ts.map +1 -1
  54. package/dist/lib/server/decisioning/proposal/dispatch.js +1 -0
  55. package/dist/lib/server/decisioning/proposal/dispatch.js.map +1 -1
  56. package/dist/lib/server/decisioning/proposal/mock-manager.d.ts +3 -2
  57. package/dist/lib/server/decisioning/proposal/mock-manager.d.ts.map +1 -1
  58. package/dist/lib/server/decisioning/proposal/mock-manager.js.map +1 -1
  59. package/dist/lib/server/decisioning/proposal/types.d.ts +3 -2
  60. package/dist/lib/server/decisioning/proposal/types.d.ts.map +1 -1
  61. package/dist/lib/server/decisioning/proposal/types.js.map +1 -1
  62. package/dist/lib/server/decisioning/runtime/from-platform.d.ts.map +1 -1
  63. package/dist/lib/server/decisioning/runtime/from-platform.js +14 -1
  64. package/dist/lib/server/decisioning/runtime/from-platform.js.map +1 -1
  65. package/dist/lib/server/decisioning/specialisms/audiences.d.ts +4 -1
  66. package/dist/lib/server/decisioning/specialisms/audiences.d.ts.map +1 -1
  67. package/dist/lib/server/decisioning/specialisms/brand-rights.d.ts +19 -7
  68. package/dist/lib/server/decisioning/specialisms/brand-rights.d.ts.map +1 -1
  69. package/dist/lib/server/decisioning/specialisms/campaign-governance.d.ts +9 -4
  70. package/dist/lib/server/decisioning/specialisms/campaign-governance.d.ts.map +1 -1
  71. package/dist/lib/server/decisioning/specialisms/content-standards.d.ts +17 -8
  72. package/dist/lib/server/decisioning/specialisms/content-standards.d.ts.map +1 -1
  73. package/dist/lib/server/decisioning/specialisms/creative-ad-server.d.ts +13 -5
  74. package/dist/lib/server/decisioning/specialisms/creative-ad-server.d.ts.map +1 -1
  75. package/dist/lib/server/decisioning/specialisms/creative.d.ts +14 -9
  76. package/dist/lib/server/decisioning/specialisms/creative.d.ts.map +1 -1
  77. package/dist/lib/server/decisioning/specialisms/lists.d.ts +21 -10
  78. package/dist/lib/server/decisioning/specialisms/lists.d.ts.map +1 -1
  79. package/dist/lib/server/decisioning/specialisms/sales.d.ts +27 -12
  80. package/dist/lib/server/decisioning/specialisms/sales.d.ts.map +1 -1
  81. package/dist/lib/server/decisioning/specialisms/signals.d.ts +5 -2
  82. package/dist/lib/server/decisioning/specialisms/signals.d.ts.map +1 -1
  83. package/dist/lib/server/decisioning/specialisms/sponsored-intelligence.d.ts +9 -4
  84. package/dist/lib/server/decisioning/specialisms/sponsored-intelligence.d.ts.map +1 -1
  85. package/dist/lib/server/index.d.ts +1 -0
  86. package/dist/lib/server/index.d.ts.map +1 -1
  87. package/dist/lib/server/index.js.map +1 -1
  88. package/dist/lib/server/operational-platform.d.ts +4 -3
  89. package/dist/lib/server/operational-platform.d.ts.map +1 -1
  90. package/dist/lib/server/operational-platform.js.map +1 -1
  91. package/dist/lib/server/responses.d.ts +28 -27
  92. package/dist/lib/server/responses.d.ts.map +1 -1
  93. package/dist/lib/server/responses.js +96 -35
  94. package/dist/lib/server/responses.js.map +1 -1
  95. package/dist/lib/signing/types.d.ts +6 -0
  96. package/dist/lib/signing/types.d.ts.map +1 -1
  97. package/dist/lib/signing/types.js.map +1 -1
  98. package/dist/lib/signing/verifier.d.ts.map +1 -1
  99. package/dist/lib/signing/verifier.js +33 -4
  100. package/dist/lib/signing/verifier.js.map +1 -1
  101. package/dist/lib/testing/client.d.ts +5 -0
  102. package/dist/lib/testing/client.d.ts.map +1 -1
  103. package/dist/lib/testing/client.js +33 -2
  104. package/dist/lib/testing/client.js.map +1 -1
  105. package/dist/lib/testing/compliance/comply.d.ts +8 -1
  106. package/dist/lib/testing/compliance/comply.d.ts.map +1 -1
  107. package/dist/lib/testing/compliance/comply.js +57 -25
  108. package/dist/lib/testing/compliance/comply.js.map +1 -1
  109. package/dist/lib/testing/compliance/types.d.ts +2 -0
  110. package/dist/lib/testing/compliance/types.d.ts.map +1 -1
  111. package/dist/lib/testing/index.d.ts +1 -1
  112. package/dist/lib/testing/index.d.ts.map +1 -1
  113. package/dist/lib/testing/index.js +4 -1
  114. package/dist/lib/testing/index.js.map +1 -1
  115. package/dist/lib/testing/storyboard/compliance.d.ts +11 -1
  116. package/dist/lib/testing/storyboard/compliance.d.ts.map +1 -1
  117. package/dist/lib/testing/storyboard/compliance.js +37 -3
  118. package/dist/lib/testing/storyboard/compliance.js.map +1 -1
  119. package/dist/lib/testing/storyboard/index.d.ts +2 -2
  120. package/dist/lib/testing/storyboard/index.d.ts.map +1 -1
  121. package/dist/lib/testing/storyboard/index.js +6 -2
  122. package/dist/lib/testing/storyboard/index.js.map +1 -1
  123. package/dist/lib/testing/storyboard/probes.d.ts.map +1 -1
  124. package/dist/lib/testing/storyboard/probes.js +3 -0
  125. package/dist/lib/testing/storyboard/probes.js.map +1 -1
  126. package/dist/lib/testing/storyboard/request-builder.d.ts.map +1 -1
  127. package/dist/lib/testing/storyboard/request-builder.js +4 -1
  128. package/dist/lib/testing/storyboard/request-builder.js.map +1 -1
  129. package/dist/lib/testing/storyboard/runner.d.ts +2 -0
  130. package/dist/lib/testing/storyboard/runner.d.ts.map +1 -1
  131. package/dist/lib/testing/storyboard/runner.js +335 -43
  132. package/dist/lib/testing/storyboard/runner.js.map +1 -1
  133. package/dist/lib/testing/storyboard/types.d.ts +65 -0
  134. package/dist/lib/testing/storyboard/types.d.ts.map +1 -1
  135. package/dist/lib/testing/storyboard/types.js.map +1 -1
  136. package/dist/lib/testing/storyboard/validations.d.ts +4 -3
  137. package/dist/lib/testing/storyboard/validations.d.ts.map +1 -1
  138. package/dist/lib/testing/storyboard/validations.js +26 -2
  139. package/dist/lib/testing/storyboard/validations.js.map +1 -1
  140. package/dist/lib/testing/types.d.ts +19 -0
  141. package/dist/lib/testing/types.d.ts.map +1 -1
  142. package/dist/lib/types/activate-signal.d.ts +647 -0
  143. package/dist/lib/types/build-creative.d.ts +2105 -0
  144. package/dist/lib/types/calibrate-content.d.ts +675 -0
  145. package/dist/lib/types/check-governance.d.ts +619 -0
  146. package/dist/lib/types/comply-test-controller.d.ts +8428 -0
  147. package/dist/lib/types/core.generated.d.ts +547 -537
  148. package/dist/lib/types/core.generated.d.ts.map +1 -1
  149. package/dist/lib/types/core.generated.js +1 -1
  150. package/dist/lib/types/create-collection-list.d.ts +693 -0
  151. package/dist/lib/types/create-content-standards.d.ts +830 -0
  152. package/dist/lib/types/create-media-buy.d.ts +3374 -0
  153. package/dist/lib/types/create-property-list.d.ts +836 -0
  154. package/dist/lib/types/delete-collection-list.d.ts +497 -0
  155. package/dist/lib/types/delete-property-list.d.ts +497 -0
  156. package/dist/lib/types/get-account-financials.d.ts +624 -0
  157. package/dist/lib/types/get-adcp-capabilities.d.ts +2863 -0
  158. package/dist/lib/types/get-collection-list.d.ts +763 -0
  159. package/dist/lib/types/get-content-standards.d.ts +919 -0
  160. package/dist/lib/types/get-creative-delivery.d.ts +2219 -0
  161. package/dist/lib/types/get-creative-features.d.ts +1736 -0
  162. package/dist/lib/types/get-media-buy-artifacts.d.ts +864 -0
  163. package/dist/lib/types/get-media-buys.d.ts +1670 -0
  164. package/dist/lib/types/get-plan-audit-logs.d.ts +455 -0
  165. package/dist/lib/types/get-products.d.ts +4935 -0
  166. package/dist/lib/types/get-property-list.d.ts +874 -0
  167. package/dist/lib/types/get-signals.d.ts +986 -0
  168. package/dist/lib/types/index.d.ts +1 -0
  169. package/dist/lib/types/index.d.ts.map +1 -1
  170. package/dist/lib/types/index.js.map +1 -1
  171. package/dist/lib/types/inline-enums.generated.d.ts +163 -7
  172. package/dist/lib/types/inline-enums.generated.d.ts.map +1 -1
  173. package/dist/lib/types/inline-enums.generated.js +222 -9
  174. package/dist/lib/types/inline-enums.generated.js.map +1 -1
  175. package/dist/lib/types/list-accounts.d.ts +851 -0
  176. package/dist/lib/types/list-content-standards.d.ts +975 -0
  177. package/dist/lib/types/list-creative-formats.d.ts +3132 -0
  178. package/dist/lib/types/list-creatives.d.ts +2390 -0
  179. package/dist/lib/types/list-property-lists.d.ts +855 -0
  180. package/dist/lib/types/log-event.d.ts +373 -0
  181. package/dist/lib/types/per-tool-index.json +391 -0
  182. package/dist/lib/types/preview-creative.d.ts +1981 -0
  183. package/dist/lib/types/provide-performance-feedback.d.ts +218 -0
  184. package/dist/lib/types/report-plan-outcome.d.ts +433 -0
  185. package/dist/lib/types/report-usage.d.ts +579 -0
  186. package/dist/lib/types/schemas.generated.d.ts +146765 -128986
  187. package/dist/lib/types/schemas.generated.d.ts.map +1 -1
  188. package/dist/lib/types/schemas.generated.js +405 -437
  189. package/dist/lib/types/schemas.generated.js.map +1 -1
  190. package/dist/lib/types/server-payload.d.ts +39 -0
  191. package/dist/lib/types/server-payload.d.ts.map +1 -0
  192. package/dist/lib/types/server-payload.js +11 -0
  193. package/dist/lib/types/server-payload.js.map +1 -0
  194. package/dist/lib/types/si-get-offering.d.ts +259 -0
  195. package/dist/lib/types/si-initiate-session.d.ts +372 -0
  196. package/dist/lib/types/si-send-message.d.ts +300 -0
  197. package/dist/lib/types/si-terminate-session.d.ts +213 -0
  198. package/dist/lib/types/sync-accounts.d.ts +856 -0
  199. package/dist/lib/types/sync-audiences.d.ts +707 -0
  200. package/dist/lib/types/sync-catalogs.d.ts +766 -0
  201. package/dist/lib/types/sync-creatives.d.ts +2134 -0
  202. package/dist/lib/types/sync-event-sources.d.ts +665 -0
  203. package/dist/lib/types/sync-governance.d.ts +558 -0
  204. package/dist/lib/types/sync-plans.d.ts +979 -0
  205. package/dist/lib/types/tools.generated.d.ts +236 -188
  206. package/dist/lib/types/tools.generated.d.ts.map +1 -1
  207. package/dist/lib/types/update-collection-list.d.ts +697 -0
  208. package/dist/lib/types/update-content-standards.d.ts +847 -0
  209. package/dist/lib/types/update-media-buy.d.ts +3047 -0
  210. package/dist/lib/types/update-property-list.d.ts +840 -0
  211. package/dist/lib/types/validate-content-delivery.d.ts +722 -0
  212. package/dist/lib/types/validate-input.d.ts +1683 -0
  213. package/dist/lib/utils/adcp-version-config.d.ts +2 -0
  214. package/dist/lib/utils/adcp-version-config.d.ts.map +1 -1
  215. package/dist/lib/utils/adcp-version-config.js +42 -0
  216. package/dist/lib/utils/adcp-version-config.js.map +1 -1
  217. package/dist/lib/utils/response-schemas.d.ts.map +1 -1
  218. package/dist/lib/utils/response-schemas.js +3 -0
  219. package/dist/lib/utils/response-schemas.js.map +1 -1
  220. package/dist/lib/utils/response-unwrapper.d.ts.map +1 -1
  221. package/dist/lib/utils/response-unwrapper.js +28 -4
  222. package/dist/lib/utils/response-unwrapper.js.map +1 -1
  223. package/dist/lib/utils/tool-request-schemas.d.ts +8574 -1
  224. package/dist/lib/utils/tool-request-schemas.d.ts.map +1 -1
  225. package/dist/lib/utils/tool-request-schemas.js +4 -5
  226. package/dist/lib/utils/tool-request-schemas.js.map +1 -1
  227. package/dist/lib/utils/union-errors.d.ts +13 -6
  228. package/dist/lib/utils/union-errors.d.ts.map +1 -1
  229. package/dist/lib/utils/union-errors.js +34 -7
  230. package/dist/lib/utils/union-errors.js.map +1 -1
  231. package/dist/lib/validation/schema-loader.d.ts.map +1 -1
  232. package/dist/lib/validation/schema-loader.js +68 -15
  233. package/dist/lib/validation/schema-loader.js.map +1 -1
  234. package/dist/lib/version.d.ts +27 -3
  235. package/dist/lib/version.d.ts.map +1 -1
  236. package/dist/lib/version.js +42 -3
  237. package/dist/lib/version.js.map +1 -1
  238. package/examples/error-compliant-server.ts +1 -1
  239. package/examples/hello_seller_adapter_guaranteed.ts +14 -10
  240. package/examples/hello_seller_adapter_multi_tenant.ts +27 -23
  241. package/examples/hello_seller_adapter_non_guaranteed.ts +16 -14
  242. package/examples/hello_seller_adapter_proposal_mode.ts +22 -6
  243. package/examples/hello_signals_adapter_marketplace.ts +34 -3
  244. package/package.json +9 -2
@@ -7,6 +7,8 @@
7
7
  * - runStoryboardStep(): run a single step (stateless, LLM-friendly)
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.applyStoryboardVersionOptions = applyStoryboardVersionOptions;
11
+ exports.applyAdcpVersionRunOptions = applyAdcpVersionRunOptions;
10
12
  exports.resolveCapabilityPath = resolveCapabilityPath;
11
13
  exports.evaluateCapabilityPredicate = evaluateCapabilityPredicate;
12
14
  exports.__redactSecretsForTest = __redactSecretsForTest;
@@ -39,7 +41,9 @@ const request_builder_1 = require("./request-builder");
39
41
  const client_2 = require("../client");
40
42
  const idempotency_1 = require("../../utils/idempotency");
41
43
  const schema_loader_1 = require("../../validation/schema-loader");
44
+ const version_1 = require("../../version");
42
45
  const probes_1 = require("./probes");
46
+ const capabilities_types_1 = require("../../signing/agent-resolver/capabilities-types");
43
47
  const test_kit_1 = require("./test-kit");
44
48
  const loader_1 = require("./loader");
45
49
  const probe_dispatch_1 = require("./request-signing/probe-dispatch");
@@ -65,6 +69,26 @@ const SKIP_DETAILS = {
65
69
  };
66
70
  const CONTROLLER_SEEDING_FAILED_DETAIL = 'Skipped: pre-flight comply_test_controller seeding failed; the agent was not populated with the storyboard fixtures the remaining phases depend on.';
67
71
  const OAUTH_NOT_ADVERTISED_DETAIL = 'Skipped: agent does not advertise OAuth — /.well-known/oauth-protected-resource returned 404 (RFC 9728 §3). API-key path must carry auth_mechanism_verified for this storyboard to pass.';
72
+ function applyStoryboardVersionOptions(storyboard, options) {
73
+ return applyAdcpVersionRunOptions(storyboard.adcp_version, options);
74
+ }
75
+ function applyAdcpVersionRunOptions(defaultAdcpVersion, options) {
76
+ const adcpVersion = options.adcpVersion ?? defaultAdcpVersion;
77
+ if (adcpVersion === undefined)
78
+ return options;
79
+ const versionEnvelope = options.versionEnvelope ?? storyboardVersionEnvelopeMode(adcpVersion);
80
+ if (options.adcpVersion === adcpVersion && options.versionEnvelope === versionEnvelope) {
81
+ return options;
82
+ }
83
+ return { ...options, adcpVersion, versionEnvelope };
84
+ }
85
+ function storyboardVersionEnvelopeMode(adcpVersion) {
86
+ const bundleKey = (0, schema_loader_1.resolveBundleKey)(adcpVersion);
87
+ const major = (0, version_1.parseAdcpMajorVersion)(bundleKey);
88
+ if (Number.isFinite(major) && major < 3)
89
+ return 'none';
90
+ return 'auto';
91
+ }
68
92
  /**
69
93
  * Suffix appended to the skip detail when the sole-stateful-step exemption
70
94
  * fires (`not_applicable` / `missing_tool` / `missing_test_controller` on
@@ -179,6 +203,107 @@ function evaluateCapabilityPredicate(predicate, actual) {
179
203
  function buildSkip(reason, detail) {
180
204
  return { reason, detail: detail ?? SKIP_DETAILS[reason] };
181
205
  }
206
+ function detectResponseDerivedNotApplicable(step, request, response, runState, allSteps) {
207
+ if (response === undefined || response === null)
208
+ return null;
209
+ const gates = [
210
+ ...normalizeResponseNotApplicableGates(step.not_applicable_if),
211
+ ...inferImplicitResponseNotApplicableGates(step, request, runState, allSteps),
212
+ ];
213
+ for (const gate of gates) {
214
+ if (gate.kind !== 'terminal_page')
215
+ continue;
216
+ if (!terminalPageGateMatches(gate, request, response))
217
+ continue;
218
+ const detail = gate.detail ??
219
+ `${gate.reason ?? 'single_page_result'}: ${step.task} response is terminal; cursor-walk not applicable`;
220
+ return {
221
+ detail,
222
+ contextKeys: responseNotApplicableContextKeys(step, gate),
223
+ };
224
+ }
225
+ return null;
226
+ }
227
+ function normalizeResponseNotApplicableGates(gates) {
228
+ if (!gates)
229
+ return [];
230
+ return Array.isArray(gates) ? gates : [gates];
231
+ }
232
+ function inferImplicitResponseNotApplicableGates(step, request, runState, allSteps) {
233
+ // Back-compat for the already-published pagination_integrity_list_accounts
234
+ // storyboard: it expresses the continuation requirement as validations
235
+ // rather than a dedicated response-derived gate. Keep this narrowly scoped
236
+ // to list_accounts so other seeded pagination storyboards do not silently
237
+ // waive fixture/setup mistakes.
238
+ if (step.task !== 'list_accounts')
239
+ return [];
240
+ const expectsContinuation = step.validations?.some(v => v.check === 'field_value' && v.path === 'pagination.has_more' && v.value === true);
241
+ const capturesCursor = step.context_outputs?.some(o => o.path === 'pagination.cursor');
242
+ const validatesCursor = step.validations?.some(v => v.check === 'field_present' && v.path === 'pagination.cursor');
243
+ if (!expectsContinuation || (!capturesCursor && !validatesCursor))
244
+ return [];
245
+ const maxResults = (0, path_1.resolvePath)(request, 'pagination.max_results');
246
+ if (typeof maxResults === 'number' &&
247
+ Number.isFinite(maxResults) &&
248
+ hasUnrunAccountSeedExceedingPageSize(allSteps, runState, maxResults)) {
249
+ return [];
250
+ }
251
+ const setupAccounts = (0, path_1.resolvePath)(runState?.priorStepResults.get('sync_three_accounts')?.response, 'accounts');
252
+ if (typeof maxResults === 'number' &&
253
+ Number.isFinite(maxResults) &&
254
+ Array.isArray(setupAccounts) &&
255
+ setupAccounts.length > maxResults) {
256
+ return [];
257
+ }
258
+ return [{ kind: 'terminal_page', items_path: 'accounts', reason: 'single_page_result' }];
259
+ }
260
+ function hasUnrunAccountSeedExceedingPageSize(allSteps, runState, maxResults) {
261
+ return (allSteps?.some(({ step }) => {
262
+ if (runState?.priorStepResults.has(step.id))
263
+ return false;
264
+ if (step.task !== 'sync_accounts')
265
+ return false;
266
+ const accounts = (0, path_1.resolvePath)(step.sample_request, 'accounts');
267
+ return Array.isArray(accounts) && accounts.length > maxResults;
268
+ }) ?? false);
269
+ }
270
+ function terminalPageGateMatches(gate, request, response) {
271
+ const maxResults = (0, path_1.resolvePath)(request, gate.request_max_results_path ?? 'pagination.max_results');
272
+ if (typeof maxResults !== 'number' || !Number.isFinite(maxResults) || maxResults <= 0)
273
+ return false;
274
+ const items = (0, path_1.resolvePath)(response, gate.items_path);
275
+ if (!Array.isArray(items))
276
+ return false;
277
+ const pagination = (0, path_1.resolvePath)(response, 'pagination');
278
+ if (pagination === undefined || pagination === null)
279
+ return items.length < maxResults;
280
+ if (typeof pagination !== 'object' || Array.isArray(pagination))
281
+ return false;
282
+ const p = pagination;
283
+ if (p.has_more === true)
284
+ return false;
285
+ if (p.has_more !== false)
286
+ return false;
287
+ if (typeof p.total_count === 'number' && p.total_count > items.length)
288
+ return false;
289
+ if (items.length < maxResults)
290
+ return true;
291
+ return typeof p.total_count === 'number' && p.total_count <= items.length;
292
+ }
293
+ function responseNotApplicableContextKeys(step, gate) {
294
+ if (gate.context_keys?.length)
295
+ return gate.context_keys;
296
+ return (step.context_outputs ?? [])
297
+ .filter(o => o.path === 'pagination.cursor')
298
+ .map(o => o.key)
299
+ .filter((key) => typeof key === 'string' && key.length > 0);
300
+ }
301
+ function responseDerivedContextResult(runState) {
302
+ const entries = runState.responseDerivedNotApplicableContextKeys;
303
+ return entries && entries.size > 0
304
+ ? { response_derived_not_applicable_context_keys: Object.fromEntries(entries) }
305
+ : {};
306
+ }
182
307
  /**
183
308
  * True for skip reasons that imply state genuinely never materialized
184
309
  * — no other code path could have established it. The runner trips
@@ -571,6 +696,7 @@ function filterResponseHeaders(headers) {
571
696
  * empty on instance B.
572
697
  */
573
698
  async function runStoryboard(agentUrlOrUrls, storyboard, options = {}) {
699
+ options = applyStoryboardVersionOptions(storyboard, options);
574
700
  (0, test_kit_1.validateTestKit)(options.test_kit);
575
701
  // Enforce authoring-time branch_set invariants regardless of how the
576
702
  // storyboard reached us. YAML callers already ran these rules in
@@ -1211,6 +1337,7 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
1211
1337
  let clients;
1212
1338
  let routingContext;
1213
1339
  let profile;
1340
+ let callerOwnsClients = false;
1214
1341
  if (useRouting) {
1215
1342
  try {
1216
1343
  routingContext = await (0, agent_routing_1.buildRoutingContext)(storyboard, options);
@@ -1225,8 +1352,7 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
1225
1352
  duration_ms: 0,
1226
1353
  error: detail,
1227
1354
  };
1228
- if (!options._client)
1229
- await (0, protocols_1.closeConnections)(options.protocol);
1355
+ await (0, protocols_1.closeConnections)(options.protocol);
1230
1356
  return buildDiscoveryFailedResult(agentUrls, storyboard, failedStep);
1231
1357
  }
1232
1358
  clients = [...routingContext.clients.values()];
@@ -1258,7 +1384,9 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
1258
1384
  else {
1259
1385
  // Build one client per URL. In single-URL mode `_client` (from comply()) is
1260
1386
  // honored so the shared MCP transport is reused across storyboards.
1261
- clients = agentUrls.map(url => (0, client_1.getOrCreateClient)(url, options));
1387
+ const clientResolutions = agentUrls.map(url => (0, client_1.getOrCreateClientResolution)(url, options));
1388
+ clients = clientResolutions.map(r => r.client);
1389
+ callerOwnsClients = clientResolutions.some(r => r.reusedShared);
1262
1390
  // Drop any retained A2A session ids before this storyboard's first call.
1263
1391
  // `comply()` shares one client across N storyboards for transport reuse;
1264
1392
  // AgentClient.retainSession holds onto `pendingTaskId` from non-terminal
@@ -1273,7 +1401,7 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
1273
1401
  // expected to run the same code behind a shared state store, so one probe
1274
1402
  // is sufficient. For multi-instance runs, skipping N-1 redundant
1275
1403
  // get_agent_info calls also keeps CI output clean.
1276
- if (!options._client) {
1404
+ if (!callerOwnsClients) {
1277
1405
  const discovered = await (0, client_1.getOrDiscoverProfile)(clients[0], options);
1278
1406
  // Discovery failure must surface as a HARD STORYBOARD FAILURE, not a
1279
1407
  // silent empty `agentTools: []` that lets every step skip with
@@ -1282,8 +1410,7 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
1282
1410
  // (auth misconfig, MCP transport-fallback bugs, network policy, etc.).
1283
1411
  // See: https://github.com/adcontextprotocol/adcp-client/issues/...
1284
1412
  if (discovered.step.passed === false) {
1285
- if (!options._client)
1286
- await (0, protocols_1.closeConnections)(options.protocol);
1413
+ await (0, protocols_1.closeConnections)(options.protocol);
1287
1414
  return buildDiscoveryFailedResult(agentUrls, storyboard, discovered.step);
1288
1415
  }
1289
1416
  profile = discovered.profile;
@@ -1329,7 +1456,7 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
1329
1456
  if (allRequires.length) {
1330
1457
  const unmet = checkRequires(allRequires, options, profile);
1331
1458
  if (unmet) {
1332
- if (!options._client)
1459
+ if (!callerOwnsClients)
1333
1460
  await (0, protocols_1.closeConnections)(options.protocol);
1334
1461
  return {
1335
1462
  ...buildRequirementUnmetResult(agentUrls, storyboard, unmet.requirement, unmet.detail),
@@ -1348,7 +1475,7 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
1348
1475
  const actual = resolveCapabilityPath(rawCaps, cap.path);
1349
1476
  const unmetDetail = evaluateCapabilityPredicate(cap, actual);
1350
1477
  if (unmetDetail !== null) {
1351
- if (!options._client)
1478
+ if (!callerOwnsClients)
1352
1479
  await (0, protocols_1.closeConnections)(options.protocol);
1353
1480
  return {
1354
1481
  ...buildCapabilityUnsupportedResult(agentUrls, storyboard, unmetDetail),
@@ -1371,7 +1498,7 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
1371
1498
  if (storyboard.required_tools?.length && options.agentTools) {
1372
1499
  const hasAnyRequired = storyboard.required_tools.some(t => options.agentTools.includes(t));
1373
1500
  if (!hasAnyRequired) {
1374
- if (!options._client)
1501
+ if (!callerOwnsClients)
1375
1502
  await (0, protocols_1.closeConnections)(options.protocol);
1376
1503
  return {
1377
1504
  ...buildRequiredToolsMissingResult(agentUrls, storyboard, `agent does not advertise any of [${storyboard.required_tools.join(', ')}]`),
@@ -1394,6 +1521,7 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
1394
1521
  const contextProvenance = new Map();
1395
1522
  const priorA2aEnvelopes = new Map();
1396
1523
  const stepRequestStarts = new Map();
1524
+ const responseDerivedNotApplicableContextKeys = new Map();
1397
1525
  const phaseResults = [];
1398
1526
  let passedCount = 0;
1399
1527
  let failedCount = 0;
@@ -1863,6 +1991,7 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
1863
1991
  contextProvenance,
1864
1992
  priorA2aEnvelopes,
1865
1993
  stepRequestStarts,
1994
+ responseDerivedNotApplicableContextKeys,
1866
1995
  agentLibraryVersion: profile?.library_version,
1867
1996
  });
1868
1997
  const result = { ...rawResult, storyboard_id: storyboard.id };
@@ -2323,7 +2452,7 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
2323
2452
  // Close protocol connections when the runner created its own client. The
2324
2453
  // connection pool is keyed by URL+auth, so a single closeConnections() call
2325
2454
  // evicts every instance's transport regardless of how many URLs we used.
2326
- if (!options._client) {
2455
+ if (!callerOwnsClients) {
2327
2456
  await (0, protocols_1.closeConnections)(options.protocol);
2328
2457
  }
2329
2458
  if (webhookReceiver)
@@ -2361,7 +2490,7 @@ async function runMultiPass(agentUrls, storyboard, options) {
2361
2490
  // only the first pass attaches the synthetic `__controller_seeding__`
2362
2491
  // phase to its `phaseResults`, so the aggregated top-level counts reflect
2363
2492
  // a single seeding pass across the whole run.
2364
- const preSeedClients = agentUrls.map(url => (0, client_1.getOrCreateClient)(url, options));
2493
+ const preSeedClients = agentUrls.map(url => (0, client_1.getOrCreateClientResolution)(url, options).client);
2365
2494
  const preSeedContext = { ...options.context };
2366
2495
  const preSeededResult = await (0, seeding_1.runControllerSeeding)(preSeedClients[0], storyboard, options, preSeedContext);
2367
2496
  const passes = [];
@@ -2558,15 +2687,17 @@ function summarizeStrictValidation(phases) {
2558
2687
  * Context is passed in and returned, enabling step-by-step orchestration.
2559
2688
  */
2560
2689
  async function runStoryboardStep(agentUrl, storyboard, stepId, options = {}) {
2690
+ options = applyStoryboardVersionOptions(storyboard, options);
2561
2691
  (0, test_kit_1.validateTestKit)(options.test_kit);
2562
- const client = (0, client_1.getOrCreateClient)(agentUrl, options);
2692
+ const clientResolution = (0, client_1.getOrCreateClientResolution)(agentUrl, options);
2693
+ const client = clientResolution.client;
2563
2694
  // Discover agent profile for standalone step execution. Captured so the
2564
2695
  // executeStep call below can thread `library_version` through to
2565
2696
  // shape-drift hint detection (issue #850). Also threads _profile into
2566
2697
  // options so capability-based skip gates in executeStep (e.g. account-mode
2567
2698
  // branching) can read raw_capabilities, mirroring executeStoryboardPass.
2568
2699
  let profile;
2569
- if (!options._client) {
2700
+ if (!clientResolution.reusedShared) {
2570
2701
  const discovered = await (0, client_1.getOrDiscoverProfile)(client, options);
2571
2702
  profile = discovered.profile;
2572
2703
  if (profile && !options._profile) {
@@ -2612,6 +2743,7 @@ async function runStoryboardStep(agentUrl, storyboard, stepId, options = {}) {
2612
2743
  // previous step's result). Storyboard-level runs build this internally;
2613
2744
  // here the caller owns accumulation across stateless invocations.
2614
2745
  const contextProvenance = new Map(Object.entries(options.context_provenance ?? {}));
2746
+ const responseDerivedNotApplicableContextKeys = new Map(Object.entries(options.response_derived_not_applicable_context_keys ?? {}));
2615
2747
  const result = await executeStep(client, found.step, found.phaseId, context, allSteps, options, {
2616
2748
  contributions: new Set(),
2617
2749
  priorStepResults: new Map(),
@@ -2622,9 +2754,10 @@ async function runStoryboardStep(agentUrl, storyboard, stepId, options = {}) {
2622
2754
  contextProvenance,
2623
2755
  priorA2aEnvelopes: new Map(),
2624
2756
  stepRequestStarts: new Map(),
2757
+ responseDerivedNotApplicableContextKeys,
2625
2758
  agentLibraryVersion: profile?.library_version,
2626
2759
  });
2627
- if (!options._client) {
2760
+ if (!clientResolution.reusedShared) {
2628
2761
  await (0, protocols_1.closeConnections)(options.protocol);
2629
2762
  }
2630
2763
  if (ownsWebhookReceiver && webhookReceiver)
@@ -2642,6 +2775,7 @@ client, step, phaseId, context, allSteps, options, state) {
2642
2775
  agentUrl: '',
2643
2776
  contextProvenance: new Map(),
2644
2777
  stepRequestStarts: new Map(),
2778
+ responseDerivedNotApplicableContextKeys: new Map(),
2645
2779
  };
2646
2780
  // HTTP probe tasks bypass the MCP client entirely.
2647
2781
  if (probes_1.PROBE_TASKS.has(step.task)) {
@@ -2810,45 +2944,50 @@ client, step, phaseId, context, allSteps, options, state) {
2810
2944
  const unresolvedVars = findUnresolvedContextVars(request);
2811
2945
  if (unresolvedVars.length > 0 && !step.expect_error) {
2812
2946
  const next = getNextStepPreview(step.id, allSteps, context, runState.runnerVars);
2813
- const detail = `Skipped: unresolved context variables from prior steps: ${unresolvedVars.map(v => v.key).join(', ')}.`;
2814
- // Per runner-output-contract.yaml v2.0.0, a skipped consumer step MUST
2815
- // carry an `unresolved_substitution` validation result for each missing
2816
- // token `expected` is the token string, `actual` / `json_pointer` /
2817
- // `request` / `response` are null (pre-wire failure; no response payload
2818
- // exists). Surfacing this as a validation rather than only a skip detail
2819
- // keeps the runner-output contract's "failed/skipped steps include at
2820
- // least one validation result" invariant intact and lets dashboards
2821
- // attribute the cascade origin without parsing the skip message.
2822
- const seenTokens = new Set();
2947
+ const responseDerivedDetails = unresolvedVars
2948
+ .map(v => runState.responseDerivedNotApplicableContextKeys?.get(v.key))
2949
+ .filter((d) => typeof d === 'string');
2950
+ const allResponseDerived = responseDerivedDetails.length === unresolvedVars.length && responseDerivedDetails.length > 0;
2951
+ const detail = allResponseDerived
2952
+ ? [...new Set(responseDerivedDetails)].join('; ')
2953
+ : `Skipped: unresolved context variables from prior steps: ${unresolvedVars.map(v => v.key).join(', ')}.`;
2954
+ // Normal unresolved substitutions carry one validation result per missing
2955
+ // token. Response-derived terminal-page skips are already successful
2956
+ // not_applicable rows, so their downstream cursor consumers stay validation
2957
+ // empty to avoid inventing a failing-looking check for an expected skip.
2823
2958
  const synthesized = [];
2824
- for (const v of unresolvedVars) {
2825
- if (seenTokens.has(v.token))
2826
- continue;
2827
- seenTokens.add(v.token);
2828
- synthesized.push({
2829
- check: 'unresolved_substitution',
2830
- passed: false,
2831
- description: `request token "${v.token}" did not resolve — prior step did not populate context.${v.key}`,
2832
- json_pointer: null,
2833
- expected: v.token,
2834
- actual: null,
2835
- schema_id: null,
2836
- schema_url: null,
2837
- });
2959
+ if (!allResponseDerived) {
2960
+ const seenTokens = new Set();
2961
+ for (const v of unresolvedVars) {
2962
+ if (seenTokens.has(v.token))
2963
+ continue;
2964
+ seenTokens.add(v.token);
2965
+ synthesized.push({
2966
+ check: 'unresolved_substitution',
2967
+ passed: false,
2968
+ description: `request token "${v.token}" did not resolve — prior step did not populate context.${v.key}`,
2969
+ json_pointer: null,
2970
+ expected: v.token,
2971
+ actual: null,
2972
+ schema_id: null,
2973
+ schema_url: null,
2974
+ });
2975
+ }
2838
2976
  }
2839
2977
  return {
2840
2978
  step_id: step.id,
2841
2979
  phase_id: phaseId,
2842
2980
  title: step.title,
2843
2981
  task: step.task,
2844
- passed: false,
2982
+ passed: allResponseDerived,
2845
2983
  skipped: true,
2846
- skip_reason: 'prerequisite_failed',
2847
- skip: buildSkip('prerequisite_failed', detail),
2984
+ skip_reason: allResponseDerived ? 'not_applicable' : 'prerequisite_failed',
2985
+ skip: buildSkip(allResponseDerived ? 'not_applicable' : 'prerequisite_failed', detail),
2848
2986
  duration_ms: 0,
2849
2987
  validations: synthesized,
2850
2988
  context,
2851
- error: detail,
2989
+ ...responseDerivedContextResult(runState),
2990
+ ...(!allResponseDerived && { error: detail }),
2852
2991
  next,
2853
2992
  extraction: { path: 'none' },
2854
2993
  };
@@ -3203,6 +3342,32 @@ client, step, phaseId, context, allSteps, options, state) {
3203
3342
  if (step.expect_error && !taskResult?.data && taskResult?.error) {
3204
3343
  taskResult = { ...taskResult, data: { error: taskResult.error } };
3205
3344
  }
3345
+ const responseDerivedSkip = detectResponseDerivedNotApplicable(effectiveStep, request, taskResult?.data, runState, allSteps);
3346
+ if (responseDerivedSkip && !step.expect_error) {
3347
+ for (const key of responseDerivedSkip.contextKeys) {
3348
+ runState.responseDerivedNotApplicableContextKeys?.set(key, responseDerivedSkip.detail);
3349
+ }
3350
+ const next = getNextStepPreview(step.id, allSteps, context, runState.runnerVars);
3351
+ return {
3352
+ step_id: step.id,
3353
+ phase_id: phaseId,
3354
+ title: step.title,
3355
+ task: step.task,
3356
+ passed: true,
3357
+ skipped: true,
3358
+ skip_reason: 'not_applicable',
3359
+ skip: buildSkip('not_applicable', responseDerivedSkip.detail),
3360
+ duration_ms: stepResult.duration_ms,
3361
+ validations: [],
3362
+ context,
3363
+ ...responseDerivedContextResult(runState),
3364
+ response: (0, redact_secrets_1.redactSecrets)(taskResult?.data),
3365
+ next,
3366
+ request: requestRecord,
3367
+ ...(responseRecord && { response_record: responseRecord }),
3368
+ extraction: extractionFromTaskResult(taskResult),
3369
+ };
3370
+ }
3206
3371
  // Determine pass/fail — inverted when expect_error is set
3207
3372
  let passed;
3208
3373
  if (step.expect_error) {
@@ -3370,6 +3535,9 @@ client, step, phaseId, context, allSteps, options, state) {
3370
3535
  if (passed && hasData && taskResult) {
3371
3536
  const extracted = (0, context_1.extractContextWithProvenance)(effectiveStep.task, taskResult.data, step.id);
3372
3537
  Object.assign(updatedContext, extracted.values);
3538
+ for (const key of Object.keys(extracted.values)) {
3539
+ runState.responseDerivedNotApplicableContextKeys?.delete(key);
3540
+ }
3373
3541
  if (runState.contextProvenance) {
3374
3542
  for (const [key, entry] of Object.entries(extracted.provenance)) {
3375
3543
  runState.contextProvenance.set(key, entry);
@@ -3387,6 +3555,10 @@ client, step, phaseId, context, allSteps, options, state) {
3387
3555
  // ensures the minted value from any same-step $generate:…#<key> inline
3388
3556
  // substitution is visible here.
3389
3557
  if (step.context_outputs?.length) {
3558
+ for (const output of step.context_outputs) {
3559
+ if (output.key)
3560
+ runState.responseDerivedNotApplicableContextKeys?.delete(output.key);
3561
+ }
3390
3562
  // Resolve `task_completion.<path>` outputs against the eventual task
3391
3563
  // artifact rather than the immediate response. When the immediate
3392
3564
  // response is a submitted-arm envelope (HITL / async-signed-IO flows),
@@ -3413,6 +3585,9 @@ client, step, phaseId, context, allSteps, options, state) {
3413
3585
  const remappedOutputs = remapTaskCompletionOutputs(step.context_outputs);
3414
3586
  const explicit = (0, context_1.applyContextOutputsWithProvenance)(extractionData, remappedOutputs, step.id, effectiveStep.task, updatedContext);
3415
3587
  Object.assign(updatedContext, explicit.values);
3588
+ for (const key of Object.keys(explicit.values)) {
3589
+ runState.responseDerivedNotApplicableContextKeys?.delete(key);
3590
+ }
3416
3591
  if (runState.contextProvenance) {
3417
3592
  for (const [key, entry] of Object.entries(explicit.provenance)) {
3418
3593
  runState.contextProvenance.set(key, entry);
@@ -3543,6 +3718,7 @@ client, step, phaseId, context, allSteps, options, state) {
3543
3718
  runState.contextProvenance.size > 0 && {
3544
3719
  context_provenance: Object.fromEntries(runState.contextProvenance),
3545
3720
  }),
3721
+ ...responseDerivedContextResult(runState),
3546
3722
  error: step.expect_error ? undefined : truncateError(stepResult.error || taskResult?.error),
3547
3723
  ...(!step.expect_error && taskResult?.adcp_error && { adcp_error: taskResult.adcp_error }),
3548
3724
  next,
@@ -3559,7 +3735,24 @@ async function executeProbeStep(step, phaseId, context, allSteps, options, runSt
3559
3735
  const start = Date.now();
3560
3736
  let httpResult;
3561
3737
  const probeOpts = { allowPrivateIp: options.allow_http === true };
3562
- if (step.task === 'protected_resource_metadata') {
3738
+ if (step.requires_contract) {
3739
+ const contracts = new Set(options.contracts ?? []);
3740
+ if (!contracts.has(step.requires_contract)) {
3741
+ httpResult = {
3742
+ url: runState.agentUrl,
3743
+ status: 0,
3744
+ headers: {},
3745
+ body: null,
3746
+ skipped: true,
3747
+ skip_reason: 'missing_test_kit_contract',
3748
+ error: `Test-kit contract "${step.requires_contract}" is not configured on this runner.`,
3749
+ };
3750
+ }
3751
+ }
3752
+ if (httpResult) {
3753
+ // Contract-gated synthetic probes self-skip before doing any network work.
3754
+ }
3755
+ else if (step.task === 'protected_resource_metadata') {
3563
3756
  httpResult = await (0, probes_1.probeProtectedResourceMetadata)(runState.agentUrl, probeOpts);
3564
3757
  // RFC 9728 presence semantics (adcp-client#677): a 404 means the agent is
3565
3758
  // honestly not advertising OAuth. Convert to a clean step skip so the
@@ -3585,6 +3778,21 @@ async function executeProbeStep(step, phaseId, context, allSteps, options, runSt
3585
3778
  else if (step.task === 'request_signing_probe') {
3586
3779
  httpResult = await (0, probe_dispatch_1.probeRequestSigningVector)(step.id, runState.agentUrl, options);
3587
3780
  }
3781
+ else if (step.task === 'fetch_brand_jwks') {
3782
+ httpResult = await probeBrandJwks(options._profile?.raw_capabilities, probeOpts);
3783
+ }
3784
+ else if (step.task === 'assert_jwks_purpose') {
3785
+ httpResult = assertJwksPurpose(runState.priorProbes.get('fetch_brand_jwks'), 'webhook-signing');
3786
+ }
3787
+ else if (step.task === 'expect_rate_limit_not_replayed') {
3788
+ httpResult = {
3789
+ url: runState.agentUrl,
3790
+ status: 0,
3791
+ headers: {},
3792
+ body: null,
3793
+ error: 'rate_limit_trip_runner contract is configured, but this SDK runner does not yet implement live rate-limit trip/replay probing.',
3794
+ };
3795
+ }
3588
3796
  if (httpResult)
3589
3797
  runState.priorProbes.set(step.task, httpResult);
3590
3798
  const duration = Date.now() - start;
@@ -3670,6 +3878,90 @@ async function executeProbeStep(step, phaseId, context, allSteps, options, runSt
3670
3878
  extraction,
3671
3879
  };
3672
3880
  }
3881
+ async function probeBrandJwks(rawCapabilities, options) {
3882
+ const brandJsonUrl = (0, capabilities_types_1.readBrandJsonUrl)(rawCapabilities);
3883
+ if (!brandJsonUrl) {
3884
+ return {
3885
+ url: '',
3886
+ status: 0,
3887
+ headers: {},
3888
+ body: null,
3889
+ error: 'identity.brand_json_url missing from get_adcp_capabilities; cannot fetch brand JWKS',
3890
+ };
3891
+ }
3892
+ const brand = await (0, probes_1.fetchProbe)(brandJsonUrl, options);
3893
+ if (brand.error || brand.status < 200 || brand.status >= 300) {
3894
+ return {
3895
+ ...brand,
3896
+ error: brand.error ?? `brand.json fetch returned HTTP ${brand.status}`,
3897
+ };
3898
+ }
3899
+ const agents = brand.body && typeof brand.body === 'object' ? brand.body.agents : undefined;
3900
+ const jwksUri = Array.isArray(agents)
3901
+ ? agents
3902
+ .map(agent => (agent && typeof agent === 'object' ? agent.jwks_uri : undefined))
3903
+ .find((uri) => typeof uri === 'string' && uri.length > 0)
3904
+ : undefined;
3905
+ if (!jwksUri) {
3906
+ return {
3907
+ url: brandJsonUrl,
3908
+ status: 0,
3909
+ headers: {},
3910
+ body: brand.body,
3911
+ error: 'brand.json agents[] did not contain a jwks_uri',
3912
+ };
3913
+ }
3914
+ const jwks = await (0, probes_1.fetchProbe)(jwksUri, options);
3915
+ if (jwks.error || jwks.status < 200 || jwks.status >= 300) {
3916
+ return {
3917
+ ...jwks,
3918
+ error: jwks.error ?? `JWKS fetch returned HTTP ${jwks.status}`,
3919
+ };
3920
+ }
3921
+ return jwks;
3922
+ }
3923
+ function assertJwksPurpose(prior, purpose) {
3924
+ if (!prior || prior.error) {
3925
+ return {
3926
+ url: prior?.url ?? '',
3927
+ status: prior?.status ?? 0,
3928
+ headers: prior?.headers ?? {},
3929
+ body: prior?.body ?? null,
3930
+ error: prior?.error ?? 'fetch_brand_jwks step missing; cannot assert JWKS purpose',
3931
+ };
3932
+ }
3933
+ const keys = prior.body && typeof prior.body === 'object' ? prior.body.keys : undefined;
3934
+ if (!Array.isArray(keys)) {
3935
+ return {
3936
+ url: prior.url,
3937
+ status: 0,
3938
+ headers: {},
3939
+ body: prior.body,
3940
+ error: 'JWKS body does not contain keys[]',
3941
+ };
3942
+ }
3943
+ const matching = keys.filter(key => {
3944
+ if (!key || typeof key !== 'object')
3945
+ return false;
3946
+ const rec = key;
3947
+ return rec.adcp_use === purpose && rec.status !== 'revoked' && rec.revoked !== true;
3948
+ });
3949
+ if (matching.length === 0) {
3950
+ return {
3951
+ url: prior.url,
3952
+ status: 0,
3953
+ headers: {},
3954
+ body: prior.body,
3955
+ error: `JWKS contains no active key with adcp_use="${purpose}"`,
3956
+ };
3957
+ }
3958
+ return {
3959
+ url: prior.url,
3960
+ status: 200,
3961
+ headers: prior.headers,
3962
+ body: { purpose, matching_key_count: matching.length },
3963
+ };
3964
+ }
3673
3965
  function findPriorProbe(priorStepResults) {
3674
3966
  // Fallback for runStoryboardStep where priorProbes isn't populated — reach
3675
3967
  // into the step result's response, which we set to the HttpProbeResult above.