@adcp/sdk 15.9.4 → 15.9.5

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 (38) hide show
  1. package/dist/lib/core/TaskExecutor.d.mts +1 -0
  2. package/dist/lib/core/TaskExecutor.d.ts +1 -0
  3. package/dist/lib/core/TaskExecutor.js +48 -34
  4. package/dist/lib/core/TaskExecutor.mjs +49 -35
  5. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  6. package/dist/lib/testing/client.js +3 -5
  7. package/dist/lib/testing/client.mjs +4 -6
  8. package/dist/lib/testing/compliance/comply.js +7 -2
  9. package/dist/lib/testing/compliance/comply.mjs +8 -3
  10. package/dist/lib/testing/index.d.mts +1 -1
  11. package/dist/lib/testing/index.d.ts +1 -1
  12. package/dist/lib/testing/index.js +4 -2
  13. package/dist/lib/testing/index.mjs +8 -2
  14. package/dist/lib/testing/storyboard/runner.js +12 -12
  15. package/dist/lib/testing/storyboard/runner.mjs +14 -13
  16. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  17. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  18. package/dist/lib/testing/storyboard/validations.js +23 -1
  19. package/dist/lib/testing/storyboard/validations.mjs +28 -2
  20. package/dist/lib/testing/types.d.mts +39 -0
  21. package/dist/lib/testing/types.d.ts +39 -0
  22. package/dist/lib/validation/index.d.mts +1 -1
  23. package/dist/lib/validation/index.d.ts +1 -1
  24. package/dist/lib/validation/index.js +4 -2
  25. package/dist/lib/validation/index.mjs +4 -2
  26. package/dist/lib/validation/schema-loader.d.mts +26 -0
  27. package/dist/lib/validation/schema-loader.d.ts +26 -0
  28. package/dist/lib/validation/schema-loader.js +98 -14
  29. package/dist/lib/validation/schema-loader.mjs +95 -13
  30. package/dist/lib/version.d.mts +3 -3
  31. package/dist/lib/version.d.ts +3 -3
  32. package/dist/lib/version.js +3 -3
  33. package/dist/lib/version.mjs +3 -3
  34. package/docs/TYPE-SUMMARY.md +2 -2
  35. package/docs/guides/VALIDATE-YOUR-AGENT.md +34 -0
  36. package/docs/llms.txt +2 -2
  37. package/docs/migration-14.x-rc-worksheet.md +4 -4
  38. package/package.json +1 -1
@@ -58,6 +58,45 @@ export interface TestOptions {
58
58
  * authoritative over the SDK's generated Zod snapshot for the run.
59
59
  */
60
60
  schemaRoot?: string;
61
+ /**
62
+ * Schema roots for releases the agent may echo in its
63
+ * `get_adcp_capabilities` response, keyed by AdCP version (`'3.2'`,
64
+ * `'3.2.5'`, `'3.3.0-beta.1'`). Values are schema-data roots in the same
65
+ * shape as `schemaRoot`.
66
+ *
67
+ * A 3.0 pin sends only `adcp_major_version`, so an agent may serve and echo
68
+ * a newer release in the same major (versioning spec, "Server echoes").
69
+ * The SDK validates that capabilities response against the echoed release
70
+ * when the echo is the pin's major, is listed in the response's
71
+ * `adcp.supported_versions`, and has a schema root: an entry here, an
72
+ * external root registered for it, or the SDK's bundled schemas. That
73
+ * applies to discovery's capability notices, `getAdcpCapabilities()` and
74
+ * storyboard `response_schema` checks on `get_adcp_capabilities`, including
75
+ * when `schemaRoot` is set for the pin. Otherwise validation stays on the
76
+ * pin.
77
+ *
78
+ * Entries are used for that capabilities validation only. Every other
79
+ * task's response, storyboard selection and grading stay on the pin. Each
80
+ * root is checked against its key once per process; a malformed map throws
81
+ * before the run starts. Honored by `comply()`, `runStoryboard()` and
82
+ * `runStoryboardStep()`; other callers can use `withServedReleaseSchemaRoots`.
83
+ * The echo is compared with the run's pin, so storyboard checks apply it
84
+ * only when the run has one (`adcpVersion`, or the storyboard or compliance
85
+ * bundle's `adcp_version`); `comply()` always does.
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * await comply(agentUrl, {
90
+ * complianceDir: hosted30.complianceDir,
91
+ * schemaRoot: hosted30.schemaRoot,
92
+ * servedReleaseSchemaRoots: {
93
+ * '3.1.29': hosted31.schemaRoot,
94
+ * '3.2.5': hosted32.schemaRoot,
95
+ * },
96
+ * });
97
+ * ```
98
+ */
99
+ servedReleaseSchemaRoots?: Readonly<Record<string, string>>;
61
100
  /**
62
101
  * Whether strict AJV response-schema failures contribute to storyboard
63
102
  * grading. Defaults to `true`, matching the hosted compliance grader.
@@ -58,6 +58,45 @@ export interface TestOptions {
58
58
  * authoritative over the SDK's generated Zod snapshot for the run.
59
59
  */
60
60
  schemaRoot?: string;
61
+ /**
62
+ * Schema roots for releases the agent may echo in its
63
+ * `get_adcp_capabilities` response, keyed by AdCP version (`'3.2'`,
64
+ * `'3.2.5'`, `'3.3.0-beta.1'`). Values are schema-data roots in the same
65
+ * shape as `schemaRoot`.
66
+ *
67
+ * A 3.0 pin sends only `adcp_major_version`, so an agent may serve and echo
68
+ * a newer release in the same major (versioning spec, "Server echoes").
69
+ * The SDK validates that capabilities response against the echoed release
70
+ * when the echo is the pin's major, is listed in the response's
71
+ * `adcp.supported_versions`, and has a schema root: an entry here, an
72
+ * external root registered for it, or the SDK's bundled schemas. That
73
+ * applies to discovery's capability notices, `getAdcpCapabilities()` and
74
+ * storyboard `response_schema` checks on `get_adcp_capabilities`, including
75
+ * when `schemaRoot` is set for the pin. Otherwise validation stays on the
76
+ * pin.
77
+ *
78
+ * Entries are used for that capabilities validation only. Every other
79
+ * task's response, storyboard selection and grading stay on the pin. Each
80
+ * root is checked against its key once per process; a malformed map throws
81
+ * before the run starts. Honored by `comply()`, `runStoryboard()` and
82
+ * `runStoryboardStep()`; other callers can use `withServedReleaseSchemaRoots`.
83
+ * The echo is compared with the run's pin, so storyboard checks apply it
84
+ * only when the run has one (`adcpVersion`, or the storyboard or compliance
85
+ * bundle's `adcp_version`); `comply()` always does.
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * await comply(agentUrl, {
90
+ * complianceDir: hosted30.complianceDir,
91
+ * schemaRoot: hosted30.schemaRoot,
92
+ * servedReleaseSchemaRoots: {
93
+ * '3.1.29': hosted31.schemaRoot,
94
+ * '3.2.5': hosted32.schemaRoot,
95
+ * },
96
+ * });
97
+ * ```
98
+ */
99
+ servedReleaseSchemaRoots?: Readonly<Record<string, string>>;
61
100
  /**
62
101
  * Whether strict AJV response-schema failures contribute to storyboard
63
102
  * grading. Defaults to `true`, matching the hosted compliance grader.
@@ -6,7 +6,7 @@ export { validateRequest, validateResponse, formatIssues } from './schema-valida
6
6
  export type { ValidationIssue, ValidationOutcome } from './schema-validator.mjs';
7
7
  export { buildValidationError, buildAdcpValidationErrorPayload } from './schema-errors.mjs';
8
8
  export type { ValidationErrorDetails, AdcpValidationErrorDetails } from './schema-errors.mjs';
9
- export { getValidator, listValidatorKeys, resolveBundleKey, hasSchemaBundle, registerExternalSchemaRoot, unregisterExternalSchemaRoot, withExternalSchemaRoot, } from './schema-loader.mjs';
9
+ export { getValidator, listValidatorKeys, resolveBundleKey, hasSchemaBundle, registerExternalSchemaRoot, unregisterExternalSchemaRoot, withExternalSchemaRoot, withServedReleaseSchemaRoots, } from './schema-loader.mjs';
10
10
  export type { Direction, ResponseVariant } from './schema-loader.mjs';
11
11
  export { validateOutgoingRequest, validateIncomingResponse, resolveValidationModes } from './client-hooks.mjs';
12
12
  export type { ValidationMode, ValidationHookConfig } from './client-hooks.mjs';
@@ -6,7 +6,7 @@ export { validateRequest, validateResponse, formatIssues } from './schema-valida
6
6
  export type { ValidationIssue, ValidationOutcome } from './schema-validator';
7
7
  export { buildValidationError, buildAdcpValidationErrorPayload } from './schema-errors';
8
8
  export type { ValidationErrorDetails, AdcpValidationErrorDetails } from './schema-errors';
9
- export { getValidator, listValidatorKeys, resolveBundleKey, hasSchemaBundle, registerExternalSchemaRoot, unregisterExternalSchemaRoot, withExternalSchemaRoot, } from './schema-loader';
9
+ export { getValidator, listValidatorKeys, resolveBundleKey, hasSchemaBundle, registerExternalSchemaRoot, unregisterExternalSchemaRoot, withExternalSchemaRoot, withServedReleaseSchemaRoots, } from './schema-loader';
10
10
  export type { Direction, ResponseVariant } from './schema-loader';
11
11
  export { validateOutgoingRequest, validateIncomingResponse, resolveValidationModes } from './client-hooks';
12
12
  export type { ValidationMode, ValidationHookConfig } from './client-hooks';
@@ -36,7 +36,8 @@ __export(validation_exports, {
36
36
  validateOutgoingRequest: () => import_client_hooks.validateOutgoingRequest,
37
37
  validateRequest: () => import_schema_validator.validateRequest,
38
38
  validateResponse: () => import_schema_validator.validateResponse,
39
- withExternalSchemaRoot: () => import_schema_loader.withExternalSchemaRoot
39
+ withExternalSchemaRoot: () => import_schema_loader.withExternalSchemaRoot,
40
+ withServedReleaseSchemaRoots: () => import_schema_loader.withServedReleaseSchemaRoots
40
41
  });
41
42
  module.exports = __toCommonJS(validation_exports);
42
43
  var import_probe_policy = require('../utils/probe-policy.js');
@@ -201,5 +202,6 @@ async function handleAdCPResponse(response, expectedSchema, agentName) {
201
202
  validateOutgoingRequest,
202
203
  validateRequest,
203
204
  validateResponse,
204
- withExternalSchemaRoot
205
+ withExternalSchemaRoot,
206
+ withServedReleaseSchemaRoots
205
207
  });
@@ -8,7 +8,8 @@ import {
8
8
  hasSchemaBundle,
9
9
  registerExternalSchemaRoot,
10
10
  unregisterExternalSchemaRoot,
11
- withExternalSchemaRoot
11
+ withExternalSchemaRoot,
12
+ withServedReleaseSchemaRoots
12
13
  } from "./schema-loader.mjs";
13
14
  import { validateOutgoingRequest, validateIncomingResponse, resolveValidationModes } from "./client-hooks.mjs";
14
15
  function getExpectedSchema(toolName) {
@@ -167,5 +168,6 @@ export {
167
168
  validateOutgoingRequest,
168
169
  validateRequest,
169
170
  validateResponse,
170
- withExternalSchemaRoot
171
+ withExternalSchemaRoot,
172
+ withServedReleaseSchemaRoots
171
173
  };
@@ -168,6 +168,32 @@ export { SCHEMA_FILENAME_SUFFIX };
168
168
  * we don't have schemas for.
169
169
  */
170
170
  export declare function hasSchemaBundle(version: string): boolean;
171
+ /**
172
+ * Run `fn` with schema roots for the releases an agent may echo in its
173
+ * `get_adcp_capabilities` response.
174
+ *
175
+ * Keys are AdCP versions in any form the SDK accepts for a pin (`'3.2'`,
176
+ * `'3.2.5'`, `'3.3.0-beta.1'`); values are schema-data roots in the same
177
+ * shape as `schemaRoot` (a directory holding `bundled/`, e.g. a hosted
178
+ * `dist/schemas/3.2.5`). Each root is checked against its key the same way
179
+ * `withExternalSchemaRoot` checks one, and a malformed map throws before `fn`
180
+ * runs.
181
+ *
182
+ * The roots are consulted only when validating a `get_adcp_capabilities`
183
+ * response against the release the agent echoed (see
184
+ * {@link resolveServedValidationVersion}): discovery's capability notices,
185
+ * `getAdcpCapabilities()` response validation and storyboard
186
+ * `response_schema` checks on that task. They are not activated for any other
187
+ * task, so every other response, storyboard selection and grading stay on the
188
+ * pin. A supplied root takes precedence over an external root registered for
189
+ * the same release and over the SDK's bundled schemas for it.
190
+ *
191
+ * Scoped to the current async execution chain; nested calls add to the
192
+ * enclosing scope, with the innermost entry for a release winning.
193
+ * `comply()` and `runStoryboard()` accept the same map as
194
+ * `servedReleaseSchemaRoots` and call this for you.
195
+ */
196
+ export declare function withServedReleaseSchemaRoots<T>(roots: Readonly<Record<string, string>> | undefined, fn: () => T): T;
171
197
  /** Load the exact self-contained request projection from an MCP role profile. */
172
198
  export declare function getMcpProfileInputSchema(toolName: string, profile: string, version?: string): Readonly<Record<string, unknown>> | undefined;
173
199
  /**
@@ -168,6 +168,32 @@ export { SCHEMA_FILENAME_SUFFIX };
168
168
  * we don't have schemas for.
169
169
  */
170
170
  export declare function hasSchemaBundle(version: string): boolean;
171
+ /**
172
+ * Run `fn` with schema roots for the releases an agent may echo in its
173
+ * `get_adcp_capabilities` response.
174
+ *
175
+ * Keys are AdCP versions in any form the SDK accepts for a pin (`'3.2'`,
176
+ * `'3.2.5'`, `'3.3.0-beta.1'`); values are schema-data roots in the same
177
+ * shape as `schemaRoot` (a directory holding `bundled/`, e.g. a hosted
178
+ * `dist/schemas/3.2.5`). Each root is checked against its key the same way
179
+ * `withExternalSchemaRoot` checks one, and a malformed map throws before `fn`
180
+ * runs.
181
+ *
182
+ * The roots are consulted only when validating a `get_adcp_capabilities`
183
+ * response against the release the agent echoed (see
184
+ * {@link resolveServedValidationVersion}): discovery's capability notices,
185
+ * `getAdcpCapabilities()` response validation and storyboard
186
+ * `response_schema` checks on that task. They are not activated for any other
187
+ * task, so every other response, storyboard selection and grading stay on the
188
+ * pin. A supplied root takes precedence over an external root registered for
189
+ * the same release and over the SDK's bundled schemas for it.
190
+ *
191
+ * Scoped to the current async execution chain; nested calls add to the
192
+ * enclosing scope, with the innermost entry for a release winning.
193
+ * `comply()` and `runStoryboard()` accept the same map as
194
+ * `servedReleaseSchemaRoots` and call this for you.
195
+ */
196
+ export declare function withServedReleaseSchemaRoots<T>(roots: Readonly<Record<string, string>> | undefined, fn: () => T): T;
171
197
  /** Load the exact self-contained request projection from an MCP role profile. */
172
198
  export declare function getMcpProfileInputSchema(toolName: string, profile: string, version?: string): Readonly<Record<string, unknown>> | undefined;
173
199
  /**
@@ -51,7 +51,9 @@ __export(schema_loader_exports, {
51
51
  toReleasePrecisionWire: () => toReleasePrecisionWire,
52
52
  unregisterExternalSchemaRoot: () => unregisterExternalSchemaRoot,
53
53
  validateAdcpVersionWire: () => validateAdcpVersionWire,
54
- withExternalSchemaRoot: () => withExternalSchemaRoot
54
+ withExternalSchemaRoot: () => withExternalSchemaRoot,
55
+ withServedReleaseSchemaRoots: () => withServedReleaseSchemaRoots,
56
+ withServedValidationVersion: () => withServedValidationVersion
55
57
  });
56
58
  module.exports = __toCommonJS(schema_loader_exports);
57
59
  var import_ajv = __toESM(require("ajv"));
@@ -719,28 +721,108 @@ function hasSchemaBundle(version) {
719
721
  return false;
720
722
  }
721
723
  }
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;
724
+ const servedReleaseSchemaRootScope = new import_async_hooks.AsyncLocalStorage();
725
+ const servedReleaseRootAliases = /* @__PURE__ */ new Map();
726
+ function withServedReleaseSchemaRoots(roots, fn) {
727
+ if (roots === void 0) return fn();
728
+ if (roots === null || typeof roots !== "object" || Array.isArray(roots)) {
729
+ throw new TypeError("servedReleaseSchemaRoots must be an object mapping AdCP versions to schema roots");
730
+ }
731
+ const entries = [];
732
+ for (const [version, root] of Object.entries(roots)) {
733
+ resolveBundleKey(version);
734
+ if (typeof root !== "string" || root.length === 0) {
735
+ throw new TypeError(`servedReleaseSchemaRoots["${version}"] must be a non-empty schema root path`);
736
+ }
737
+ const memoKey = `${version}\0${root}`;
738
+ let aliases = servedReleaseRootAliases.get(memoKey);
739
+ if (!aliases) {
740
+ if (!hasSchemaRootShape(root)) {
741
+ throw new Error(`Served-release AdCP schema root for version "${version}" not found or empty at ${root}`);
742
+ }
743
+ aliases = assertSchemaRootMatchesVersion(version, root);
744
+ servedReleaseRootAliases.set(memoKey, aliases);
745
+ }
746
+ const conflict = entries.find((entry) => entry.root !== root && [...aliases].some((alias) => entry.aliases.has(alias)));
747
+ if (conflict) {
748
+ throw new Error(
749
+ `servedReleaseSchemaRoots["${version}"] and servedReleaseSchemaRoots["${conflict.version}"] name the same release with different schema roots (${root}, ${conflict.root})`
750
+ );
751
+ }
752
+ entries.push({ version, root, aliases });
753
+ }
754
+ if (entries.length === 0) return fn();
755
+ const parent = servedReleaseSchemaRootScope.getStore() ?? [];
756
+ return servedReleaseSchemaRootScope.run([...entries, ...parent], fn);
757
+ }
758
+ function findServedReleaseSchemaRoot(release) {
759
+ const entries = servedReleaseSchemaRootScope.getStore();
760
+ if (!entries?.length) return void 0;
761
+ let key;
727
762
  try {
728
- if (isExternalSchemaRootActive(pinnedVersion)) return pinnedVersion;
763
+ key = resolveBundleKey(release);
729
764
  } catch {
730
- return pinnedVersion;
765
+ return void 0;
731
766
  }
732
- const echoed = response !== null && typeof response === "object" ? response.adcp_version : void 0;
733
- if (typeof echoed !== "string") return pinnedVersion;
767
+ return entries.find((entry) => entry.aliases.has(key) || entry.aliases.has(release));
768
+ }
769
+ const SERVED_RELEASE_MAJOR = /^(\d+)\.\d+(?:-[a-zA-Z0-9.-]+)?$/;
770
+ const NUMERIC_PIN_MAJOR = /^(\d+)\.\d+/;
771
+ function listsSupportedRelease(response, release) {
772
+ const adcp = response.adcp;
773
+ if (adcp === null || typeof adcp !== "object") return false;
774
+ const supported = adcp.supported_versions;
775
+ if (!Array.isArray(supported)) return false;
776
+ return supported.some((entry) => {
777
+ if (typeof entry !== "string") return false;
778
+ if (entry === release) return true;
779
+ try {
780
+ return toReleasePrecisionWire(entry) === release;
781
+ } catch {
782
+ return false;
783
+ }
784
+ });
785
+ }
786
+ function selectServedValidationVersion(response, pinnedVersion) {
787
+ const pin = { version: pinnedVersion };
788
+ const pinMajor = NUMERIC_PIN_MAJOR.exec(pinnedVersion)?.[1];
789
+ if (pinMajor === void 0) return pin;
790
+ if (response === null || typeof response !== "object") return pin;
791
+ const echoed = response.adcp_version;
792
+ if (typeof echoed !== "string") return pin;
734
793
  const echoMajor = SERVED_RELEASE_MAJOR.exec(echoed)?.[1];
735
- if (echoMajor === void 0 || echoMajor !== pinMajor) return pinnedVersion;
794
+ if (echoMajor === void 0 || echoMajor !== pinMajor) return pin;
736
795
  let pinnedRelease;
737
796
  try {
738
797
  pinnedRelease = toReleasePrecisionWire(pinnedVersion);
798
+ } catch {
799
+ return pin;
800
+ }
801
+ if (echoed === pinnedRelease) return pin;
802
+ if (!listsSupportedRelease(response, echoed)) return pin;
803
+ const served = findServedReleaseSchemaRoot(echoed);
804
+ if (served) return { version: echoed, served };
805
+ return hasSchemaBundle(echoed) ? { version: echoed } : pin;
806
+ }
807
+ function resolveServedValidationVersion(response, pinnedVersion) {
808
+ try {
809
+ return selectServedValidationVersion(response, pinnedVersion).version;
739
810
  } catch {
740
811
  return pinnedVersion;
741
812
  }
742
- if (echoed === pinnedRelease) return pinnedVersion;
743
- return hasSchemaBundle(echoed) ? echoed : pinnedVersion;
813
+ }
814
+ function withServedValidationVersion(response, pinnedVersion, fn) {
815
+ let selection;
816
+ try {
817
+ selection = selectServedValidationVersion(response, pinnedVersion);
818
+ } catch {
819
+ selection = { version: pinnedVersion };
820
+ }
821
+ const { version, served } = selection;
822
+ if (!served) return fn(version);
823
+ const next = new Map(scopedExternalSchemaRoots.getStore());
824
+ for (const alias of served.aliases) next.set(alias, served.root);
825
+ return scopedExternalSchemaRoots.run(next, () => fn(version));
744
826
  }
745
827
  const mcpToolSchemaCache = /* @__PURE__ */ new Map();
746
828
  const mcpToolSummaryCache = /* @__PURE__ */ new Map();
@@ -1010,5 +1092,7 @@ function cloneJsonValue(value) {
1010
1092
  toReleasePrecisionWire,
1011
1093
  unregisterExternalSchemaRoot,
1012
1094
  validateAdcpVersionWire,
1013
- withExternalSchemaRoot
1095
+ withExternalSchemaRoot,
1096
+ withServedReleaseSchemaRoots,
1097
+ withServedValidationVersion
1014
1098
  });
@@ -668,28 +668,108 @@ 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;
671
+ const servedReleaseSchemaRootScope = new AsyncLocalStorage();
672
+ const servedReleaseRootAliases = /* @__PURE__ */ new Map();
673
+ function withServedReleaseSchemaRoots(roots, fn) {
674
+ if (roots === void 0) return fn();
675
+ if (roots === null || typeof roots !== "object" || Array.isArray(roots)) {
676
+ throw new TypeError("servedReleaseSchemaRoots must be an object mapping AdCP versions to schema roots");
677
+ }
678
+ const entries = [];
679
+ for (const [version, root] of Object.entries(roots)) {
680
+ resolveBundleKey(version);
681
+ if (typeof root !== "string" || root.length === 0) {
682
+ throw new TypeError(`servedReleaseSchemaRoots["${version}"] must be a non-empty schema root path`);
683
+ }
684
+ const memoKey = `${version}\0${root}`;
685
+ let aliases = servedReleaseRootAliases.get(memoKey);
686
+ if (!aliases) {
687
+ if (!hasSchemaRootShape(root)) {
688
+ throw new Error(`Served-release AdCP schema root for version "${version}" not found or empty at ${root}`);
689
+ }
690
+ aliases = assertSchemaRootMatchesVersion(version, root);
691
+ servedReleaseRootAliases.set(memoKey, aliases);
692
+ }
693
+ const conflict = entries.find((entry) => entry.root !== root && [...aliases].some((alias) => entry.aliases.has(alias)));
694
+ if (conflict) {
695
+ throw new Error(
696
+ `servedReleaseSchemaRoots["${version}"] and servedReleaseSchemaRoots["${conflict.version}"] name the same release with different schema roots (${root}, ${conflict.root})`
697
+ );
698
+ }
699
+ entries.push({ version, root, aliases });
700
+ }
701
+ if (entries.length === 0) return fn();
702
+ const parent = servedReleaseSchemaRootScope.getStore() ?? [];
703
+ return servedReleaseSchemaRootScope.run([...entries, ...parent], fn);
704
+ }
705
+ function findServedReleaseSchemaRoot(release) {
706
+ const entries = servedReleaseSchemaRootScope.getStore();
707
+ if (!entries?.length) return void 0;
708
+ let key;
676
709
  try {
677
- if (isExternalSchemaRootActive(pinnedVersion)) return pinnedVersion;
710
+ key = resolveBundleKey(release);
678
711
  } catch {
679
- return pinnedVersion;
712
+ return void 0;
680
713
  }
681
- const echoed = response !== null && typeof response === "object" ? response.adcp_version : void 0;
682
- if (typeof echoed !== "string") return pinnedVersion;
714
+ return entries.find((entry) => entry.aliases.has(key) || entry.aliases.has(release));
715
+ }
716
+ const SERVED_RELEASE_MAJOR = /^(\d+)\.\d+(?:-[a-zA-Z0-9.-]+)?$/;
717
+ const NUMERIC_PIN_MAJOR = /^(\d+)\.\d+/;
718
+ function listsSupportedRelease(response, release) {
719
+ const adcp = response.adcp;
720
+ if (adcp === null || typeof adcp !== "object") return false;
721
+ const supported = adcp.supported_versions;
722
+ if (!Array.isArray(supported)) return false;
723
+ return supported.some((entry) => {
724
+ if (typeof entry !== "string") return false;
725
+ if (entry === release) return true;
726
+ try {
727
+ return toReleasePrecisionWire(entry) === release;
728
+ } catch {
729
+ return false;
730
+ }
731
+ });
732
+ }
733
+ function selectServedValidationVersion(response, pinnedVersion) {
734
+ const pin = { version: pinnedVersion };
735
+ const pinMajor = NUMERIC_PIN_MAJOR.exec(pinnedVersion)?.[1];
736
+ if (pinMajor === void 0) return pin;
737
+ if (response === null || typeof response !== "object") return pin;
738
+ const echoed = response.adcp_version;
739
+ if (typeof echoed !== "string") return pin;
683
740
  const echoMajor = SERVED_RELEASE_MAJOR.exec(echoed)?.[1];
684
- if (echoMajor === void 0 || echoMajor !== pinMajor) return pinnedVersion;
741
+ if (echoMajor === void 0 || echoMajor !== pinMajor) return pin;
685
742
  let pinnedRelease;
686
743
  try {
687
744
  pinnedRelease = toReleasePrecisionWire(pinnedVersion);
745
+ } catch {
746
+ return pin;
747
+ }
748
+ if (echoed === pinnedRelease) return pin;
749
+ if (!listsSupportedRelease(response, echoed)) return pin;
750
+ const served = findServedReleaseSchemaRoot(echoed);
751
+ if (served) return { version: echoed, served };
752
+ return hasSchemaBundle(echoed) ? { version: echoed } : pin;
753
+ }
754
+ function resolveServedValidationVersion(response, pinnedVersion) {
755
+ try {
756
+ return selectServedValidationVersion(response, pinnedVersion).version;
688
757
  } catch {
689
758
  return pinnedVersion;
690
759
  }
691
- if (echoed === pinnedRelease) return pinnedVersion;
692
- return hasSchemaBundle(echoed) ? echoed : pinnedVersion;
760
+ }
761
+ function withServedValidationVersion(response, pinnedVersion, fn) {
762
+ let selection;
763
+ try {
764
+ selection = selectServedValidationVersion(response, pinnedVersion);
765
+ } catch {
766
+ selection = { version: pinnedVersion };
767
+ }
768
+ const { version, served } = selection;
769
+ if (!served) return fn(version);
770
+ const next = new Map(scopedExternalSchemaRoots.getStore());
771
+ for (const alias of served.aliases) next.set(alias, served.root);
772
+ return scopedExternalSchemaRoots.run(next, () => fn(version));
693
773
  }
694
774
  const mcpToolSchemaCache = /* @__PURE__ */ new Map();
695
775
  const mcpToolSummaryCache = /* @__PURE__ */ new Map();
@@ -958,5 +1038,7 @@ export {
958
1038
  toReleasePrecisionWire,
959
1039
  unregisterExternalSchemaRoot,
960
1040
  validateAdcpVersionWire,
961
- withExternalSchemaRoot
1041
+ withExternalSchemaRoot,
1042
+ withServedReleaseSchemaRoots,
1043
+ withServedValidationVersion
962
1044
  };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * AdCP SDK library version
3
3
  */
4
- export declare const LIBRARY_VERSION = "15.9.4";
4
+ export declare const LIBRARY_VERSION = "15.9.5";
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.4";
36
+ readonly library: "15.9.5";
37
37
  readonly adcp: "3.2.5";
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.1.25", "3.1.26", "3.1.27", "3.1.28", "3.1.29", "3.2.1", "3.2", "3.2.2", "3.2.3", "3.2.4", "3.2.5"];
39
- readonly generatedAt: "2026-10-16T23:12:08.652Z";
39
+ readonly generatedAt: "2026-10-17T14:24:48.482Z";
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.4";
4
+ export declare const LIBRARY_VERSION = "15.9.5";
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.4";
36
+ readonly library: "15.9.5";
37
37
  readonly adcp: "3.2.5";
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.1.25", "3.1.26", "3.1.27", "3.1.28", "3.1.29", "3.2.1", "3.2", "3.2.2", "3.2.3", "3.2.4", "3.2.5"];
39
- readonly generatedAt: "2026-10-16T23:12:08.652Z";
39
+ readonly generatedAt: "2026-10-17T14:24:48.482Z";
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.4";
34
+ const LIBRARY_VERSION = "15.9.5";
35
35
  const ADCP_VERSION = "3.2.5";
36
36
  const ADCP_MAJOR_VERSION = 3;
37
37
  const COMPATIBLE_ADCP_VERSIONS = [
@@ -104,10 +104,10 @@ const COMPATIBLE_ADCP_VERSIONS = [
104
104
  "3.2.5"
105
105
  ];
106
106
  const VERSION_INFO = {
107
- library: "15.9.4",
107
+ library: "15.9.5",
108
108
  adcp: "3.2.5",
109
109
  compatibleVersions: COMPATIBLE_ADCP_VERSIONS,
110
- generatedAt: "2026-10-16T23:12:08.652Z"
110
+ generatedAt: "2026-10-17T14:24:48.482Z"
111
111
  };
112
112
  function getAdcpVersion() {
113
113
  return ADCP_VERSION;
@@ -1,4 +1,4 @@
1
- const LIBRARY_VERSION = "15.9.4";
1
+ const LIBRARY_VERSION = "15.9.5";
2
2
  const ADCP_VERSION = "3.2.5";
3
3
  const ADCP_MAJOR_VERSION = 3;
4
4
  const COMPATIBLE_ADCP_VERSIONS = [
@@ -71,10 +71,10 @@ const COMPATIBLE_ADCP_VERSIONS = [
71
71
  "3.2.5"
72
72
  ];
73
73
  const VERSION_INFO = {
74
- library: "15.9.4",
74
+ library: "15.9.5",
75
75
  adcp: "3.2.5",
76
76
  compatibleVersions: COMPATIBLE_ADCP_VERSIONS,
77
- generatedAt: "2026-10-16T23:12:08.652Z"
77
+ generatedAt: "2026-10-17T14:24:48.482Z"
78
78
  };
79
79
  function getAdcpVersion() {
80
80
  return ADCP_VERSION;
@@ -1,7 +1,7 @@
1
1
  # AdCP Type Summary
2
2
 
3
- > Generated at: 2026-10-16
4
- > @adcp/sdk v15.9.4
3
+ > Generated at: 2026-10-17
4
+ > @adcp/sdk v15.9.5
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
 
@@ -187,6 +187,40 @@ await comply(agentUrl, {
187
187
  });
188
188
  ```
189
189
 
190
+ **Capabilities served at a newer release.** A 3.0 pin sends only
191
+ `adcp_major_version: 3`, so a conformant agent may serve and echo its highest
192
+ 3.x release in `get_adcp_capabilities` (versioning spec, "Server echoes"). The
193
+ runner validates that one response against the echoed release when the echo is
194
+ the pin's major, is listed in the response's own `adcp.supported_versions`, and
195
+ has a schema root. That covers discovery's `capabilities_response_schema_invalid`
196
+ notices, `getAdcpCapabilities()` and every storyboard `response_schema` check on
197
+ `get_adcp_capabilities`, even with `schemaRoot` set for the pin. Otherwise it
198
+ validates strictly against the pin. Every other task's response, storyboard
199
+ selection, the run's `adcp_version` and grading stay on the pin.
200
+
201
+ The echoed release's schemas come from the SDK's bundle by default. A grader
202
+ that ships its own roots per release can supply them with
203
+ `servedReleaseSchemaRoots` (on `comply()`, `runStoryboard()` and
204
+ `runStoryboardStep()`), keyed by AdCP version:
205
+
206
+ ```ts
207
+ await comply(agentUrl, {
208
+ version: '3.0.27',
209
+ complianceDir: '/app/dist/compliance/3.0.27',
210
+ schemaRoot: '/app/dist/schemas/3.0.27',
211
+ servedReleaseSchemaRoots: {
212
+ '3.1.29': '/app/dist/schemas/3.1.29',
213
+ '3.2.5': '/app/dist/schemas/3.2.5',
214
+ },
215
+ });
216
+ ```
217
+
218
+ Those roots are used only for that capabilities validation. Each one is checked
219
+ against its key once per process, and a mismatched or missing root throws
220
+ before the run starts. Callers that validate outside `comply()` and the runner
221
+ can scope the same map with `withServedReleaseSchemaRoots(roots, fn)` from
222
+ `@adcp/sdk/testing`.
223
+
190
224
  **Multi-agent storyboards under `comply()`.** `comply()` grades one agent, so
191
225
  storyboards that declare `requires: [multi_agent]` (for example the
192
226
  governance-aware seller scenarios) skip with `requirement_unmet` and cap their
package/docs/llms.txt CHANGED
@@ -1,7 +1,7 @@
1
1
  # Ad Context Protocol (AdCP)
2
2
 
3
- > Generated at: 2026-10-16
4
- > Library: @adcp/sdk v15.9.4
3
+ > Generated at: 2026-10-17
4
+ > Library: @adcp/sdk v15.9.5
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.