@adcp/sdk 8.1.0-beta.6 → 8.1.0-beta.8

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 (122) hide show
  1. package/README.md +12 -0
  2. package/dist/lib/conformance/oracle.d.ts.map +1 -1
  3. package/dist/lib/conformance/oracle.js +8 -1
  4. package/dist/lib/conformance/oracle.js.map +1 -1
  5. package/dist/lib/conformance/schemaArbitrary.js +135 -9
  6. package/dist/lib/conformance/schemaArbitrary.js.map +1 -1
  7. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  8. package/dist/lib/server/create-adcp-server.d.ts +5 -0
  9. package/dist/lib/server/create-adcp-server.d.ts.map +1 -1
  10. package/dist/lib/server/create-adcp-server.js +41 -3
  11. package/dist/lib/server/create-adcp-server.js.map +1 -1
  12. package/dist/lib/server/decisioning/capabilities.d.ts +8 -0
  13. package/dist/lib/server/decisioning/capabilities.d.ts.map +1 -1
  14. package/dist/lib/server/decisioning/proposal/dispatch.d.ts.map +1 -1
  15. package/dist/lib/server/decisioning/proposal/dispatch.js +2 -0
  16. package/dist/lib/server/decisioning/proposal/dispatch.js.map +1 -1
  17. package/dist/lib/server/decisioning/runtime/from-platform.d.ts.map +1 -1
  18. package/dist/lib/server/decisioning/runtime/from-platform.js +14 -1
  19. package/dist/lib/server/decisioning/runtime/from-platform.js.map +1 -1
  20. package/dist/lib/server/responses.d.ts +1 -1
  21. package/dist/lib/server/responses.d.ts.map +1 -1
  22. package/dist/lib/server/responses.js +5 -2
  23. package/dist/lib/server/responses.js.map +1 -1
  24. package/dist/lib/signing/types.d.ts +6 -0
  25. package/dist/lib/signing/types.d.ts.map +1 -1
  26. package/dist/lib/signing/types.js.map +1 -1
  27. package/dist/lib/signing/verifier.d.ts.map +1 -1
  28. package/dist/lib/signing/verifier.js +33 -4
  29. package/dist/lib/signing/verifier.js.map +1 -1
  30. package/dist/lib/testing/storyboard/compliance.d.ts +1 -0
  31. package/dist/lib/testing/storyboard/compliance.d.ts.map +1 -1
  32. package/dist/lib/testing/storyboard/compliance.js +8 -2
  33. package/dist/lib/testing/storyboard/compliance.js.map +1 -1
  34. package/dist/lib/testing/storyboard/index.d.ts +1 -1
  35. package/dist/lib/testing/storyboard/index.d.ts.map +1 -1
  36. package/dist/lib/testing/storyboard/index.js +3 -2
  37. package/dist/lib/testing/storyboard/index.js.map +1 -1
  38. package/dist/lib/testing/storyboard/probes.d.ts.map +1 -1
  39. package/dist/lib/testing/storyboard/probes.js +3 -0
  40. package/dist/lib/testing/storyboard/probes.js.map +1 -1
  41. package/dist/lib/testing/storyboard/runner.d.ts.map +1 -1
  42. package/dist/lib/testing/storyboard/runner.js +294 -29
  43. package/dist/lib/testing/storyboard/runner.js.map +1 -1
  44. package/dist/lib/testing/storyboard/types.d.ts +59 -0
  45. package/dist/lib/testing/storyboard/types.d.ts.map +1 -1
  46. package/dist/lib/testing/storyboard/types.js.map +1 -1
  47. package/dist/lib/testing/storyboard/validations.d.ts +4 -3
  48. package/dist/lib/testing/storyboard/validations.d.ts.map +1 -1
  49. package/dist/lib/testing/storyboard/validations.js +26 -2
  50. package/dist/lib/testing/storyboard/validations.js.map +1 -1
  51. package/dist/lib/types/activate-signal.d.ts +647 -0
  52. package/dist/lib/types/build-creative.d.ts +2105 -0
  53. package/dist/lib/types/calibrate-content.d.ts +675 -0
  54. package/dist/lib/types/check-governance.d.ts +619 -0
  55. package/dist/lib/types/comply-test-controller.d.ts +8428 -0
  56. package/dist/lib/types/core.generated.d.ts +180 -252
  57. package/dist/lib/types/core.generated.d.ts.map +1 -1
  58. package/dist/lib/types/core.generated.js +1 -1
  59. package/dist/lib/types/create-collection-list.d.ts +693 -0
  60. package/dist/lib/types/create-content-standards.d.ts +830 -0
  61. package/dist/lib/types/create-media-buy.d.ts +3374 -0
  62. package/dist/lib/types/create-property-list.d.ts +836 -0
  63. package/dist/lib/types/delete-collection-list.d.ts +497 -0
  64. package/dist/lib/types/delete-property-list.d.ts +497 -0
  65. package/dist/lib/types/get-account-financials.d.ts +624 -0
  66. package/dist/lib/types/get-adcp-capabilities.d.ts +2863 -0
  67. package/dist/lib/types/get-collection-list.d.ts +763 -0
  68. package/dist/lib/types/get-content-standards.d.ts +919 -0
  69. package/dist/lib/types/get-creative-delivery.d.ts +2219 -0
  70. package/dist/lib/types/get-creative-features.d.ts +1736 -0
  71. package/dist/lib/types/get-media-buy-artifacts.d.ts +864 -0
  72. package/dist/lib/types/get-media-buys.d.ts +1670 -0
  73. package/dist/lib/types/get-plan-audit-logs.d.ts +455 -0
  74. package/dist/lib/types/get-products.d.ts +4935 -0
  75. package/dist/lib/types/get-property-list.d.ts +874 -0
  76. package/dist/lib/types/get-signals.d.ts +986 -0
  77. package/dist/lib/types/list-accounts.d.ts +851 -0
  78. package/dist/lib/types/list-content-standards.d.ts +975 -0
  79. package/dist/lib/types/list-creative-formats.d.ts +3132 -0
  80. package/dist/lib/types/list-creatives.d.ts +2390 -0
  81. package/dist/lib/types/list-property-lists.d.ts +855 -0
  82. package/dist/lib/types/log-event.d.ts +373 -0
  83. package/dist/lib/types/per-tool-index.json +391 -0
  84. package/dist/lib/types/preview-creative.d.ts +1981 -0
  85. package/dist/lib/types/provide-performance-feedback.d.ts +218 -0
  86. package/dist/lib/types/report-plan-outcome.d.ts +433 -0
  87. package/dist/lib/types/report-usage.d.ts +579 -0
  88. package/dist/lib/types/schemas.generated.d.ts +127279 -125067
  89. package/dist/lib/types/schemas.generated.d.ts.map +1 -1
  90. package/dist/lib/types/schemas.generated.js +221 -293
  91. package/dist/lib/types/schemas.generated.js.map +1 -1
  92. package/dist/lib/types/si-get-offering.d.ts +259 -0
  93. package/dist/lib/types/si-initiate-session.d.ts +372 -0
  94. package/dist/lib/types/si-send-message.d.ts +300 -0
  95. package/dist/lib/types/si-terminate-session.d.ts +213 -0
  96. package/dist/lib/types/sync-accounts.d.ts +856 -0
  97. package/dist/lib/types/sync-audiences.d.ts +707 -0
  98. package/dist/lib/types/sync-catalogs.d.ts +766 -0
  99. package/dist/lib/types/sync-creatives.d.ts +2134 -0
  100. package/dist/lib/types/sync-event-sources.d.ts +665 -0
  101. package/dist/lib/types/sync-governance.d.ts +558 -0
  102. package/dist/lib/types/sync-plans.d.ts +979 -0
  103. package/dist/lib/types/update-collection-list.d.ts +697 -0
  104. package/dist/lib/types/update-content-standards.d.ts +847 -0
  105. package/dist/lib/types/update-media-buy.d.ts +3047 -0
  106. package/dist/lib/types/update-property-list.d.ts +840 -0
  107. package/dist/lib/types/validate-content-delivery.d.ts +722 -0
  108. package/dist/lib/types/validate-input.d.ts +1683 -0
  109. package/dist/lib/utils/response-schemas.js +1 -1
  110. package/dist/lib/utils/response-schemas.js.map +1 -1
  111. package/dist/lib/utils/response-unwrapper.d.ts.map +1 -1
  112. package/dist/lib/utils/response-unwrapper.js +18 -3
  113. package/dist/lib/utils/response-unwrapper.js.map +1 -1
  114. package/dist/lib/version.d.ts +3 -3
  115. package/dist/lib/version.js +3 -3
  116. package/examples/error-compliant-server.ts +1 -1
  117. package/examples/hello_seller_adapter_guaranteed.ts +8 -3
  118. package/examples/hello_seller_adapter_multi_tenant.ts +27 -23
  119. package/examples/hello_seller_adapter_non_guaranteed.ts +7 -3
  120. package/examples/hello_seller_adapter_proposal_mode.ts +22 -6
  121. package/examples/hello_signals_adapter_marketplace.ts +34 -3
  122. package/package.json +9 -2
@@ -40,6 +40,7 @@ const client_2 = require("../client");
40
40
  const idempotency_1 = require("../../utils/idempotency");
41
41
  const schema_loader_1 = require("../../validation/schema-loader");
42
42
  const probes_1 = require("./probes");
43
+ const capabilities_types_1 = require("../../signing/agent-resolver/capabilities-types");
43
44
  const test_kit_1 = require("./test-kit");
44
45
  const loader_1 = require("./loader");
45
46
  const probe_dispatch_1 = require("./request-signing/probe-dispatch");
@@ -179,6 +180,107 @@ function evaluateCapabilityPredicate(predicate, actual) {
179
180
  function buildSkip(reason, detail) {
180
181
  return { reason, detail: detail ?? SKIP_DETAILS[reason] };
181
182
  }
183
+ function detectResponseDerivedNotApplicable(step, request, response, runState, allSteps) {
184
+ if (response === undefined || response === null)
185
+ return null;
186
+ const gates = [
187
+ ...normalizeResponseNotApplicableGates(step.not_applicable_if),
188
+ ...inferImplicitResponseNotApplicableGates(step, request, runState, allSteps),
189
+ ];
190
+ for (const gate of gates) {
191
+ if (gate.kind !== 'terminal_page')
192
+ continue;
193
+ if (!terminalPageGateMatches(gate, request, response))
194
+ continue;
195
+ const detail = gate.detail ??
196
+ `${gate.reason ?? 'single_page_result'}: ${step.task} response is terminal; cursor-walk not applicable`;
197
+ return {
198
+ detail,
199
+ contextKeys: responseNotApplicableContextKeys(step, gate),
200
+ };
201
+ }
202
+ return null;
203
+ }
204
+ function normalizeResponseNotApplicableGates(gates) {
205
+ if (!gates)
206
+ return [];
207
+ return Array.isArray(gates) ? gates : [gates];
208
+ }
209
+ function inferImplicitResponseNotApplicableGates(step, request, runState, allSteps) {
210
+ // Back-compat for the already-published pagination_integrity_list_accounts
211
+ // storyboard: it expresses the continuation requirement as validations
212
+ // rather than a dedicated response-derived gate. Keep this narrowly scoped
213
+ // to list_accounts so other seeded pagination storyboards do not silently
214
+ // waive fixture/setup mistakes.
215
+ if (step.task !== 'list_accounts')
216
+ return [];
217
+ const expectsContinuation = step.validations?.some(v => v.check === 'field_value' && v.path === 'pagination.has_more' && v.value === true);
218
+ const capturesCursor = step.context_outputs?.some(o => o.path === 'pagination.cursor');
219
+ const validatesCursor = step.validations?.some(v => v.check === 'field_present' && v.path === 'pagination.cursor');
220
+ if (!expectsContinuation || (!capturesCursor && !validatesCursor))
221
+ return [];
222
+ const maxResults = (0, path_1.resolvePath)(request, 'pagination.max_results');
223
+ if (typeof maxResults === 'number' &&
224
+ Number.isFinite(maxResults) &&
225
+ hasUnrunAccountSeedExceedingPageSize(allSteps, runState, maxResults)) {
226
+ return [];
227
+ }
228
+ const setupAccounts = (0, path_1.resolvePath)(runState?.priorStepResults.get('sync_three_accounts')?.response, 'accounts');
229
+ if (typeof maxResults === 'number' &&
230
+ Number.isFinite(maxResults) &&
231
+ Array.isArray(setupAccounts) &&
232
+ setupAccounts.length > maxResults) {
233
+ return [];
234
+ }
235
+ return [{ kind: 'terminal_page', items_path: 'accounts', reason: 'single_page_result' }];
236
+ }
237
+ function hasUnrunAccountSeedExceedingPageSize(allSteps, runState, maxResults) {
238
+ return (allSteps?.some(({ step }) => {
239
+ if (runState?.priorStepResults.has(step.id))
240
+ return false;
241
+ if (step.task !== 'sync_accounts')
242
+ return false;
243
+ const accounts = (0, path_1.resolvePath)(step.sample_request, 'accounts');
244
+ return Array.isArray(accounts) && accounts.length > maxResults;
245
+ }) ?? false);
246
+ }
247
+ function terminalPageGateMatches(gate, request, response) {
248
+ const maxResults = (0, path_1.resolvePath)(request, gate.request_max_results_path ?? 'pagination.max_results');
249
+ if (typeof maxResults !== 'number' || !Number.isFinite(maxResults) || maxResults <= 0)
250
+ return false;
251
+ const items = (0, path_1.resolvePath)(response, gate.items_path);
252
+ if (!Array.isArray(items))
253
+ return false;
254
+ const pagination = (0, path_1.resolvePath)(response, 'pagination');
255
+ if (pagination === undefined || pagination === null)
256
+ return items.length < maxResults;
257
+ if (typeof pagination !== 'object' || Array.isArray(pagination))
258
+ return false;
259
+ const p = pagination;
260
+ if (p.has_more === true)
261
+ return false;
262
+ if (p.has_more !== false)
263
+ return false;
264
+ if (typeof p.total_count === 'number' && p.total_count > items.length)
265
+ return false;
266
+ if (items.length < maxResults)
267
+ return true;
268
+ return typeof p.total_count === 'number' && p.total_count <= items.length;
269
+ }
270
+ function responseNotApplicableContextKeys(step, gate) {
271
+ if (gate.context_keys?.length)
272
+ return gate.context_keys;
273
+ return (step.context_outputs ?? [])
274
+ .filter(o => o.path === 'pagination.cursor')
275
+ .map(o => o.key)
276
+ .filter((key) => typeof key === 'string' && key.length > 0);
277
+ }
278
+ function responseDerivedContextResult(runState) {
279
+ const entries = runState.responseDerivedNotApplicableContextKeys;
280
+ return entries && entries.size > 0
281
+ ? { response_derived_not_applicable_context_keys: Object.fromEntries(entries) }
282
+ : {};
283
+ }
182
284
  /**
183
285
  * True for skip reasons that imply state genuinely never materialized
184
286
  * — no other code path could have established it. The runner trips
@@ -1394,6 +1496,7 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
1394
1496
  const contextProvenance = new Map();
1395
1497
  const priorA2aEnvelopes = new Map();
1396
1498
  const stepRequestStarts = new Map();
1499
+ const responseDerivedNotApplicableContextKeys = new Map();
1397
1500
  const phaseResults = [];
1398
1501
  let passedCount = 0;
1399
1502
  let failedCount = 0;
@@ -1863,6 +1966,7 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
1863
1966
  contextProvenance,
1864
1967
  priorA2aEnvelopes,
1865
1968
  stepRequestStarts,
1969
+ responseDerivedNotApplicableContextKeys,
1866
1970
  agentLibraryVersion: profile?.library_version,
1867
1971
  });
1868
1972
  const result = { ...rawResult, storyboard_id: storyboard.id };
@@ -2612,6 +2716,7 @@ async function runStoryboardStep(agentUrl, storyboard, stepId, options = {}) {
2612
2716
  // previous step's result). Storyboard-level runs build this internally;
2613
2717
  // here the caller owns accumulation across stateless invocations.
2614
2718
  const contextProvenance = new Map(Object.entries(options.context_provenance ?? {}));
2719
+ const responseDerivedNotApplicableContextKeys = new Map(Object.entries(options.response_derived_not_applicable_context_keys ?? {}));
2615
2720
  const result = await executeStep(client, found.step, found.phaseId, context, allSteps, options, {
2616
2721
  contributions: new Set(),
2617
2722
  priorStepResults: new Map(),
@@ -2622,6 +2727,7 @@ async function runStoryboardStep(agentUrl, storyboard, stepId, options = {}) {
2622
2727
  contextProvenance,
2623
2728
  priorA2aEnvelopes: new Map(),
2624
2729
  stepRequestStarts: new Map(),
2730
+ responseDerivedNotApplicableContextKeys,
2625
2731
  agentLibraryVersion: profile?.library_version,
2626
2732
  });
2627
2733
  if (!options._client) {
@@ -2642,6 +2748,7 @@ client, step, phaseId, context, allSteps, options, state) {
2642
2748
  agentUrl: '',
2643
2749
  contextProvenance: new Map(),
2644
2750
  stepRequestStarts: new Map(),
2751
+ responseDerivedNotApplicableContextKeys: new Map(),
2645
2752
  };
2646
2753
  // HTTP probe tasks bypass the MCP client entirely.
2647
2754
  if (probes_1.PROBE_TASKS.has(step.task)) {
@@ -2810,45 +2917,50 @@ client, step, phaseId, context, allSteps, options, state) {
2810
2917
  const unresolvedVars = findUnresolvedContextVars(request);
2811
2918
  if (unresolvedVars.length > 0 && !step.expect_error) {
2812
2919
  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();
2920
+ const responseDerivedDetails = unresolvedVars
2921
+ .map(v => runState.responseDerivedNotApplicableContextKeys?.get(v.key))
2922
+ .filter((d) => typeof d === 'string');
2923
+ const allResponseDerived = responseDerivedDetails.length === unresolvedVars.length && responseDerivedDetails.length > 0;
2924
+ const detail = allResponseDerived
2925
+ ? [...new Set(responseDerivedDetails)].join('; ')
2926
+ : `Skipped: unresolved context variables from prior steps: ${unresolvedVars.map(v => v.key).join(', ')}.`;
2927
+ // Normal unresolved substitutions carry one validation result per missing
2928
+ // token. Response-derived terminal-page skips are already successful
2929
+ // not_applicable rows, so their downstream cursor consumers stay validation
2930
+ // empty to avoid inventing a failing-looking check for an expected skip.
2823
2931
  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
- });
2932
+ if (!allResponseDerived) {
2933
+ const seenTokens = new Set();
2934
+ for (const v of unresolvedVars) {
2935
+ if (seenTokens.has(v.token))
2936
+ continue;
2937
+ seenTokens.add(v.token);
2938
+ synthesized.push({
2939
+ check: 'unresolved_substitution',
2940
+ passed: false,
2941
+ description: `request token "${v.token}" did not resolve — prior step did not populate context.${v.key}`,
2942
+ json_pointer: null,
2943
+ expected: v.token,
2944
+ actual: null,
2945
+ schema_id: null,
2946
+ schema_url: null,
2947
+ });
2948
+ }
2838
2949
  }
2839
2950
  return {
2840
2951
  step_id: step.id,
2841
2952
  phase_id: phaseId,
2842
2953
  title: step.title,
2843
2954
  task: step.task,
2844
- passed: false,
2955
+ passed: allResponseDerived,
2845
2956
  skipped: true,
2846
- skip_reason: 'prerequisite_failed',
2847
- skip: buildSkip('prerequisite_failed', detail),
2957
+ skip_reason: allResponseDerived ? 'not_applicable' : 'prerequisite_failed',
2958
+ skip: buildSkip(allResponseDerived ? 'not_applicable' : 'prerequisite_failed', detail),
2848
2959
  duration_ms: 0,
2849
2960
  validations: synthesized,
2850
2961
  context,
2851
- error: detail,
2962
+ ...responseDerivedContextResult(runState),
2963
+ ...(!allResponseDerived && { error: detail }),
2852
2964
  next,
2853
2965
  extraction: { path: 'none' },
2854
2966
  };
@@ -3203,6 +3315,32 @@ client, step, phaseId, context, allSteps, options, state) {
3203
3315
  if (step.expect_error && !taskResult?.data && taskResult?.error) {
3204
3316
  taskResult = { ...taskResult, data: { error: taskResult.error } };
3205
3317
  }
3318
+ const responseDerivedSkip = detectResponseDerivedNotApplicable(effectiveStep, request, taskResult?.data, runState, allSteps);
3319
+ if (responseDerivedSkip && !step.expect_error) {
3320
+ for (const key of responseDerivedSkip.contextKeys) {
3321
+ runState.responseDerivedNotApplicableContextKeys?.set(key, responseDerivedSkip.detail);
3322
+ }
3323
+ const next = getNextStepPreview(step.id, allSteps, context, runState.runnerVars);
3324
+ return {
3325
+ step_id: step.id,
3326
+ phase_id: phaseId,
3327
+ title: step.title,
3328
+ task: step.task,
3329
+ passed: true,
3330
+ skipped: true,
3331
+ skip_reason: 'not_applicable',
3332
+ skip: buildSkip('not_applicable', responseDerivedSkip.detail),
3333
+ duration_ms: stepResult.duration_ms,
3334
+ validations: [],
3335
+ context,
3336
+ ...responseDerivedContextResult(runState),
3337
+ response: (0, redact_secrets_1.redactSecrets)(taskResult?.data),
3338
+ next,
3339
+ request: requestRecord,
3340
+ ...(responseRecord && { response_record: responseRecord }),
3341
+ extraction: extractionFromTaskResult(taskResult),
3342
+ };
3343
+ }
3206
3344
  // Determine pass/fail — inverted when expect_error is set
3207
3345
  let passed;
3208
3346
  if (step.expect_error) {
@@ -3370,6 +3508,9 @@ client, step, phaseId, context, allSteps, options, state) {
3370
3508
  if (passed && hasData && taskResult) {
3371
3509
  const extracted = (0, context_1.extractContextWithProvenance)(effectiveStep.task, taskResult.data, step.id);
3372
3510
  Object.assign(updatedContext, extracted.values);
3511
+ for (const key of Object.keys(extracted.values)) {
3512
+ runState.responseDerivedNotApplicableContextKeys?.delete(key);
3513
+ }
3373
3514
  if (runState.contextProvenance) {
3374
3515
  for (const [key, entry] of Object.entries(extracted.provenance)) {
3375
3516
  runState.contextProvenance.set(key, entry);
@@ -3387,6 +3528,10 @@ client, step, phaseId, context, allSteps, options, state) {
3387
3528
  // ensures the minted value from any same-step $generate:…#<key> inline
3388
3529
  // substitution is visible here.
3389
3530
  if (step.context_outputs?.length) {
3531
+ for (const output of step.context_outputs) {
3532
+ if (output.key)
3533
+ runState.responseDerivedNotApplicableContextKeys?.delete(output.key);
3534
+ }
3390
3535
  // Resolve `task_completion.<path>` outputs against the eventual task
3391
3536
  // artifact rather than the immediate response. When the immediate
3392
3537
  // response is a submitted-arm envelope (HITL / async-signed-IO flows),
@@ -3413,6 +3558,9 @@ client, step, phaseId, context, allSteps, options, state) {
3413
3558
  const remappedOutputs = remapTaskCompletionOutputs(step.context_outputs);
3414
3559
  const explicit = (0, context_1.applyContextOutputsWithProvenance)(extractionData, remappedOutputs, step.id, effectiveStep.task, updatedContext);
3415
3560
  Object.assign(updatedContext, explicit.values);
3561
+ for (const key of Object.keys(explicit.values)) {
3562
+ runState.responseDerivedNotApplicableContextKeys?.delete(key);
3563
+ }
3416
3564
  if (runState.contextProvenance) {
3417
3565
  for (const [key, entry] of Object.entries(explicit.provenance)) {
3418
3566
  runState.contextProvenance.set(key, entry);
@@ -3543,6 +3691,7 @@ client, step, phaseId, context, allSteps, options, state) {
3543
3691
  runState.contextProvenance.size > 0 && {
3544
3692
  context_provenance: Object.fromEntries(runState.contextProvenance),
3545
3693
  }),
3694
+ ...responseDerivedContextResult(runState),
3546
3695
  error: step.expect_error ? undefined : truncateError(stepResult.error || taskResult?.error),
3547
3696
  ...(!step.expect_error && taskResult?.adcp_error && { adcp_error: taskResult.adcp_error }),
3548
3697
  next,
@@ -3559,7 +3708,24 @@ async function executeProbeStep(step, phaseId, context, allSteps, options, runSt
3559
3708
  const start = Date.now();
3560
3709
  let httpResult;
3561
3710
  const probeOpts = { allowPrivateIp: options.allow_http === true };
3562
- if (step.task === 'protected_resource_metadata') {
3711
+ if (step.requires_contract) {
3712
+ const contracts = new Set(options.contracts ?? []);
3713
+ if (!contracts.has(step.requires_contract)) {
3714
+ httpResult = {
3715
+ url: runState.agentUrl,
3716
+ status: 0,
3717
+ headers: {},
3718
+ body: null,
3719
+ skipped: true,
3720
+ skip_reason: 'missing_test_kit_contract',
3721
+ error: `Test-kit contract "${step.requires_contract}" is not configured on this runner.`,
3722
+ };
3723
+ }
3724
+ }
3725
+ if (httpResult) {
3726
+ // Contract-gated synthetic probes self-skip before doing any network work.
3727
+ }
3728
+ else if (step.task === 'protected_resource_metadata') {
3563
3729
  httpResult = await (0, probes_1.probeProtectedResourceMetadata)(runState.agentUrl, probeOpts);
3564
3730
  // RFC 9728 presence semantics (adcp-client#677): a 404 means the agent is
3565
3731
  // honestly not advertising OAuth. Convert to a clean step skip so the
@@ -3585,6 +3751,21 @@ async function executeProbeStep(step, phaseId, context, allSteps, options, runSt
3585
3751
  else if (step.task === 'request_signing_probe') {
3586
3752
  httpResult = await (0, probe_dispatch_1.probeRequestSigningVector)(step.id, runState.agentUrl, options);
3587
3753
  }
3754
+ else if (step.task === 'fetch_brand_jwks') {
3755
+ httpResult = await probeBrandJwks(options._profile?.raw_capabilities, probeOpts);
3756
+ }
3757
+ else if (step.task === 'assert_jwks_purpose') {
3758
+ httpResult = assertJwksPurpose(runState.priorProbes.get('fetch_brand_jwks'), 'webhook-signing');
3759
+ }
3760
+ else if (step.task === 'expect_rate_limit_not_replayed') {
3761
+ httpResult = {
3762
+ url: runState.agentUrl,
3763
+ status: 0,
3764
+ headers: {},
3765
+ body: null,
3766
+ error: 'rate_limit_trip_runner contract is configured, but this SDK runner does not yet implement live rate-limit trip/replay probing.',
3767
+ };
3768
+ }
3588
3769
  if (httpResult)
3589
3770
  runState.priorProbes.set(step.task, httpResult);
3590
3771
  const duration = Date.now() - start;
@@ -3670,6 +3851,90 @@ async function executeProbeStep(step, phaseId, context, allSteps, options, runSt
3670
3851
  extraction,
3671
3852
  };
3672
3853
  }
3854
+ async function probeBrandJwks(rawCapabilities, options) {
3855
+ const brandJsonUrl = (0, capabilities_types_1.readBrandJsonUrl)(rawCapabilities);
3856
+ if (!brandJsonUrl) {
3857
+ return {
3858
+ url: '',
3859
+ status: 0,
3860
+ headers: {},
3861
+ body: null,
3862
+ error: 'identity.brand_json_url missing from get_adcp_capabilities; cannot fetch brand JWKS',
3863
+ };
3864
+ }
3865
+ const brand = await (0, probes_1.fetchProbe)(brandJsonUrl, options);
3866
+ if (brand.error || brand.status < 200 || brand.status >= 300) {
3867
+ return {
3868
+ ...brand,
3869
+ error: brand.error ?? `brand.json fetch returned HTTP ${brand.status}`,
3870
+ };
3871
+ }
3872
+ const agents = brand.body && typeof brand.body === 'object' ? brand.body.agents : undefined;
3873
+ const jwksUri = Array.isArray(agents)
3874
+ ? agents
3875
+ .map(agent => (agent && typeof agent === 'object' ? agent.jwks_uri : undefined))
3876
+ .find((uri) => typeof uri === 'string' && uri.length > 0)
3877
+ : undefined;
3878
+ if (!jwksUri) {
3879
+ return {
3880
+ url: brandJsonUrl,
3881
+ status: 0,
3882
+ headers: {},
3883
+ body: brand.body,
3884
+ error: 'brand.json agents[] did not contain a jwks_uri',
3885
+ };
3886
+ }
3887
+ const jwks = await (0, probes_1.fetchProbe)(jwksUri, options);
3888
+ if (jwks.error || jwks.status < 200 || jwks.status >= 300) {
3889
+ return {
3890
+ ...jwks,
3891
+ error: jwks.error ?? `JWKS fetch returned HTTP ${jwks.status}`,
3892
+ };
3893
+ }
3894
+ return jwks;
3895
+ }
3896
+ function assertJwksPurpose(prior, purpose) {
3897
+ if (!prior || prior.error) {
3898
+ return {
3899
+ url: prior?.url ?? '',
3900
+ status: prior?.status ?? 0,
3901
+ headers: prior?.headers ?? {},
3902
+ body: prior?.body ?? null,
3903
+ error: prior?.error ?? 'fetch_brand_jwks step missing; cannot assert JWKS purpose',
3904
+ };
3905
+ }
3906
+ const keys = prior.body && typeof prior.body === 'object' ? prior.body.keys : undefined;
3907
+ if (!Array.isArray(keys)) {
3908
+ return {
3909
+ url: prior.url,
3910
+ status: 0,
3911
+ headers: {},
3912
+ body: prior.body,
3913
+ error: 'JWKS body does not contain keys[]',
3914
+ };
3915
+ }
3916
+ const matching = keys.filter(key => {
3917
+ if (!key || typeof key !== 'object')
3918
+ return false;
3919
+ const rec = key;
3920
+ return rec.adcp_use === purpose && rec.status !== 'revoked' && rec.revoked !== true;
3921
+ });
3922
+ if (matching.length === 0) {
3923
+ return {
3924
+ url: prior.url,
3925
+ status: 0,
3926
+ headers: {},
3927
+ body: prior.body,
3928
+ error: `JWKS contains no active key with adcp_use="${purpose}"`,
3929
+ };
3930
+ }
3931
+ return {
3932
+ url: prior.url,
3933
+ status: 200,
3934
+ headers: prior.headers,
3935
+ body: { purpose, matching_key_count: matching.length },
3936
+ };
3937
+ }
3673
3938
  function findPriorProbe(priorStepResults) {
3674
3939
  // Fallback for runStoryboardStep where priorProbes isn't populated — reach
3675
3940
  // into the step result's response, which we set to the HttpProbeResult above.