@adcp/sdk 15.9.1 → 15.9.3

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.
@@ -47,6 +47,7 @@ var import_signedRequestRejection = require('../protocols/signedRequestRejection
47
47
  var import_errors = require('../errors/index.js');
48
48
  var import_client_hooks = require('../validation/client-hooks.js');
49
49
  var import_schema_validator = require('../validation/schema-validator.js');
50
+ var import_schema_loader = require('../validation/schema-loader.js');
50
51
  var import_version = require('../version.js');
51
52
  var import_response_unwrapper = require('../utils/response-unwrapper.js');
52
53
  var import_error_extraction = require('../utils/error-extraction.js');
@@ -4397,7 +4398,10 @@ class TaskExecutor {
4397
4398
  const mode = this.responseValidationMode;
4398
4399
  const logViolations = this.config.logSchemaViolations !== false;
4399
4400
  try {
4400
- const validationVersion = (serverVersion ?? this.lastKnownServerVersion) === "v2" ? "v2.5" : this.responseAdcpVersionForValidation() ?? this.config.adcpVersion ?? import_version.ADCP_VERSION;
4401
+ let validationVersion = (serverVersion ?? this.lastKnownServerVersion) === "v2" ? "v2.5" : this.responseAdcpVersionForValidation() ?? this.config.adcpVersion ?? import_version.ADCP_VERSION;
4402
+ if (taskName === "get_adcp_capabilities" && validationVersion !== "v2.5") {
4403
+ validationVersion = (0, import_schema_loader.resolveServedValidationVersion)(response, validationVersion);
4404
+ }
4401
4405
  const normalizedResponse = this.normalizeResponseForValidation(response, taskName, validationVersion === "v2.5");
4402
4406
  const outcome = (0, import_client_hooks.validateIncomingResponse)(taskName, normalizedResponse, mode, void 0, validationVersion);
4403
4407
  if (outcome.valid) return { valid: true, errors: [] };
@@ -15,6 +15,7 @@ import {
15
15
  resolveValidationModes
16
16
  } from "../validation/client-hooks.mjs";
17
17
  import { formatIssues } from "../validation/schema-validator.mjs";
18
+ import { resolveServedValidationVersion } from "../validation/schema-loader.mjs";
18
19
  import { ADCP_VERSION } from "../version.mjs";
19
20
  import { unwrapProtocolResponse, isAdcpOperationSuccess } from "../utils/response-unwrapper.mjs";
20
21
  import { extractAdcpErrorInfo, extractCorrelationId } from "../utils/error-extraction.mjs";
@@ -4370,7 +4371,10 @@ class TaskExecutor {
4370
4371
  const mode = this.responseValidationMode;
4371
4372
  const logViolations = this.config.logSchemaViolations !== false;
4372
4373
  try {
4373
- const validationVersion = (serverVersion ?? this.lastKnownServerVersion) === "v2" ? "v2.5" : this.responseAdcpVersionForValidation() ?? this.config.adcpVersion ?? ADCP_VERSION;
4374
+ let validationVersion = (serverVersion ?? this.lastKnownServerVersion) === "v2" ? "v2.5" : this.responseAdcpVersionForValidation() ?? this.config.adcpVersion ?? ADCP_VERSION;
4375
+ if (taskName === "get_adcp_capabilities" && validationVersion !== "v2.5") {
4376
+ validationVersion = resolveServedValidationVersion(response, validationVersion);
4377
+ }
4374
4378
  const normalizedResponse = this.normalizeResponseForValidation(response, taskName, validationVersion === "v2.5");
4375
4379
  const outcome = validateIncomingResponse(taskName, normalizedResponse, mode, void 0, validationVersion);
4376
4380
  if (outcome.valid) return { valid: true, errors: [] };
@@ -4,5 +4,5 @@
4
4
  "source_sha": "4e553ad955f83b49c7d221ab5c3ff78237ad02e3",
5
5
  "source_tarball_sha256": "580656d6466ef9f0d1119985e6726c2efea718dc671e2ad30957fcb2fd54af0f",
6
6
  "upstream_adcp_version": "2.5.3",
7
- "synced_at": "2026-10-14T11:39:27.882Z"
7
+ "synced_at": "2026-10-16T14:52:19.557Z"
8
8
  }
@@ -50,6 +50,7 @@ var import_ssrf_fetch = require('../net/ssrf-fetch.js');
50
50
  var import_version = require('../version.js');
51
51
  var import_node_crypto = require("node:crypto");
52
52
  var import_client_hooks = require('../validation/client-hooks.js');
53
+ var import_schema_loader = require('../validation/schema-loader.js');
53
54
  var import_error_extraction = require('../utils/error-extraction.js');
54
55
  var import_client = require('../signing/client.js');
55
56
  var import_storage_registry = require('../auth/oauth/storage-registry.js');
@@ -418,7 +419,7 @@ async function discoverAgentProfile(client, signal, schemaAdcpVersion) {
418
419
  caps.data,
419
420
  "strict",
420
421
  void 0,
421
- schemaAdcpVersion ?? client.getAdcpVersion()
422
+ (0, import_schema_loader.resolveServedValidationVersion)(caps.data, schemaAdcpVersion ?? client.getAdcpVersion())
422
423
  );
423
424
  if (!validation.valid) {
424
425
  profile.capabilities_schema_issues = validation.issues.map((issue) => ({
@@ -10,6 +10,7 @@ import { SsrfRefusedError } from "../net/ssrf-fetch.mjs";
10
10
  import { ADCP_VERSION, toReleasePrecisionVersion } from "../version.mjs";
11
11
  import { createHmac, hkdfSync, randomBytes } from "node:crypto";
12
12
  import { validateIncomingResponse } from "../validation/client-hooks.mjs";
13
+ import { resolveServedValidationVersion } from "../validation/schema-loader.mjs";
13
14
  import { extractVersionUnsupportedDetails } from "../utils/error-extraction.mjs";
14
15
  import { buildAgentSigningContext } from "../signing/client.mjs";
15
16
  import { bindAgentStorage } from "../auth/oauth/storage-registry.mjs";
@@ -378,7 +379,7 @@ async function discoverAgentProfile(client, signal, schemaAdcpVersion) {
378
379
  caps.data,
379
380
  "strict",
380
381
  void 0,
381
- schemaAdcpVersion ?? client.getAdcpVersion()
382
+ resolveServedValidationVersion(caps.data, schemaAdcpVersion ?? client.getAdcpVersion())
382
383
  );
383
384
  if (!validation.valid) {
384
385
  profile.capabilities_schema_issues = validation.issues.map((issue) => ({
@@ -239,7 +239,7 @@ function crossValidateProtocolsAndTools(capabilities, tools) {
239
239
  creative: import_capabilities.CREATIVE_TOOLS,
240
240
  sponsored_intelligence: import_capabilities.SPONSORED_INTELLIGENCE_TOOLS,
241
241
  trusted_match: import_capabilities.TRUSTED_MATCH_TOOLS,
242
- compliance: import_capabilities.COMPLIANCE_TOOLS
242
+ compliance_testing: import_capabilities.COMPLIANCE_TOOLS
243
243
  };
244
244
  const perProtocolDiffs = {};
245
245
  for (const protocol of capabilities.protocols) {
@@ -225,7 +225,7 @@ function crossValidateProtocolsAndTools(capabilities, tools) {
225
225
  creative: CREATIVE_TOOLS,
226
226
  sponsored_intelligence: SPONSORED_INTELLIGENCE_TOOLS,
227
227
  trusted_match: TRUSTED_MATCH_TOOLS,
228
- compliance: COMPLIANCE_TOOLS
228
+ compliance_testing: COMPLIANCE_TOOLS
229
229
  };
230
230
  const perProtocolDiffs = {};
231
231
  for (const protocol of capabilities.protocols) {
@@ -82,8 +82,9 @@ function formatStoryboardResultsAsJUnit(results, options = {}) {
82
82
  const suiteGroup = options.suite_groups?.[sb.storyboard_id];
83
83
  const suiteClassname = suiteGroup ? `${suiteGroup}.${sb.storyboard_id}` : sb.storyboard_id;
84
84
  const suiteCases = [];
85
- let suiteFailures = sb.failed_count;
85
+ let suiteFailures = 0;
86
86
  let representedSkipped = 0;
87
+ let suiteSkipped = 0;
87
88
  const passPhases = sb.passes?.length ? sb.passes.map((pass) => ({ label: `Pass ${pass.pass_index} \u203A `, phases: pass.phases })) : [{ label: "", phases: sb.phases }];
88
89
  for (const pass of passPhases) {
89
90
  for (const phase of pass.phases) {
@@ -93,6 +94,7 @@ function formatStoryboardResultsAsJUnit(results, options = {}) {
93
94
  const time = ((step.duration_ms || 0) / 1e3).toFixed(3);
94
95
  if (step.skipped) {
95
96
  representedSkipped += 1;
97
+ suiteSkipped += 1;
96
98
  totalSkipped += 1;
97
99
  suiteCases.push(
98
100
  ` <testcase classname="${xmlEscape(suiteClassname)}" name="${xmlEscape(name)}" time="${time}">
@@ -102,7 +104,6 @@ function formatStoryboardResultsAsJUnit(results, options = {}) {
102
104
  continue;
103
105
  }
104
106
  if (!step.passed) {
105
- totalFailures += 1;
106
107
  const failureDetails = [
107
108
  step.error,
108
109
  ...step.validations.filter((v) => !v.passed).map(
@@ -114,6 +115,17 @@ function formatStoryboardResultsAsJUnit(results, options = {}) {
114
115
  // report body.
115
116
  ...hintLines(step.hints)
116
117
  ].filter(Boolean).join("\n");
118
+ if (step.counts_toward_failure === false) {
119
+ suiteCases.push(
120
+ ` <testcase classname="${xmlEscape(suiteClassname)}" name="${xmlEscape(name)}" time="${time}">
121
+ <system-out>${xmlEscape(`Optional phase failure; excluded from storyboard verdict.
122
+ ${failureDetails}`)}</system-out>
123
+ </testcase>`
124
+ );
125
+ continue;
126
+ }
127
+ totalFailures += 1;
128
+ suiteFailures += 1;
117
129
  const message = step.error || firstHintMessage(step) || "validation failed";
118
130
  suiteCases.push(
119
131
  ` <testcase classname="${xmlEscape(suiteClassname)}" name="${xmlEscape(name)}" time="${time}">
@@ -136,6 +148,7 @@ function formatStoryboardResultsAsJUnit(results, options = {}) {
136
148
  if (hasStoryboardFixtureSkip) {
137
149
  totalTests += 1;
138
150
  totalSkipped += 1;
151
+ suiteSkipped += 1;
139
152
  suiteCases.push(
140
153
  ` <testcase classname="${xmlEscape(suiteClassname)}" name="Fixture resolution" time="0.000">
141
154
  <skipped message="${xmlEscape(fixtureGap.detail)}"/>
@@ -159,7 +172,7 @@ ${assertion.error}` : ""}`;
159
172
  totalDuration += sb.total_duration_ms || 0;
160
173
  const suiteTests = suiteCases.length;
161
174
  suites.push(
162
- ` <testsuite name="${xmlEscape(suiteGroup ? `${suiteGroup} \u203A ${sb.storyboard_title}` : sb.storyboard_title)}"${suiteGroup ? ` package="${xmlEscape(`adcp.${suiteGroup}`)}"` : ""} tests="${suiteTests}" failures="${suiteFailures}" skipped="${sb.skipped_count}" time="${((sb.total_duration_ms || 0) / 1e3).toFixed(3)}" timestamp="${sb.tested_at || (/* @__PURE__ */ new Date()).toISOString()}">
175
+ ` <testsuite name="${xmlEscape(suiteGroup ? `${suiteGroup} \u203A ${sb.storyboard_title}` : sb.storyboard_title)}"${suiteGroup ? ` package="${xmlEscape(`adcp.${suiteGroup}`)}"` : ""} tests="${suiteTests}" failures="${suiteFailures}" skipped="${suiteSkipped}" time="${((sb.total_duration_ms || 0) / 1e3).toFixed(3)}" timestamp="${sb.tested_at || (/* @__PURE__ */ new Date()).toISOString()}">
163
176
  ` + suiteCases.join("\n") + `
164
177
  </testsuite>`
165
178
  );
@@ -58,8 +58,9 @@ function formatStoryboardResultsAsJUnit(results, options = {}) {
58
58
  const suiteGroup = options.suite_groups?.[sb.storyboard_id];
59
59
  const suiteClassname = suiteGroup ? `${suiteGroup}.${sb.storyboard_id}` : sb.storyboard_id;
60
60
  const suiteCases = [];
61
- let suiteFailures = sb.failed_count;
61
+ let suiteFailures = 0;
62
62
  let representedSkipped = 0;
63
+ let suiteSkipped = 0;
63
64
  const passPhases = sb.passes?.length ? sb.passes.map((pass) => ({ label: `Pass ${pass.pass_index} \u203A `, phases: pass.phases })) : [{ label: "", phases: sb.phases }];
64
65
  for (const pass of passPhases) {
65
66
  for (const phase of pass.phases) {
@@ -69,6 +70,7 @@ function formatStoryboardResultsAsJUnit(results, options = {}) {
69
70
  const time = ((step.duration_ms || 0) / 1e3).toFixed(3);
70
71
  if (step.skipped) {
71
72
  representedSkipped += 1;
73
+ suiteSkipped += 1;
72
74
  totalSkipped += 1;
73
75
  suiteCases.push(
74
76
  ` <testcase classname="${xmlEscape(suiteClassname)}" name="${xmlEscape(name)}" time="${time}">
@@ -78,7 +80,6 @@ function formatStoryboardResultsAsJUnit(results, options = {}) {
78
80
  continue;
79
81
  }
80
82
  if (!step.passed) {
81
- totalFailures += 1;
82
83
  const failureDetails = [
83
84
  step.error,
84
85
  ...step.validations.filter((v) => !v.passed).map(
@@ -90,6 +91,17 @@ function formatStoryboardResultsAsJUnit(results, options = {}) {
90
91
  // report body.
91
92
  ...hintLines(step.hints)
92
93
  ].filter(Boolean).join("\n");
94
+ if (step.counts_toward_failure === false) {
95
+ suiteCases.push(
96
+ ` <testcase classname="${xmlEscape(suiteClassname)}" name="${xmlEscape(name)}" time="${time}">
97
+ <system-out>${xmlEscape(`Optional phase failure; excluded from storyboard verdict.
98
+ ${failureDetails}`)}</system-out>
99
+ </testcase>`
100
+ );
101
+ continue;
102
+ }
103
+ totalFailures += 1;
104
+ suiteFailures += 1;
93
105
  const message = step.error || firstHintMessage(step) || "validation failed";
94
106
  suiteCases.push(
95
107
  ` <testcase classname="${xmlEscape(suiteClassname)}" name="${xmlEscape(name)}" time="${time}">
@@ -112,6 +124,7 @@ function formatStoryboardResultsAsJUnit(results, options = {}) {
112
124
  if (hasStoryboardFixtureSkip) {
113
125
  totalTests += 1;
114
126
  totalSkipped += 1;
127
+ suiteSkipped += 1;
115
128
  suiteCases.push(
116
129
  ` <testcase classname="${xmlEscape(suiteClassname)}" name="Fixture resolution" time="0.000">
117
130
  <skipped message="${xmlEscape(fixtureGap.detail)}"/>
@@ -135,7 +148,7 @@ ${assertion.error}` : ""}`;
135
148
  totalDuration += sb.total_duration_ms || 0;
136
149
  const suiteTests = suiteCases.length;
137
150
  suites.push(
138
- ` <testsuite name="${xmlEscape(suiteGroup ? `${suiteGroup} \u203A ${sb.storyboard_title}` : sb.storyboard_title)}"${suiteGroup ? ` package="${xmlEscape(`adcp.${suiteGroup}`)}"` : ""} tests="${suiteTests}" failures="${suiteFailures}" skipped="${sb.skipped_count}" time="${((sb.total_duration_ms || 0) / 1e3).toFixed(3)}" timestamp="${sb.tested_at || (/* @__PURE__ */ new Date()).toISOString()}">
151
+ ` <testsuite name="${xmlEscape(suiteGroup ? `${suiteGroup} \u203A ${sb.storyboard_title}` : sb.storyboard_title)}"${suiteGroup ? ` package="${xmlEscape(`adcp.${suiteGroup}`)}"` : ""} tests="${suiteTests}" failures="${suiteFailures}" skipped="${suiteSkipped}" time="${((sb.total_duration_ms || 0) / 1e3).toFixed(3)}" timestamp="${sb.tested_at || (/* @__PURE__ */ new Date()).toISOString()}">
139
152
  ` + suiteCases.join("\n") + `
140
153
  </testsuite>`
141
154
  );
@@ -548,6 +548,7 @@ function applyBranchSetGrading(phases, phaseResults, branchSetByPhaseId, contrib
548
548
  step.skip = { reason: "peer_branch_taken", detail };
549
549
  delete step.error;
550
550
  delete step.adcp_error;
551
+ delete step.counts_toward_failure;
551
552
  skippedDelta++;
552
553
  regraded = true;
553
554
  }
@@ -2646,6 +2647,8 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
2646
2647
  if (!phase.optional || (routingContext ? routedOauthPresent.has(assignment.instanceIndex) : presenceDetected)) {
2647
2648
  failedCount++;
2648
2649
  countedAsFailed.add(result2);
2650
+ } else if (!result2.validations.some((validation) => validation.check === "assertion" && !validation.passed)) {
2651
+ result2.counts_toward_failure = false;
2649
2652
  }
2650
2653
  if (step.stateful) {
2651
2654
  phaseStatefulCascades.set(phase.id, null);
@@ -3657,9 +3660,20 @@ async function executeStep(client, step, storyboardId, phaseId, context, allStep
3657
3660
  const testsIdempotencyKeyOmission = (step.omit_idempotency_key === true || governedRequest && (typeof request.idempotency_key !== "string" || request.idempotency_key.length === 0)) && (0, import_idempotency.requestUsesIdempotency)(effectiveStep.task, request);
3658
3661
  const testsMissingAccount = effectiveStep.task === "create_media_buy" && (step.omit_account === true || governedRequest && !Object.prototype.hasOwnProperty.call(request, "account"));
3659
3662
  const testsSchemaInvalidRequest = step.expect_error === true && step.negative_path !== "payload_well_formed";
3663
+ const useSdkAuthOverride = typeof step.auth === "object" && step.auth.type === "api_key" && step.auth.from_test_kit === true && step.auth.value_strategy === void 0 && step.expect_error !== true && !(step.validations ?? []).some((validation) => HTTP_PROBE_ONLY_CHECKS.has(validation.check));
3660
3664
  let rawProbeHeaders;
3665
+ let authOverrideClient;
3661
3666
  try {
3662
3667
  rawProbeHeaders = step.auth !== void 0 ? authHeadersForStep(step.auth, options) : void 0;
3668
+ if (useSdkAuthOverride) {
3669
+ if (rawProbeHeaders === void 0) throw new Error("Step auth override credentials were not resolved");
3670
+ authOverrideClient = createAuthOverrideClient(
3671
+ runState.agentUrl,
3672
+ options.protocol ?? "mcp",
3673
+ options,
3674
+ rawProbeHeaders
3675
+ );
3676
+ }
3663
3677
  } catch (err) {
3664
3678
  return {
3665
3679
  step_id: step.id,
@@ -3674,7 +3688,8 @@ async function executeStep(client, step, storyboardId, phaseId, context, allStep
3674
3688
  extraction: { path: "none" }
3675
3689
  };
3676
3690
  }
3677
- const useRawProbe = rawProbeHeaders !== void 0;
3691
+ const useRawProbe = rawProbeHeaders !== void 0 && !useSdkAuthOverride;
3692
+ const stepClient = () => authOverrideClient ?? client;
3678
3693
  let taskResult;
3679
3694
  let stepResult;
3680
3695
  let caughtError;
@@ -3758,7 +3773,7 @@ async function executeStep(client, step, storyboardId, phaseId, context, allStep
3758
3773
  const started = Date.now();
3759
3774
  try {
3760
3775
  if (options.protocol === "a2a") {
3761
- const probeClient = createA2AAuthOverrideClient(runState.agentUrl, options, rawProbeHeaders ?? {});
3776
+ const probeClient = createAuthOverrideClient(runState.agentUrl, "a2a", options, rawProbeHeaders ?? {});
3762
3777
  const captured = await (0, import_rawResponseCapture.withRawResponseCapture)(
3763
3778
  () => (0, import_client.runStep)(
3764
3779
  step.title,
@@ -3853,7 +3868,7 @@ async function executeStep(client, step, storyboardId, phaseId, context, allStep
3853
3868
  let a2aCaptures;
3854
3869
  if (step.parallel_dispatch) {
3855
3870
  const started = Date.now();
3856
- crossResponses = await (0, import_parallel_dispatch.runParallelDispatches)(client, effectiveStep.task, request, {
3871
+ crossResponses = await (0, import_parallel_dispatch.runParallelDispatches)(stepClient(), effectiveStep.task, request, {
3857
3872
  spec: step.parallel_dispatch,
3858
3873
  keyMinter: import_idempotency.generateIdempotencyKey,
3859
3874
  correlationPrefix: step.id,
@@ -3890,7 +3905,7 @@ async function executeStep(client, step, storyboardId, phaseId, context, allStep
3890
3905
  };
3891
3906
  }
3892
3907
  } else {
3893
- const dispatch = () => (0, import_task_map.executeStoryboardTask)(client, effectiveStep.task, request, {
3908
+ const dispatch = () => (0, import_task_map.executeStoryboardTask)(stepClient(), effectiveStep.task, request, {
3894
3909
  skipIdempotencyAutoInject: testsIdempotencyKeyOmission,
3895
3910
  skipAccountValidation: testsMissingAccount,
3896
3911
  skipRequestValidation: testsSchemaInvalidRequest,
@@ -5699,8 +5714,10 @@ function defaultAuthHeadersForRawProbe(options) {
5699
5714
  }
5700
5715
  function sessionProbeRoutingHeaders(options) {
5701
5716
  const headers = {};
5717
+ const runSecrets = runCredentialValues(options);
5702
5718
  for (const [name, value] of Object.entries(options.headers ?? {})) {
5703
5719
  if (!SESSION_PROBE_ROUTING_HEADERS.has(name.toLowerCase())) continue;
5720
+ if (runSecrets.some((secret) => value.includes(secret))) continue;
5704
5721
  try {
5705
5722
  assertSafeAuthHeaderPart(value, `options.headers.${name}`);
5706
5723
  } catch {
@@ -5725,35 +5742,62 @@ function setHeaderIfAbsent(headers, name, value, label) {
5725
5742
  }
5726
5743
  function runCredentialValues(options) {
5727
5744
  const values = [];
5745
+ const pushHeaderToken = (value) => {
5746
+ values.push(value, value.replace(/^[\t ]+|[\t ]+$/g, ""));
5747
+ };
5748
+ const pushUserPass = (userpass) => {
5749
+ values.push(userpass, Buffer.from(userpass).toString("base64"));
5750
+ const separator = userpass.indexOf(":");
5751
+ if (separator >= 0) values.push(userpass.slice(separator + 1));
5752
+ };
5728
5753
  const auth = options.auth;
5729
- if (auth?.type === "bearer" && typeof auth.token === "string") values.push(auth.token);
5754
+ if (auth?.type === "bearer" && typeof auth.token === "string") pushHeaderToken(auth.token);
5730
5755
  if (auth?.type === "basic") {
5731
5756
  if (typeof auth.password === "string") values.push(auth.password);
5732
5757
  if (typeof auth.username === "string" && typeof auth.password === "string") {
5733
- values.push(`${auth.username}:${auth.password}`);
5758
+ pushUserPass(`${auth.username}:${auth.password}`);
5734
5759
  }
5735
5760
  }
5736
5761
  if (auth?.type === "oauth" || auth?.type === "oauth_client_credentials") {
5737
5762
  const token = auth.tokens?.access_token;
5738
- if (typeof token === "string") values.push(token);
5763
+ if (typeof token === "string") pushHeaderToken(token);
5739
5764
  }
5740
5765
  const kit = options.test_kit?.auth;
5741
- if (typeof kit?.api_key === "string") values.push(kit.api_key);
5766
+ if (typeof kit?.api_key === "string") pushHeaderToken(kit.api_key);
5742
5767
  const basic = kit?.basic;
5743
5768
  if (typeof basic?.password === "string") values.push(basic.password);
5744
5769
  if (typeof basic?.username === "string" && typeof basic?.password === "string") {
5745
- values.push(`${basic.username}:${basic.password}`);
5770
+ pushUserPass(`${basic.username}:${basic.password}`);
5746
5771
  }
5747
- if (typeof basic?.credentials === "string") values.push(basic.credentials);
5772
+ if (typeof basic?.credentials === "string") pushUserPass(basic.credentials);
5748
5773
  for (const [name, value] of Object.entries(options.headers ?? {})) {
5749
5774
  if (typeof value !== "string" || value.length === 0) continue;
5750
- if (AUTH_OVERRIDE_HEADER_NAMES.has(name.toLowerCase()) || isCredentialHeaderName(name)) values.push(value);
5775
+ if (!AUTH_OVERRIDE_HEADER_NAMES.has(name.toLowerCase()) && !isCredentialHeaderName(name)) continue;
5776
+ pushHeaderToken(value);
5777
+ const scheme = /^(Bearer|Basic|Token|ApiKey)\s+(.+)$/i.exec(value.trim());
5778
+ if (scheme?.[1] !== void 0 && scheme[2] !== void 0) {
5779
+ const payload = scheme[2].trim();
5780
+ values.push(payload);
5781
+ if (/^Basic$/i.test(scheme[1]) && /^[A-Za-z0-9+/]+={0,2}$/.test(payload)) {
5782
+ const bytes = Buffer.from(payload, "base64");
5783
+ const decoded = bytes.toString("utf8");
5784
+ if (bytes.toString("base64").replace(/=+$/, "") === payload.replace(/=+$/, "") && Buffer.from(decoded, "utf8").equals(bytes) && !/[\x00-\x1f\x7f]/.test(decoded) && decoded.includes(":")) {
5785
+ pushUserPass(decoded);
5786
+ }
5787
+ }
5788
+ }
5751
5789
  }
5752
5790
  return values.filter((value) => value.trim().length > 0);
5753
5791
  }
5754
5792
  const SESSION_PROBE_ROUTING_HEADERS = /* @__PURE__ */ new Set([
5755
5793
  "x-tenant-id",
5756
5794
  "x-tenant",
5795
+ // AdCP's own tenant-routing header (`src/lib/testing/types.ts` names it as
5796
+ // the canonical example). Omitting it meant a run that routes on it had the
5797
+ // header classified as unknown: dropped from session probes, and — once the
5798
+ // isolated-client value test landed — silently strippable on a credential
5799
+ // collision, which reroutes the step to another tenant.
5800
+ "x-adcp-tenant",
5757
5801
  "x-account",
5758
5802
  "x-account-id",
5759
5803
  "x-routing-key",
@@ -5772,16 +5816,17 @@ function unroutableProbeHeader(options) {
5772
5816
  for (const [name, value] of Object.entries(options.headers ?? {})) {
5773
5817
  const lower = name.toLowerCase();
5774
5818
  if (SESSION_PROBE_ROUTING_HEADERS.has(lower)) {
5819
+ if (runSecrets.some((secret) => value.includes(secret))) return { name, reason: "credential_collision" };
5775
5820
  try {
5776
5821
  assertSafeAuthHeaderPart(value, `options.headers.${name}`);
5777
5822
  } catch {
5778
- return name;
5823
+ return { name, reason: "malformed" };
5779
5824
  }
5780
5825
  continue;
5781
5826
  }
5782
5827
  if (AUTH_OVERRIDE_HEADER_NAMES.has(lower)) continue;
5783
5828
  if (runSecrets.some((secret) => value.includes(secret))) continue;
5784
- return name;
5829
+ return { name, reason: "unknown" };
5785
5830
  }
5786
5831
  return void 0;
5787
5832
  }
@@ -6015,11 +6060,9 @@ function planMcpSessionSentinel(step, options, client, agentUrl) {
6015
6060
  }
6016
6061
  const unroutable = unroutableProbeHeader(options);
6017
6062
  if (unroutable !== void 0) {
6018
- return {
6019
- kind: "skip",
6020
- reason: "session_probe_ungradable",
6021
- detail: `The runner cannot classify run header "${escapeControlChars(unroutable.slice(0, 64))}", and both answers are unsafe \u2014 forwarding it could make the unauthenticated probe authenticated, dropping it could send the probe to a different tenant than the rest of this run. Nothing was graded, and this is a property of the run, not of the agent. Three ways forward: rename it to a recognised routing header (${[...SESSION_PROBE_ROUTING_HEADERS].join(", ")}); drop it from this run if the probe does not need it; or, if it carries a credential, move that into \`options.auth\` / \`test_kit.auth\` where the runner owns it.`
6022
- };
6063
+ const name = escapeControlChars(unroutable.name.slice(0, 64));
6064
+ const detail = unroutable.reason === "credential_collision" ? `Routing header "${name}" has a value containing a credential this run holds. Nothing was graded: forwarding it could authenticate the unauthenticated probe, and dropping it could change tenants. Choose a routing value or suite credential that does not collide.` : unroutable.reason === "malformed" ? `Routing header "${name}" contains invalid header characters. Nothing was graded because dropping it could change tenants. Correct this run header before retrying the probe.` : `The runner cannot classify run header "${name}", and both answers are unsafe \u2014 forwarding it could make the unauthenticated probe authenticated, dropping it could send the probe to a different tenant than the rest of this run. Nothing was graded, and this is a property of the run, not of the agent. Three ways forward: rename it to a recognised routing header (${[...SESSION_PROBE_ROUTING_HEADERS].join(", ")}); drop it from this run if the probe does not need it; or, if it carries a credential, move that into \`options.auth\` / \`test_kit.auth\` where the runner owns it.`;
6065
+ return { kind: "skip", reason: "session_probe_ungradable", detail };
6023
6066
  }
6024
6067
  const routing = sessionProbeRoutingHeaders(options);
6025
6068
  let headers;
@@ -6052,6 +6095,11 @@ function authHeadersForStep(directive, options) {
6052
6095
  if ("value" in directive && directive.value) {
6053
6096
  value = directive.value;
6054
6097
  } else if ("from_test_kit" in directive && directive.from_test_kit) {
6098
+ if (typeof directive.from_test_kit === "string") {
6099
+ throw new Error(
6100
+ "Named test-kit API-key paths are not supported; use from_test_kit: true only for the primary key."
6101
+ );
6102
+ }
6055
6103
  value = options.test_kit?.auth?.api_key;
6056
6104
  if (!value) {
6057
6105
  throw new Error(
@@ -6068,6 +6116,13 @@ function authHeadersForStep(directive, options) {
6068
6116
  }
6069
6117
  return { authorization: `Bearer ${value}` };
6070
6118
  }
6119
+ const HTTP_PROBE_ONLY_CHECKS = /* @__PURE__ */ new Set([
6120
+ "http_status",
6121
+ "http_status_in",
6122
+ "probe_passed",
6123
+ "on_401_require_header",
6124
+ "resource_equals_agent_url"
6125
+ ]);
6071
6126
  const AUTH_OVERRIDE_HEADER_NAMES = /* @__PURE__ */ new Set([
6072
6127
  "authorization",
6073
6128
  "proxy-authorization",
@@ -6076,14 +6131,28 @@ const AUTH_OVERRIDE_HEADER_NAMES = /* @__PURE__ */ new Set([
6076
6131
  "x-api-key"
6077
6132
  ]);
6078
6133
  const AUTH_OVERRIDE_CREDENTIAL_HEADER_RE = /(^|[-_])(auth(?:entication|orization)?|credentials?|secrets?|tokens?|keys?|api[-_]?keys?|access[-_]?keys?|private[-_]?keys?|password|passwd|signatures?|cert(?:ificate)?s?)([-_]|$)/i;
6134
+ const AUTH_OVERRIDE_COMPACT_CREDENTIAL_RE = /(auth(?:entication|orization)?|credentials?|secrets?|tokens?|passwords?|passwd|signatures?|certificates?|bearer|(?:api|access|private|session|auth|secret|client|app|master|signing|shared|subscription)keys?|(?:client|tls|ssl)certs?)$/i;
6079
6135
  function isCredentialHeaderName(name) {
6080
6136
  const normalized = name.toLowerCase();
6081
- return AUTH_OVERRIDE_HEADER_NAMES.has(normalized) || AUTH_OVERRIDE_CREDENTIAL_HEADER_RE.test(normalized);
6137
+ if (SESSION_PROBE_ROUTING_HEADERS.has(normalized)) return false;
6138
+ return AUTH_OVERRIDE_HEADER_NAMES.has(normalized) || AUTH_OVERRIDE_CREDENTIAL_HEADER_RE.test(normalized) || AUTH_OVERRIDE_COMPACT_CREDENTIAL_RE.test(normalized.replace(/[-_]/g, ""));
6082
6139
  }
6083
- function createA2AAuthOverrideClient(agentUrl, options, authHeaders) {
6084
- const headers = Object.fromEntries(
6085
- Object.entries(options.headers ?? {}).filter(([name]) => !isCredentialHeaderName(name))
6086
- );
6140
+ function createAuthOverrideClient(agentUrl, protocol, options, authHeaders) {
6141
+ const runSecrets = runCredentialValues(options);
6142
+ const headers = {};
6143
+ for (const [name, value] of Object.entries(options.headers ?? {})) {
6144
+ const isRouting = SESSION_PROBE_ROUTING_HEADERS.has(name.toLowerCase());
6145
+ if (!isRouting && isCredentialHeaderName(name)) continue;
6146
+ if (typeof value === "string" && runSecrets.some((secret) => value.includes(secret))) {
6147
+ if (isRouting) {
6148
+ throw new Error(
6149
+ `options.headers.${name} is a routing header but its value contains a credential this run holds; an isolated step cannot both route correctly and withhold the suite principal. Choose a routing value or suite credential that does not collide.`
6150
+ );
6151
+ }
6152
+ continue;
6153
+ }
6154
+ headers[name] = value;
6155
+ }
6087
6156
  let auth;
6088
6157
  for (const [name, value] of Object.entries(authHeaders)) {
6089
6158
  if (name.toLowerCase() === "authorization" && value.startsWith("Bearer ")) {
@@ -6092,9 +6161,9 @@ function createA2AAuthOverrideClient(agentUrl, options, authHeaders) {
6092
6161
  headers[name] = value;
6093
6162
  }
6094
6163
  }
6095
- return (0, import_client.createTestClient)(agentUrl, "a2a", {
6164
+ return (0, import_client.createTestClient)(agentUrl, protocol, {
6096
6165
  ...options,
6097
- protocol: "a2a",
6166
+ protocol,
6098
6167
  auth,
6099
6168
  test_kit: void 0,
6100
6169
  _client: void 0,
@@ -570,6 +570,7 @@ function applyBranchSetGrading(phases, phaseResults, branchSetByPhaseId, contrib
570
570
  step.skip = { reason: "peer_branch_taken", detail };
571
571
  delete step.error;
572
572
  delete step.adcp_error;
573
+ delete step.counts_toward_failure;
573
574
  skippedDelta++;
574
575
  regraded = true;
575
576
  }
@@ -2668,6 +2669,8 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
2668
2669
  if (!phase.optional || (routingContext ? routedOauthPresent.has(assignment.instanceIndex) : presenceDetected)) {
2669
2670
  failedCount++;
2670
2671
  countedAsFailed.add(result2);
2672
+ } else if (!result2.validations.some((validation) => validation.check === "assertion" && !validation.passed)) {
2673
+ result2.counts_toward_failure = false;
2671
2674
  }
2672
2675
  if (step.stateful) {
2673
2676
  phaseStatefulCascades.set(phase.id, null);
@@ -3679,9 +3682,20 @@ async function executeStep(client, step, storyboardId, phaseId, context, allStep
3679
3682
  const testsIdempotencyKeyOmission = (step.omit_idempotency_key === true || governedRequest && (typeof request.idempotency_key !== "string" || request.idempotency_key.length === 0)) && requestUsesIdempotency(effectiveStep.task, request);
3680
3683
  const testsMissingAccount = effectiveStep.task === "create_media_buy" && (step.omit_account === true || governedRequest && !Object.prototype.hasOwnProperty.call(request, "account"));
3681
3684
  const testsSchemaInvalidRequest = step.expect_error === true && step.negative_path !== "payload_well_formed";
3685
+ const useSdkAuthOverride = typeof step.auth === "object" && step.auth.type === "api_key" && step.auth.from_test_kit === true && step.auth.value_strategy === void 0 && step.expect_error !== true && !(step.validations ?? []).some((validation) => HTTP_PROBE_ONLY_CHECKS.has(validation.check));
3682
3686
  let rawProbeHeaders;
3687
+ let authOverrideClient;
3683
3688
  try {
3684
3689
  rawProbeHeaders = step.auth !== void 0 ? authHeadersForStep(step.auth, options) : void 0;
3690
+ if (useSdkAuthOverride) {
3691
+ if (rawProbeHeaders === void 0) throw new Error("Step auth override credentials were not resolved");
3692
+ authOverrideClient = createAuthOverrideClient(
3693
+ runState.agentUrl,
3694
+ options.protocol ?? "mcp",
3695
+ options,
3696
+ rawProbeHeaders
3697
+ );
3698
+ }
3685
3699
  } catch (err) {
3686
3700
  return {
3687
3701
  step_id: step.id,
@@ -3696,7 +3710,8 @@ async function executeStep(client, step, storyboardId, phaseId, context, allStep
3696
3710
  extraction: { path: "none" }
3697
3711
  };
3698
3712
  }
3699
- const useRawProbe = rawProbeHeaders !== void 0;
3713
+ const useRawProbe = rawProbeHeaders !== void 0 && !useSdkAuthOverride;
3714
+ const stepClient = () => authOverrideClient ?? client;
3700
3715
  let taskResult;
3701
3716
  let stepResult;
3702
3717
  let caughtError;
@@ -3780,7 +3795,7 @@ async function executeStep(client, step, storyboardId, phaseId, context, allStep
3780
3795
  const started = Date.now();
3781
3796
  try {
3782
3797
  if (options.protocol === "a2a") {
3783
- const probeClient = createA2AAuthOverrideClient(runState.agentUrl, options, rawProbeHeaders ?? {});
3798
+ const probeClient = createAuthOverrideClient(runState.agentUrl, "a2a", options, rawProbeHeaders ?? {});
3784
3799
  const captured = await withRawResponseCapture(
3785
3800
  () => runStep(
3786
3801
  step.title,
@@ -3875,7 +3890,7 @@ async function executeStep(client, step, storyboardId, phaseId, context, allStep
3875
3890
  let a2aCaptures;
3876
3891
  if (step.parallel_dispatch) {
3877
3892
  const started = Date.now();
3878
- crossResponses = await runParallelDispatches(client, effectiveStep.task, request, {
3893
+ crossResponses = await runParallelDispatches(stepClient(), effectiveStep.task, request, {
3879
3894
  spec: step.parallel_dispatch,
3880
3895
  keyMinter: generateIdempotencyKey,
3881
3896
  correlationPrefix: step.id,
@@ -3912,7 +3927,7 @@ async function executeStep(client, step, storyboardId, phaseId, context, allStep
3912
3927
  };
3913
3928
  }
3914
3929
  } else {
3915
- const dispatch = () => executeStoryboardTask(client, effectiveStep.task, request, {
3930
+ const dispatch = () => executeStoryboardTask(stepClient(), effectiveStep.task, request, {
3916
3931
  skipIdempotencyAutoInject: testsIdempotencyKeyOmission,
3917
3932
  skipAccountValidation: testsMissingAccount,
3918
3933
  skipRequestValidation: testsSchemaInvalidRequest,
@@ -5721,8 +5736,10 @@ function defaultAuthHeadersForRawProbe(options) {
5721
5736
  }
5722
5737
  function sessionProbeRoutingHeaders(options) {
5723
5738
  const headers = {};
5739
+ const runSecrets = runCredentialValues(options);
5724
5740
  for (const [name, value] of Object.entries(options.headers ?? {})) {
5725
5741
  if (!SESSION_PROBE_ROUTING_HEADERS.has(name.toLowerCase())) continue;
5742
+ if (runSecrets.some((secret) => value.includes(secret))) continue;
5726
5743
  try {
5727
5744
  assertSafeAuthHeaderPart(value, `options.headers.${name}`);
5728
5745
  } catch {
@@ -5747,35 +5764,62 @@ function setHeaderIfAbsent(headers, name, value, label) {
5747
5764
  }
5748
5765
  function runCredentialValues(options) {
5749
5766
  const values = [];
5767
+ const pushHeaderToken = (value) => {
5768
+ values.push(value, value.replace(/^[\t ]+|[\t ]+$/g, ""));
5769
+ };
5770
+ const pushUserPass = (userpass) => {
5771
+ values.push(userpass, Buffer.from(userpass).toString("base64"));
5772
+ const separator = userpass.indexOf(":");
5773
+ if (separator >= 0) values.push(userpass.slice(separator + 1));
5774
+ };
5750
5775
  const auth = options.auth;
5751
- if (auth?.type === "bearer" && typeof auth.token === "string") values.push(auth.token);
5776
+ if (auth?.type === "bearer" && typeof auth.token === "string") pushHeaderToken(auth.token);
5752
5777
  if (auth?.type === "basic") {
5753
5778
  if (typeof auth.password === "string") values.push(auth.password);
5754
5779
  if (typeof auth.username === "string" && typeof auth.password === "string") {
5755
- values.push(`${auth.username}:${auth.password}`);
5780
+ pushUserPass(`${auth.username}:${auth.password}`);
5756
5781
  }
5757
5782
  }
5758
5783
  if (auth?.type === "oauth" || auth?.type === "oauth_client_credentials") {
5759
5784
  const token = auth.tokens?.access_token;
5760
- if (typeof token === "string") values.push(token);
5785
+ if (typeof token === "string") pushHeaderToken(token);
5761
5786
  }
5762
5787
  const kit = options.test_kit?.auth;
5763
- if (typeof kit?.api_key === "string") values.push(kit.api_key);
5788
+ if (typeof kit?.api_key === "string") pushHeaderToken(kit.api_key);
5764
5789
  const basic = kit?.basic;
5765
5790
  if (typeof basic?.password === "string") values.push(basic.password);
5766
5791
  if (typeof basic?.username === "string" && typeof basic?.password === "string") {
5767
- values.push(`${basic.username}:${basic.password}`);
5792
+ pushUserPass(`${basic.username}:${basic.password}`);
5768
5793
  }
5769
- if (typeof basic?.credentials === "string") values.push(basic.credentials);
5794
+ if (typeof basic?.credentials === "string") pushUserPass(basic.credentials);
5770
5795
  for (const [name, value] of Object.entries(options.headers ?? {})) {
5771
5796
  if (typeof value !== "string" || value.length === 0) continue;
5772
- if (AUTH_OVERRIDE_HEADER_NAMES.has(name.toLowerCase()) || isCredentialHeaderName(name)) values.push(value);
5797
+ if (!AUTH_OVERRIDE_HEADER_NAMES.has(name.toLowerCase()) && !isCredentialHeaderName(name)) continue;
5798
+ pushHeaderToken(value);
5799
+ const scheme = /^(Bearer|Basic|Token|ApiKey)\s+(.+)$/i.exec(value.trim());
5800
+ if (scheme?.[1] !== void 0 && scheme[2] !== void 0) {
5801
+ const payload = scheme[2].trim();
5802
+ values.push(payload);
5803
+ if (/^Basic$/i.test(scheme[1]) && /^[A-Za-z0-9+/]+={0,2}$/.test(payload)) {
5804
+ const bytes = Buffer.from(payload, "base64");
5805
+ const decoded = bytes.toString("utf8");
5806
+ if (bytes.toString("base64").replace(/=+$/, "") === payload.replace(/=+$/, "") && Buffer.from(decoded, "utf8").equals(bytes) && !/[\x00-\x1f\x7f]/.test(decoded) && decoded.includes(":")) {
5807
+ pushUserPass(decoded);
5808
+ }
5809
+ }
5810
+ }
5773
5811
  }
5774
5812
  return values.filter((value) => value.trim().length > 0);
5775
5813
  }
5776
5814
  const SESSION_PROBE_ROUTING_HEADERS = /* @__PURE__ */ new Set([
5777
5815
  "x-tenant-id",
5778
5816
  "x-tenant",
5817
+ // AdCP's own tenant-routing header (`src/lib/testing/types.ts` names it as
5818
+ // the canonical example). Omitting it meant a run that routes on it had the
5819
+ // header classified as unknown: dropped from session probes, and — once the
5820
+ // isolated-client value test landed — silently strippable on a credential
5821
+ // collision, which reroutes the step to another tenant.
5822
+ "x-adcp-tenant",
5779
5823
  "x-account",
5780
5824
  "x-account-id",
5781
5825
  "x-routing-key",
@@ -5794,16 +5838,17 @@ function unroutableProbeHeader(options) {
5794
5838
  for (const [name, value] of Object.entries(options.headers ?? {})) {
5795
5839
  const lower = name.toLowerCase();
5796
5840
  if (SESSION_PROBE_ROUTING_HEADERS.has(lower)) {
5841
+ if (runSecrets.some((secret) => value.includes(secret))) return { name, reason: "credential_collision" };
5797
5842
  try {
5798
5843
  assertSafeAuthHeaderPart(value, `options.headers.${name}`);
5799
5844
  } catch {
5800
- return name;
5845
+ return { name, reason: "malformed" };
5801
5846
  }
5802
5847
  continue;
5803
5848
  }
5804
5849
  if (AUTH_OVERRIDE_HEADER_NAMES.has(lower)) continue;
5805
5850
  if (runSecrets.some((secret) => value.includes(secret))) continue;
5806
- return name;
5851
+ return { name, reason: "unknown" };
5807
5852
  }
5808
5853
  return void 0;
5809
5854
  }
@@ -6037,11 +6082,9 @@ function planMcpSessionSentinel(step, options, client, agentUrl) {
6037
6082
  }
6038
6083
  const unroutable = unroutableProbeHeader(options);
6039
6084
  if (unroutable !== void 0) {
6040
- return {
6041
- kind: "skip",
6042
- reason: "session_probe_ungradable",
6043
- detail: `The runner cannot classify run header "${escapeControlChars(unroutable.slice(0, 64))}", and both answers are unsafe \u2014 forwarding it could make the unauthenticated probe authenticated, dropping it could send the probe to a different tenant than the rest of this run. Nothing was graded, and this is a property of the run, not of the agent. Three ways forward: rename it to a recognised routing header (${[...SESSION_PROBE_ROUTING_HEADERS].join(", ")}); drop it from this run if the probe does not need it; or, if it carries a credential, move that into \`options.auth\` / \`test_kit.auth\` where the runner owns it.`
6044
- };
6085
+ const name = escapeControlChars(unroutable.name.slice(0, 64));
6086
+ const detail = unroutable.reason === "credential_collision" ? `Routing header "${name}" has a value containing a credential this run holds. Nothing was graded: forwarding it could authenticate the unauthenticated probe, and dropping it could change tenants. Choose a routing value or suite credential that does not collide.` : unroutable.reason === "malformed" ? `Routing header "${name}" contains invalid header characters. Nothing was graded because dropping it could change tenants. Correct this run header before retrying the probe.` : `The runner cannot classify run header "${name}", and both answers are unsafe \u2014 forwarding it could make the unauthenticated probe authenticated, dropping it could send the probe to a different tenant than the rest of this run. Nothing was graded, and this is a property of the run, not of the agent. Three ways forward: rename it to a recognised routing header (${[...SESSION_PROBE_ROUTING_HEADERS].join(", ")}); drop it from this run if the probe does not need it; or, if it carries a credential, move that into \`options.auth\` / \`test_kit.auth\` where the runner owns it.`;
6087
+ return { kind: "skip", reason: "session_probe_ungradable", detail };
6045
6088
  }
6046
6089
  const routing = sessionProbeRoutingHeaders(options);
6047
6090
  let headers;
@@ -6074,6 +6117,11 @@ function authHeadersForStep(directive, options) {
6074
6117
  if ("value" in directive && directive.value) {
6075
6118
  value = directive.value;
6076
6119
  } else if ("from_test_kit" in directive && directive.from_test_kit) {
6120
+ if (typeof directive.from_test_kit === "string") {
6121
+ throw new Error(
6122
+ "Named test-kit API-key paths are not supported; use from_test_kit: true only for the primary key."
6123
+ );
6124
+ }
6077
6125
  value = options.test_kit?.auth?.api_key;
6078
6126
  if (!value) {
6079
6127
  throw new Error(
@@ -6090,6 +6138,13 @@ function authHeadersForStep(directive, options) {
6090
6138
  }
6091
6139
  return { authorization: `Bearer ${value}` };
6092
6140
  }
6141
+ const HTTP_PROBE_ONLY_CHECKS = /* @__PURE__ */ new Set([
6142
+ "http_status",
6143
+ "http_status_in",
6144
+ "probe_passed",
6145
+ "on_401_require_header",
6146
+ "resource_equals_agent_url"
6147
+ ]);
6093
6148
  const AUTH_OVERRIDE_HEADER_NAMES = /* @__PURE__ */ new Set([
6094
6149
  "authorization",
6095
6150
  "proxy-authorization",
@@ -6098,14 +6153,28 @@ const AUTH_OVERRIDE_HEADER_NAMES = /* @__PURE__ */ new Set([
6098
6153
  "x-api-key"
6099
6154
  ]);
6100
6155
  const AUTH_OVERRIDE_CREDENTIAL_HEADER_RE = /(^|[-_])(auth(?:entication|orization)?|credentials?|secrets?|tokens?|keys?|api[-_]?keys?|access[-_]?keys?|private[-_]?keys?|password|passwd|signatures?|cert(?:ificate)?s?)([-_]|$)/i;
6156
+ const AUTH_OVERRIDE_COMPACT_CREDENTIAL_RE = /(auth(?:entication|orization)?|credentials?|secrets?|tokens?|passwords?|passwd|signatures?|certificates?|bearer|(?:api|access|private|session|auth|secret|client|app|master|signing|shared|subscription)keys?|(?:client|tls|ssl)certs?)$/i;
6101
6157
  function isCredentialHeaderName(name) {
6102
6158
  const normalized = name.toLowerCase();
6103
- return AUTH_OVERRIDE_HEADER_NAMES.has(normalized) || AUTH_OVERRIDE_CREDENTIAL_HEADER_RE.test(normalized);
6159
+ if (SESSION_PROBE_ROUTING_HEADERS.has(normalized)) return false;
6160
+ return AUTH_OVERRIDE_HEADER_NAMES.has(normalized) || AUTH_OVERRIDE_CREDENTIAL_HEADER_RE.test(normalized) || AUTH_OVERRIDE_COMPACT_CREDENTIAL_RE.test(normalized.replace(/[-_]/g, ""));
6104
6161
  }
6105
- function createA2AAuthOverrideClient(agentUrl, options, authHeaders) {
6106
- const headers = Object.fromEntries(
6107
- Object.entries(options.headers ?? {}).filter(([name]) => !isCredentialHeaderName(name))
6108
- );
6162
+ function createAuthOverrideClient(agentUrl, protocol, options, authHeaders) {
6163
+ const runSecrets = runCredentialValues(options);
6164
+ const headers = {};
6165
+ for (const [name, value] of Object.entries(options.headers ?? {})) {
6166
+ const isRouting = SESSION_PROBE_ROUTING_HEADERS.has(name.toLowerCase());
6167
+ if (!isRouting && isCredentialHeaderName(name)) continue;
6168
+ if (typeof value === "string" && runSecrets.some((secret) => value.includes(secret))) {
6169
+ if (isRouting) {
6170
+ throw new Error(
6171
+ `options.headers.${name} is a routing header but its value contains a credential this run holds; an isolated step cannot both route correctly and withhold the suite principal. Choose a routing value or suite credential that does not collide.`
6172
+ );
6173
+ }
6174
+ continue;
6175
+ }
6176
+ headers[name] = value;
6177
+ }
6109
6178
  let auth;
6110
6179
  for (const [name, value] of Object.entries(authHeaders)) {
6111
6180
  if (name.toLowerCase() === "authorization" && value.startsWith("Bearer ")) {
@@ -6114,9 +6183,9 @@ function createA2AAuthOverrideClient(agentUrl, options, authHeaders) {
6114
6183
  headers[name] = value;
6115
6184
  }
6116
6185
  }
6117
- return createTestClient(agentUrl, "a2a", {
6186
+ return createTestClient(agentUrl, protocol, {
6118
6187
  ...options,
6119
- protocol: "a2a",
6188
+ protocol,
6120
6189
  auth,
6121
6190
  test_kit: void 0,
6122
6191
  _client: void 0,
@@ -2395,6 +2395,12 @@ export interface StoryboardStepResult {
2395
2395
  title: string;
2396
2396
  task: string;
2397
2397
  passed: boolean;
2398
+ /**
2399
+ * False when a failed step is diagnostic only and does not count toward
2400
+ * the storyboard verdict (for example, an optional discovery phase).
2401
+ * Absent on ordinary failures, including mandatory OAuth and invariant checks.
2402
+ */
2403
+ counts_toward_failure?: boolean;
2398
2404
  /** True when the step was not executed */
2399
2405
  skipped?: boolean;
2400
2406
  /**
@@ -2395,6 +2395,12 @@ export interface StoryboardStepResult {
2395
2395
  title: string;
2396
2396
  task: string;
2397
2397
  passed: boolean;
2398
+ /**
2399
+ * False when a failed step is diagnostic only and does not count toward
2400
+ * the storyboard verdict (for example, an optional discovery phase).
2401
+ * Absent on ordinary failures, including mandatory OAuth and invariant checks.
2402
+ */
2403
+ counts_toward_failure?: boolean;
2398
2404
  /** True when the step was not executed */
2399
2405
  skipped?: boolean;
2400
2406
  /**
@@ -197,7 +197,7 @@ export interface UpstreamTrafficQueryResult {
197
197
  */
198
198
  export declare function runValidations(validations: StoryboardValidation[], context: ValidationContext): ValidationResult[];
199
199
  /** Capability semver emitted in run summaries and used for advisory expiry. */
200
- export declare const RUNNER_CAPABILITY_VERSION = "15.9.1";
200
+ export declare const RUNNER_CAPABILITY_VERSION = "15.9.3";
201
201
  /** True when a failed validation contributes to the owning step's grade. */
202
202
  export declare function validationFailsStep(result: ValidationResult): boolean;
203
203
  /**
@@ -197,7 +197,7 @@ export interface UpstreamTrafficQueryResult {
197
197
  */
198
198
  export declare function runValidations(validations: StoryboardValidation[], context: ValidationContext): ValidationResult[];
199
199
  /** Capability semver emitted in run summaries and used for advisory expiry. */
200
- export declare const RUNNER_CAPABILITY_VERSION = "15.9.1";
200
+ export declare const RUNNER_CAPABILITY_VERSION = "15.9.3";
201
201
  /** True when a failed validation contributes to the owning step's grade. */
202
202
  export declare function validationFailsStep(result: ValidationResult): boolean;
203
203
  /**
@@ -46,6 +46,7 @@ __export(schema_loader_exports, {
46
46
  listValidatorKeys: () => listValidatorKeys,
47
47
  registerExternalSchemaRoot: () => registerExternalSchemaRoot,
48
48
  resolveBundleKey: () => resolveBundleKey,
49
+ resolveServedValidationVersion: () => resolveServedValidationVersion,
49
50
  schemaAllowsTopLevelField: () => schemaAllowsTopLevelField,
50
51
  toReleasePrecisionWire: () => toReleasePrecisionWire,
51
52
  unregisterExternalSchemaRoot: () => unregisterExternalSchemaRoot,
@@ -718,6 +719,29 @@ function hasSchemaBundle(version) {
718
719
  return false;
719
720
  }
720
721
  }
722
+ const SERVED_RELEASE_MAJOR = /^(\d+)\.\d+(?:-[a-zA-Z0-9.-]+)?$/;
723
+ const NUMERIC_PIN_MAJOR = /^(\d+)\.\d+/;
724
+ function resolveServedValidationVersion(response, pinnedVersion) {
725
+ const pinMajor = NUMERIC_PIN_MAJOR.exec(pinnedVersion)?.[1];
726
+ if (pinMajor === void 0) return pinnedVersion;
727
+ try {
728
+ if (isExternalSchemaRootActive(pinnedVersion)) return pinnedVersion;
729
+ } catch {
730
+ return pinnedVersion;
731
+ }
732
+ const echoed = response !== null && typeof response === "object" ? response.adcp_version : void 0;
733
+ if (typeof echoed !== "string") return pinnedVersion;
734
+ const echoMajor = SERVED_RELEASE_MAJOR.exec(echoed)?.[1];
735
+ if (echoMajor === void 0 || echoMajor !== pinMajor) return pinnedVersion;
736
+ let pinnedRelease;
737
+ try {
738
+ pinnedRelease = toReleasePrecisionWire(pinnedVersion);
739
+ } catch {
740
+ return pinnedVersion;
741
+ }
742
+ if (echoed === pinnedRelease) return pinnedVersion;
743
+ return hasSchemaBundle(echoed) ? echoed : pinnedVersion;
744
+ }
721
745
  const mcpToolSchemaCache = /* @__PURE__ */ new Map();
722
746
  const mcpToolSummaryCache = /* @__PURE__ */ new Map();
723
747
  function boundedMcpToolSummary(value) {
@@ -981,6 +1005,7 @@ function cloneJsonValue(value) {
981
1005
  listValidatorKeys,
982
1006
  registerExternalSchemaRoot,
983
1007
  resolveBundleKey,
1008
+ resolveServedValidationVersion,
984
1009
  schemaAllowsTopLevelField,
985
1010
  toReleasePrecisionWire,
986
1011
  unregisterExternalSchemaRoot,
@@ -668,6 +668,29 @@ function hasSchemaBundle(version) {
668
668
  return false;
669
669
  }
670
670
  }
671
+ const SERVED_RELEASE_MAJOR = /^(\d+)\.\d+(?:-[a-zA-Z0-9.-]+)?$/;
672
+ const NUMERIC_PIN_MAJOR = /^(\d+)\.\d+/;
673
+ function resolveServedValidationVersion(response, pinnedVersion) {
674
+ const pinMajor = NUMERIC_PIN_MAJOR.exec(pinnedVersion)?.[1];
675
+ if (pinMajor === void 0) return pinnedVersion;
676
+ try {
677
+ if (isExternalSchemaRootActive(pinnedVersion)) return pinnedVersion;
678
+ } catch {
679
+ return pinnedVersion;
680
+ }
681
+ const echoed = response !== null && typeof response === "object" ? response.adcp_version : void 0;
682
+ if (typeof echoed !== "string") return pinnedVersion;
683
+ const echoMajor = SERVED_RELEASE_MAJOR.exec(echoed)?.[1];
684
+ if (echoMajor === void 0 || echoMajor !== pinMajor) return pinnedVersion;
685
+ let pinnedRelease;
686
+ try {
687
+ pinnedRelease = toReleasePrecisionWire(pinnedVersion);
688
+ } catch {
689
+ return pinnedVersion;
690
+ }
691
+ if (echoed === pinnedRelease) return pinnedVersion;
692
+ return hasSchemaBundle(echoed) ? echoed : pinnedVersion;
693
+ }
671
694
  const mcpToolSchemaCache = /* @__PURE__ */ new Map();
672
695
  const mcpToolSummaryCache = /* @__PURE__ */ new Map();
673
696
  function boundedMcpToolSummary(value) {
@@ -930,6 +953,7 @@ export {
930
953
  listValidatorKeys,
931
954
  registerExternalSchemaRoot,
932
955
  resolveBundleKey,
956
+ resolveServedValidationVersion,
933
957
  schemaAllowsTopLevelField,
934
958
  toReleasePrecisionWire,
935
959
  unregisterExternalSchemaRoot,
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * AdCP SDK library version
3
3
  */
4
- export declare const LIBRARY_VERSION = "15.9.1";
4
+ export declare const LIBRARY_VERSION = "15.9.3";
5
5
  /**
6
6
  * AdCP specification version this library is built for
7
7
  */
@@ -33,10 +33,10 @@ export type AdcpVersion = (typeof COMPATIBLE_ADCP_VERSIONS)[number];
33
33
  * Full version information
34
34
  */
35
35
  export declare const VERSION_INFO: {
36
- readonly library: "15.9.1";
36
+ readonly library: "15.9.3";
37
37
  readonly adcp: "3.2.4";
38
38
  readonly compatibleVersions: readonly ["v2.5", "v2.6", "v3", "3.0.0", "3.0", "3.0.1", "3.0.2", "3.0.3", "3.0.4", "3.0.5", "3.0.6", "3.0.7", "3.0.8", "3.0.9", "3.0.10", "3.0.11", "3.0.12", "3.0.13", "3.0.14", "3.0.15", "3.0.16", "3.0.17", "3.0.18", "3.0.19", "3.0.20", "3.0.21", "3.0.22", "3.0.23", "3.0.24", "3.0.25", "3.1.0", "3.1", "3.1.1", "3.1.2", "3.1.3", "3.1.4", "3.1.5", "3.1.6", "3.1.7", "3.1.8", "3.1.9", "3.1.10", "3.1.11", "3.1.12", "3.1.13", "3.1.14", "3.1.15", "3.1.16", "3.1.17", "3.1.18", "3.1.19", "3.1.20", "3.1.21", "3.1.22", "3.1.23", "3.1.24", "3.2.1", "3.2", "3.2.2", "3.2.3", "3.2.4"];
39
- readonly generatedAt: "2026-10-14T11:09:46.080Z";
39
+ readonly generatedAt: "2026-10-16T14:25:50.306Z";
40
40
  };
41
41
  /**
42
42
  * Get the AdCP specification version this library is built for
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * AdCP SDK library version
3
3
  */
4
- export declare const LIBRARY_VERSION = "15.9.1";
4
+ export declare const LIBRARY_VERSION = "15.9.3";
5
5
  /**
6
6
  * AdCP specification version this library is built for
7
7
  */
@@ -33,10 +33,10 @@ export type AdcpVersion = (typeof COMPATIBLE_ADCP_VERSIONS)[number];
33
33
  * Full version information
34
34
  */
35
35
  export declare const VERSION_INFO: {
36
- readonly library: "15.9.1";
36
+ readonly library: "15.9.3";
37
37
  readonly adcp: "3.2.4";
38
38
  readonly compatibleVersions: readonly ["v2.5", "v2.6", "v3", "3.0.0", "3.0", "3.0.1", "3.0.2", "3.0.3", "3.0.4", "3.0.5", "3.0.6", "3.0.7", "3.0.8", "3.0.9", "3.0.10", "3.0.11", "3.0.12", "3.0.13", "3.0.14", "3.0.15", "3.0.16", "3.0.17", "3.0.18", "3.0.19", "3.0.20", "3.0.21", "3.0.22", "3.0.23", "3.0.24", "3.0.25", "3.1.0", "3.1", "3.1.1", "3.1.2", "3.1.3", "3.1.4", "3.1.5", "3.1.6", "3.1.7", "3.1.8", "3.1.9", "3.1.10", "3.1.11", "3.1.12", "3.1.13", "3.1.14", "3.1.15", "3.1.16", "3.1.17", "3.1.18", "3.1.19", "3.1.20", "3.1.21", "3.1.22", "3.1.23", "3.1.24", "3.2.1", "3.2", "3.2.2", "3.2.3", "3.2.4"];
39
- readonly generatedAt: "2026-10-14T11:09:46.080Z";
39
+ readonly generatedAt: "2026-10-16T14:25:50.306Z";
40
40
  };
41
41
  /**
42
42
  * Get the AdCP specification version this library is built for
@@ -31,7 +31,7 @@ __export(version_exports, {
31
31
  toReleasePrecisionVersion: () => toReleasePrecisionVersion
32
32
  });
33
33
  module.exports = __toCommonJS(version_exports);
34
- const LIBRARY_VERSION = "15.9.1";
34
+ const LIBRARY_VERSION = "15.9.3";
35
35
  const ADCP_VERSION = "3.2.4";
36
36
  const ADCP_MAJOR_VERSION = 3;
37
37
  const COMPATIBLE_ADCP_VERSIONS = [
@@ -98,10 +98,10 @@ const COMPATIBLE_ADCP_VERSIONS = [
98
98
  "3.2.4"
99
99
  ];
100
100
  const VERSION_INFO = {
101
- library: "15.9.1",
101
+ library: "15.9.3",
102
102
  adcp: "3.2.4",
103
103
  compatibleVersions: COMPATIBLE_ADCP_VERSIONS,
104
- generatedAt: "2026-10-14T11:09:46.080Z"
104
+ generatedAt: "2026-10-16T14:25:50.306Z"
105
105
  };
106
106
  function getAdcpVersion() {
107
107
  return ADCP_VERSION;
@@ -1,4 +1,4 @@
1
- const LIBRARY_VERSION = "15.9.1";
1
+ const LIBRARY_VERSION = "15.9.3";
2
2
  const ADCP_VERSION = "3.2.4";
3
3
  const ADCP_MAJOR_VERSION = 3;
4
4
  const COMPATIBLE_ADCP_VERSIONS = [
@@ -65,10 +65,10 @@ const COMPATIBLE_ADCP_VERSIONS = [
65
65
  "3.2.4"
66
66
  ];
67
67
  const VERSION_INFO = {
68
- library: "15.9.1",
68
+ library: "15.9.3",
69
69
  adcp: "3.2.4",
70
70
  compatibleVersions: COMPATIBLE_ADCP_VERSIONS,
71
- generatedAt: "2026-10-14T11:09:46.080Z"
71
+ generatedAt: "2026-10-16T14:25:50.306Z"
72
72
  };
73
73
  function getAdcpVersion() {
74
74
  return ADCP_VERSION;
@@ -1,7 +1,7 @@
1
1
  # AdCP Type Summary
2
2
 
3
- > Generated at: 2026-10-14
4
- > @adcp/sdk v15.9.1
3
+ > Generated at: 2026-10-16
4
+ > @adcp/sdk v15.9.3
5
5
 
6
6
  Curated reference of the types that matter for using the AdCP client. For full generated types see `src/lib/types/tools.generated.ts` and `src/lib/types/core.generated.ts`.
7
7
 
package/docs/llms.txt CHANGED
@@ -1,7 +1,7 @@
1
1
  # Ad Context Protocol (AdCP)
2
2
 
3
- > Generated at: 2026-10-14
4
- > Library: @adcp/sdk v15.9.1
3
+ > Generated at: 2026-10-16
4
+ > Library: @adcp/sdk v15.9.3
5
5
  > AdCP major version: 3
6
6
  > Canonical URL: https://adcontextprotocol.github.io/adcp-client/llms.txt
7
7
  > Note: the `Library` stamp reflects the package.json version at doc-generation time. The narrative below describes the surface that lands on the next-published minor — including any 6.7 helpers documented here ahead of the release tag.
@@ -11,8 +11,8 @@ below as the publication/deployment gate.
11
11
 
12
12
  | Fact | Value |
13
13
  | --- | --- |
14
- | Exact npm package | `@adcp/sdk@15.9.1` |
15
- | npm integrity | registry-derived after publication; run `npm view @adcp/sdk@15.9.1 dist.integrity` |
14
+ | Exact npm package | `@adcp/sdk@15.9.3` |
15
+ | npm integrity | registry-derived after publication; run `npm view @adcp/sdk@15.9.3 dist.integrity` |
16
16
  | Node.js runtime | `^20.19.0 || >=22.12.0` |
17
17
  | Default AdCP wire release | `3.2.4` |
18
18
  | Maintained wire releases | `v2.5`, `v2.6`, `v3`, `3.0.0`, `3.0`, `3.0.1`, `3.0.2`, `3.0.3`, `3.0.4`, `3.0.5`, `3.0.6`, `3.0.7`, `3.0.8`, `3.0.9`, `3.0.10`, `3.0.11`, `3.0.12`, `3.0.13`, `3.0.14`, `3.0.15`, `3.0.16`, `3.0.17`, `3.0.18`, `3.0.19`, `3.0.20`, `3.0.21`, `3.0.22`, `3.0.23`, `3.0.24`, `3.0.25`, `3.1.0`, `3.1`, `3.1.1`, `3.1.2`, `3.1.3`, `3.1.4`, `3.1.5`, `3.1.6`, `3.1.7`, `3.1.8`, `3.1.9`, `3.1.10`, `3.1.11`, `3.1.12`, `3.1.13`, `3.1.14`, `3.1.15`, `3.1.16`, `3.1.17`, `3.1.18`, `3.1.19`, `3.1.20`, `3.1.21`, `3.1.22`, `3.1.23`, `3.1.24`, `3.2.1`, `3.2`, `3.2.2`, `3.2.3`, `3.2.4` |
@@ -21,8 +21,8 @@ below as the publication/deployment gate.
21
21
  Install exact production inputs rather than a moving range or dist-tag:
22
22
 
23
23
  ```bash
24
- npm install --save-exact '@adcp/sdk@15.9.1'
25
- npm view '@adcp/sdk@15.9.1' dist.integrity
24
+ npm install --save-exact '@adcp/sdk@15.9.3'
25
+ npm view '@adcp/sdk@15.9.3' dist.integrity
26
26
  ```
27
27
 
28
28
  ### Required and optional peers
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adcp/sdk",
3
- "version": "15.9.1",
3
+ "version": "15.9.3",
4
4
  "description": "AdCP SDK — client, server, and compliance harnesses for the AdContext Protocol (MCP + A2A)",
5
5
  "workspaces": [
6
6
  ".",