@adcp/sdk 13.0.0-rc.8 → 13.0.0-rc.9

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 (200) hide show
  1. package/dist/lib/adapters/roster-account-store.d.mts +9 -8
  2. package/dist/lib/adapters/roster-account-store.d.ts +9 -8
  3. package/dist/lib/adapters/roster-account-store.d.ts.map +1 -1
  4. package/dist/lib/adapters/roster-account-store.js +2 -1
  5. package/dist/lib/adapters/roster-account-store.js.map +1 -1
  6. package/dist/lib/adapters/roster-account-store.mjs +2 -1
  7. package/dist/lib/adapters/roster-account-store.mjs.map +1 -1
  8. package/dist/lib/net/address-guards.d.ts.map +1 -1
  9. package/dist/lib/net/address-guards.js +6 -0
  10. package/dist/lib/net/address-guards.js.map +1 -1
  11. package/dist/lib/net/address-guards.mjs +6 -0
  12. package/dist/lib/net/address-guards.mjs.map +1 -1
  13. package/dist/lib/net/ssrf-fetch.d.ts.map +1 -1
  14. package/dist/lib/net/ssrf-fetch.js +66 -53
  15. package/dist/lib/net/ssrf-fetch.js.map +1 -1
  16. package/dist/lib/net/ssrf-fetch.mjs +66 -53
  17. package/dist/lib/net/ssrf-fetch.mjs.map +1 -1
  18. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  19. package/dist/lib/server/account-mode.d.mts +2 -1
  20. package/dist/lib/server/account-mode.d.ts +2 -1
  21. package/dist/lib/server/account-mode.d.ts.map +1 -1
  22. package/dist/lib/server/account-mode.js +1 -0
  23. package/dist/lib/server/account-mode.js.map +1 -1
  24. package/dist/lib/server/account-mode.mjs +1 -0
  25. package/dist/lib/server/account-mode.mjs.map +1 -1
  26. package/dist/lib/server/create-adcp-server.d.mts +10 -6
  27. package/dist/lib/server/create-adcp-server.d.ts +10 -6
  28. package/dist/lib/server/create-adcp-server.d.ts.map +1 -1
  29. package/dist/lib/server/create-adcp-server.js +2 -1
  30. package/dist/lib/server/create-adcp-server.js.map +1 -1
  31. package/dist/lib/server/create-adcp-server.mjs +2 -1
  32. package/dist/lib/server/create-adcp-server.mjs.map +1 -1
  33. package/dist/lib/server/decisioning/account.d.mts +16 -11
  34. package/dist/lib/server/decisioning/account.d.ts +16 -11
  35. package/dist/lib/server/decisioning/account.d.ts.map +1 -1
  36. package/dist/lib/server/decisioning/account.js.map +1 -1
  37. package/dist/lib/server/decisioning/account.mjs.map +1 -1
  38. package/dist/lib/server/decisioning/pagination.d.mts +2 -0
  39. package/dist/lib/server/decisioning/pagination.d.ts +2 -0
  40. package/dist/lib/server/decisioning/pagination.d.ts.map +1 -1
  41. package/dist/lib/server/decisioning/pagination.js.map +1 -1
  42. package/dist/lib/server/decisioning/runtime/from-platform.d.mts +20 -14
  43. package/dist/lib/server/decisioning/runtime/from-platform.d.ts +20 -14
  44. package/dist/lib/server/decisioning/runtime/from-platform.d.ts.map +1 -1
  45. package/dist/lib/server/decisioning/runtime/from-platform.js +32 -6
  46. package/dist/lib/server/decisioning/runtime/from-platform.js.map +1 -1
  47. package/dist/lib/server/decisioning/runtime/from-platform.mjs +32 -6
  48. package/dist/lib/server/decisioning/runtime/from-platform.mjs.map +1 -1
  49. package/dist/lib/server/decisioning/specialisms/sales.d.mts +16 -4
  50. package/dist/lib/server/decisioning/specialisms/sales.d.ts +16 -4
  51. package/dist/lib/server/decisioning/specialisms/sales.d.ts.map +1 -1
  52. package/dist/lib/server/decisioning/specialisms/sales.js.map +1 -1
  53. package/dist/lib/server/index.d.ts.map +1 -1
  54. package/dist/lib/server/index.js.map +1 -1
  55. package/dist/lib/server/index.mjs.map +1 -1
  56. package/dist/lib/server/legacy/v5/index.d.mts +2 -1
  57. package/dist/lib/server/legacy/v5/index.d.ts +2 -1
  58. package/dist/lib/server/legacy/v5/index.d.ts.map +1 -1
  59. package/dist/lib/server/legacy/v5/index.js.map +1 -1
  60. package/dist/lib/server/legacy/v5/index.mjs.map +1 -1
  61. package/dist/lib/testing/compliance/comply.d.mts +2 -0
  62. package/dist/lib/testing/compliance/comply.d.ts +2 -0
  63. package/dist/lib/testing/compliance/comply.d.ts.map +1 -1
  64. package/dist/lib/testing/compliance/comply.js +45 -14
  65. package/dist/lib/testing/compliance/comply.js.map +1 -1
  66. package/dist/lib/testing/compliance/comply.mjs +45 -14
  67. package/dist/lib/testing/compliance/comply.mjs.map +1 -1
  68. package/dist/lib/testing/compliance/index.d.mts +1 -1
  69. package/dist/lib/testing/compliance/index.d.ts +1 -1
  70. package/dist/lib/testing/compliance/index.d.ts.map +1 -1
  71. package/dist/lib/testing/compliance/index.js.map +1 -1
  72. package/dist/lib/testing/compliance/index.mjs.map +1 -1
  73. package/dist/lib/testing/compliance/storyboard-tracks.d.ts.map +1 -1
  74. package/dist/lib/testing/compliance/storyboard-tracks.js +9 -1
  75. package/dist/lib/testing/compliance/storyboard-tracks.js.map +1 -1
  76. package/dist/lib/testing/compliance/storyboard-tracks.mjs +9 -1
  77. package/dist/lib/testing/compliance/storyboard-tracks.mjs.map +1 -1
  78. package/dist/lib/testing/compliance/types.d.mts +26 -25
  79. package/dist/lib/testing/compliance/types.d.ts +26 -25
  80. package/dist/lib/testing/compliance/types.d.ts.map +1 -1
  81. package/dist/lib/testing/compliance/types.js.map +1 -1
  82. package/dist/lib/testing/index.d.mts +2 -2
  83. package/dist/lib/testing/index.d.ts +2 -2
  84. package/dist/lib/testing/index.d.ts.map +1 -1
  85. package/dist/lib/testing/index.js +16 -0
  86. package/dist/lib/testing/index.js.map +1 -1
  87. package/dist/lib/testing/index.mjs +16 -0
  88. package/dist/lib/testing/index.mjs.map +1 -1
  89. package/dist/lib/testing/scenarios/media-buy.d.ts.map +1 -1
  90. package/dist/lib/testing/scenarios/media-buy.js +20 -12
  91. package/dist/lib/testing/scenarios/media-buy.js.map +1 -1
  92. package/dist/lib/testing/scenarios/media-buy.mjs +20 -12
  93. package/dist/lib/testing/scenarios/media-buy.mjs.map +1 -1
  94. package/dist/lib/testing/storyboard/creative-assets.d.mts +23 -0
  95. package/dist/lib/testing/storyboard/creative-assets.d.ts +23 -0
  96. package/dist/lib/testing/storyboard/creative-assets.d.ts.map +1 -1
  97. package/dist/lib/testing/storyboard/creative-assets.js +322 -45
  98. package/dist/lib/testing/storyboard/creative-assets.js.map +1 -1
  99. package/dist/lib/testing/storyboard/creative-assets.mjs +321 -45
  100. package/dist/lib/testing/storyboard/creative-assets.mjs.map +1 -1
  101. package/dist/lib/testing/storyboard/index.d.mts +4 -1
  102. package/dist/lib/testing/storyboard/index.d.ts +4 -1
  103. package/dist/lib/testing/storyboard/index.d.ts.map +1 -1
  104. package/dist/lib/testing/storyboard/index.js +20 -0
  105. package/dist/lib/testing/storyboard/index.js.map +1 -1
  106. package/dist/lib/testing/storyboard/index.mjs +19 -0
  107. package/dist/lib/testing/storyboard/index.mjs.map +1 -1
  108. package/dist/lib/testing/storyboard/oauth-metadata-graph/grader.d.mts +21 -0
  109. package/dist/lib/testing/storyboard/oauth-metadata-graph/grader.d.ts +22 -0
  110. package/dist/lib/testing/storyboard/oauth-metadata-graph/grader.d.ts.map +1 -0
  111. package/dist/lib/testing/storyboard/oauth-metadata-graph/grader.js +668 -0
  112. package/dist/lib/testing/storyboard/oauth-metadata-graph/grader.js.map +1 -0
  113. package/dist/lib/testing/storyboard/oauth-metadata-graph/grader.mjs +638 -0
  114. package/dist/lib/testing/storyboard/oauth-metadata-graph/grader.mjs.map +1 -0
  115. package/dist/lib/testing/storyboard/oauth-metadata-graph/index.d.mts +5 -0
  116. package/dist/lib/testing/storyboard/oauth-metadata-graph/index.d.ts +6 -0
  117. package/dist/lib/testing/storyboard/oauth-metadata-graph/index.d.ts.map +1 -0
  118. package/dist/lib/testing/storyboard/oauth-metadata-graph/index.js +44 -0
  119. package/dist/lib/testing/storyboard/oauth-metadata-graph/index.js.map +1 -0
  120. package/dist/lib/testing/storyboard/oauth-metadata-graph/index.mjs +20 -0
  121. package/dist/lib/testing/storyboard/oauth-metadata-graph/index.mjs.map +1 -0
  122. package/dist/lib/testing/storyboard/oauth-metadata-graph/types.d.mts +38 -0
  123. package/dist/lib/testing/storyboard/oauth-metadata-graph/types.d.ts +39 -0
  124. package/dist/lib/testing/storyboard/oauth-metadata-graph/types.d.ts.map +1 -0
  125. package/dist/lib/testing/storyboard/oauth-metadata-graph/types.js +17 -0
  126. package/dist/lib/testing/storyboard/oauth-metadata-graph/types.js.map +1 -0
  127. package/dist/lib/testing/storyboard/oauth-metadata-graph/types.mjs +1 -0
  128. package/dist/lib/testing/storyboard/oauth-metadata-graph/types.mjs.map +1 -0
  129. package/dist/lib/testing/storyboard/oauth-metadata-graph/vector-loader.d.mts +32 -0
  130. package/dist/lib/testing/storyboard/oauth-metadata-graph/vector-loader.d.ts +33 -0
  131. package/dist/lib/testing/storyboard/oauth-metadata-graph/vector-loader.d.ts.map +1 -0
  132. package/dist/lib/testing/storyboard/oauth-metadata-graph/vector-loader.js +105 -0
  133. package/dist/lib/testing/storyboard/oauth-metadata-graph/vector-loader.js.map +1 -0
  134. package/dist/lib/testing/storyboard/oauth-metadata-graph/vector-loader.mjs +80 -0
  135. package/dist/lib/testing/storyboard/oauth-metadata-graph/vector-loader.mjs.map +1 -0
  136. package/dist/lib/testing/storyboard/probes.d.ts.map +1 -1
  137. package/dist/lib/testing/storyboard/probes.js +2 -1
  138. package/dist/lib/testing/storyboard/probes.js.map +1 -1
  139. package/dist/lib/testing/storyboard/probes.mjs +2 -1
  140. package/dist/lib/testing/storyboard/probes.mjs.map +1 -1
  141. package/dist/lib/testing/storyboard/runner.d.ts.map +1 -1
  142. package/dist/lib/testing/storyboard/runner.js +382 -69
  143. package/dist/lib/testing/storyboard/runner.js.map +1 -1
  144. package/dist/lib/testing/storyboard/runner.mjs +387 -70
  145. package/dist/lib/testing/storyboard/runner.mjs.map +1 -1
  146. package/dist/lib/testing/storyboard/seeding.js +3 -1
  147. package/dist/lib/testing/storyboard/seeding.js.map +1 -1
  148. package/dist/lib/testing/storyboard/seeding.mjs +3 -1
  149. package/dist/lib/testing/storyboard/seeding.mjs.map +1 -1
  150. package/dist/lib/testing/storyboard/task-map.d.ts.map +1 -1
  151. package/dist/lib/testing/storyboard/task-map.js +16 -2
  152. package/dist/lib/testing/storyboard/task-map.js.map +1 -1
  153. package/dist/lib/testing/storyboard/task-map.mjs +16 -2
  154. package/dist/lib/testing/storyboard/task-map.mjs.map +1 -1
  155. package/dist/lib/testing/storyboard/trusted-match-context-replay.d.mts +11 -0
  156. package/dist/lib/testing/storyboard/trusted-match-context-replay.d.ts +12 -0
  157. package/dist/lib/testing/storyboard/trusted-match-context-replay.d.ts.map +1 -0
  158. package/dist/lib/testing/storyboard/trusted-match-context-replay.js +340 -0
  159. package/dist/lib/testing/storyboard/trusted-match-context-replay.js.map +1 -0
  160. package/dist/lib/testing/storyboard/trusted-match-context-replay.mjs +316 -0
  161. package/dist/lib/testing/storyboard/trusted-match-context-replay.mjs.map +1 -0
  162. package/dist/lib/testing/storyboard/types.d.mts +84 -15
  163. package/dist/lib/testing/storyboard/types.d.ts +84 -15
  164. package/dist/lib/testing/storyboard/types.d.ts.map +1 -1
  165. package/dist/lib/testing/storyboard/types.js +2 -0
  166. package/dist/lib/testing/storyboard/types.js.map +1 -1
  167. package/dist/lib/testing/storyboard/types.mjs +2 -0
  168. package/dist/lib/testing/storyboard/types.mjs.map +1 -1
  169. package/dist/lib/testing/storyboard/validations.d.mts +3 -0
  170. package/dist/lib/testing/storyboard/validations.d.ts +3 -0
  171. package/dist/lib/testing/storyboard/validations.d.ts.map +1 -1
  172. package/dist/lib/testing/storyboard/validations.js +64 -24
  173. package/dist/lib/testing/storyboard/validations.js.map +1 -1
  174. package/dist/lib/testing/storyboard/validations.mjs +64 -24
  175. package/dist/lib/testing/storyboard/validations.mjs.map +1 -1
  176. package/dist/lib/types/server-payload-aliases.d.mts +8 -2
  177. package/dist/lib/types/server-payload-aliases.d.ts +8 -2
  178. package/dist/lib/types/server-payload-aliases.d.ts.map +1 -1
  179. package/dist/lib/types/server-payload-aliases.js.map +1 -1
  180. package/dist/lib/upstream-recorder/recorder.d.mts +7 -18
  181. package/dist/lib/upstream-recorder/recorder.d.ts +7 -18
  182. package/dist/lib/upstream-recorder/recorder.d.ts.map +1 -1
  183. package/dist/lib/upstream-recorder/recorder.js +17 -10
  184. package/dist/lib/upstream-recorder/recorder.js.map +1 -1
  185. package/dist/lib/upstream-recorder/recorder.mjs +12 -5
  186. package/dist/lib/upstream-recorder/recorder.mjs.map +1 -1
  187. package/dist/lib/utils/redact-secrets.d.ts.map +1 -1
  188. package/dist/lib/utils/redact-secrets.js +7 -1
  189. package/dist/lib/utils/redact-secrets.js.map +1 -1
  190. package/dist/lib/utils/redact-secrets.mjs +7 -1
  191. package/dist/lib/utils/redact-secrets.mjs.map +1 -1
  192. package/dist/lib/version.d.mts +3 -3
  193. package/dist/lib/version.d.ts +3 -3
  194. package/dist/lib/version.js +3 -3
  195. package/dist/lib/version.js.map +1 -1
  196. package/dist/lib/version.mjs +3 -3
  197. package/dist/lib/version.mjs.map +1 -1
  198. package/docs/guides/BUILD-AN-AGENT.md +4 -2
  199. package/docs/llms.txt +1 -1
  200. package/package.json +2 -2
@@ -0,0 +1,316 @@
1
+ import { createServer } from "node:http";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { basename, isAbsolute, join } from "node:path";
4
+ import { getComplianceCacheDir } from "./compliance.mjs";
5
+ const DEFAULT_VECTOR_REF = "test-vectors/trusted-match-context-merge/vectors.json";
6
+ const DEFAULT_TIMEOUT_MS = 1e4;
7
+ const MAX_ROUTER_RESPONSE_BYTES = 1024 * 1024;
8
+ const MAX_PROVIDER_REQUEST_BYTES = 64 * 1024;
9
+ async function replayTrustedMatchContextVector(step, options, params = {}) {
10
+ const vectorResult = loadVector(step.vector_ref, options.vectorsRoot, params.adcpVersion);
11
+ const emptyRequest = params.requestOverride ?? step.sample_request ?? {};
12
+ if ("error" in vectorResult) {
13
+ return {
14
+ request: emptyRequest,
15
+ httpResult: failedProbe(options.router_url, vectorResult.error)
16
+ };
17
+ }
18
+ const request = params.requestOverride ?? vectorResult.vector.request ?? step.sample_request;
19
+ if (!request || typeof request !== "object" || Array.isArray(request)) {
20
+ return {
21
+ request: {},
22
+ vectorSource: vectorResult.path,
23
+ httpResult: failedProbe(
24
+ options.router_url,
25
+ "`replay_trusted_match_context_vector` requires vector.request or step.sample_request."
26
+ )
27
+ };
28
+ }
29
+ let fixtureServer;
30
+ let cleanupRegistration = void 0;
31
+ let result;
32
+ try {
33
+ fixtureServer = await createProviderFixtureServer(vectorResult.vector, options.provider_server);
34
+ cleanupRegistration = await options.registerProviders({
35
+ profile: vectorResult.vector.profile,
36
+ providers: fixtureServer.providers,
37
+ request: structuredClone(request)
38
+ });
39
+ const httpResult = await postRouterContext(options, request, params.signal);
40
+ result = { httpResult, request, vectorSource: vectorResult.path };
41
+ } catch (error) {
42
+ result = {
43
+ request,
44
+ vectorSource: vectorResult.path,
45
+ httpResult: failedProbe(options.router_url, error instanceof Error ? error.message : String(error))
46
+ };
47
+ } finally {
48
+ const cleanupErrors = [];
49
+ try {
50
+ if (cleanupRegistration !== void 0) {
51
+ if (typeof cleanupRegistration !== "function") {
52
+ throw new Error("registerProviders must return void or a cleanup function.");
53
+ }
54
+ await cleanupRegistration();
55
+ }
56
+ } catch (error) {
57
+ cleanupErrors.push(`registration cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
58
+ }
59
+ try {
60
+ await fixtureServer?.close();
61
+ } catch (error) {
62
+ cleanupErrors.push(`provider fixture cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
63
+ }
64
+ if (cleanupErrors.length > 0) {
65
+ const priorError = result.httpResult.error;
66
+ result.httpResult.error = [...priorError ? [priorError] : [], ...cleanupErrors].join("; ");
67
+ }
68
+ }
69
+ return result;
70
+ }
71
+ function loadVector(vectorRef, vectorsRoot, adcpVersion) {
72
+ const rawRef = (vectorRef ?? DEFAULT_VECTOR_REF).split("#", 1)[0];
73
+ if (!rawRef || isAbsolute(rawRef) || rawRef.split(/[\\/]/).includes("..")) {
74
+ return { error: `Invalid Trusted Match Context vector_ref: ${vectorRef ?? rawRef}` };
75
+ }
76
+ const withoutStatic = rawRef.replace(/^static\//, "").replace(/^compliance\/source\//, "");
77
+ const candidates = [];
78
+ if (vectorsRoot) {
79
+ candidates.push(join(vectorsRoot, rawRef), join(vectorsRoot, withoutStatic), join(vectorsRoot, basename(rawRef)));
80
+ }
81
+ const complianceDir = getComplianceCacheDir({ ...adcpVersion && { version: adcpVersion } });
82
+ candidates.push(
83
+ join(complianceDir, withoutStatic),
84
+ join(complianceDir, rawRef),
85
+ join(process.cwd(), rawRef),
86
+ join(process.cwd(), withoutStatic)
87
+ );
88
+ for (const candidate of [...new Set(candidates)]) {
89
+ if (!existsSync(candidate)) continue;
90
+ try {
91
+ const parsed = JSON.parse(readFileSync(candidate, "utf8"));
92
+ const validationError = validateVector(parsed);
93
+ if (validationError) return { error: `${candidate}: ${validationError}` };
94
+ return { vector: parsed, path: candidate };
95
+ } catch (error) {
96
+ return { error: `Failed to read Trusted Match Context vector ${candidate}: ${String(error)}` };
97
+ }
98
+ }
99
+ return { error: `Trusted Match Context vector file not found for ${rawRef}` };
100
+ }
101
+ function validateVector(value) {
102
+ if (!isRecord(value)) return "vector must be an object";
103
+ if (typeof value.profile !== "string" || value.profile.length === 0) return "profile must be a non-empty string";
104
+ if (!Array.isArray(value.registrations) || value.registrations.length === 0) {
105
+ return "registrations must be a non-empty array";
106
+ }
107
+ if (!Array.isArray(value.provider_responses) || value.provider_responses.length === 0) {
108
+ return "provider_responses must be a non-empty array";
109
+ }
110
+ const ids = /* @__PURE__ */ new Set();
111
+ for (const registration of value.registrations) {
112
+ if (!isRecord(registration) || typeof registration.provider_id !== "string" || !registration.provider_id) {
113
+ return "every registration requires a non-empty provider_id";
114
+ }
115
+ if (ids.has(registration.provider_id)) return `duplicate provider_id: ${registration.provider_id}`;
116
+ ids.add(registration.provider_id);
117
+ }
118
+ const responseIds = /* @__PURE__ */ new Set();
119
+ for (const entry of value.provider_responses) {
120
+ if (!isRecord(entry) || typeof entry.registration_provider_id !== "string" || !isRecord(entry.response)) {
121
+ return "every provider response requires registration_provider_id and an object response";
122
+ }
123
+ if (!ids.has(entry.registration_provider_id)) {
124
+ return `provider response has no matching registration: ${entry.registration_provider_id}`;
125
+ }
126
+ if (responseIds.has(entry.registration_provider_id)) {
127
+ return `duplicate provider response: ${entry.registration_provider_id}`;
128
+ }
129
+ responseIds.add(entry.registration_provider_id);
130
+ }
131
+ for (const providerId of ids) {
132
+ if (!responseIds.has(providerId)) return `registration has no provider response: ${providerId}`;
133
+ }
134
+ if ("request" in value && !isRecord(value.request)) return "request must be an object when present";
135
+ return void 0;
136
+ }
137
+ async function createProviderFixtureServer(vector, config) {
138
+ const host = config?.host ?? "127.0.0.1";
139
+ const port = config?.port ?? 0;
140
+ const mode = config?.mode ?? "loopback_mock";
141
+ if (mode === "proxy_url" && !config?.public_url) {
142
+ throw new Error("trusted_match_context_router_runner.provider_server.public_url is required in proxy_url mode.");
143
+ }
144
+ if (config?.public_url) assertHttpUrl(config.public_url, "provider_server.public_url");
145
+ const responses = new Map(
146
+ vector.provider_responses.map((entry) => [entry.registration_provider_id, entry.response])
147
+ );
148
+ const server = createServer((req, res) => {
149
+ void serveProviderFixture(req, res, responses).catch((error) => {
150
+ if (res.headersSent) {
151
+ res.destroy(error instanceof Error ? error : new Error(String(error)));
152
+ } else {
153
+ respondJson(res, 500, { error: "fixture_server_error" });
154
+ }
155
+ });
156
+ });
157
+ await listen(server, host, port);
158
+ const address = server.address();
159
+ if (!address || typeof address === "string") {
160
+ await closeServer(server);
161
+ throw new Error("Provider fixture server did not expose a TCP address.");
162
+ }
163
+ const localBase = `http://${formatHost(host)}:${address.port}`;
164
+ const advertisedBase = (mode === "proxy_url" ? config.public_url : localBase).replace(/\/$/, "");
165
+ const providers = vector.registrations.map(({ provider_id }) => ({
166
+ provider_id,
167
+ context_url: `${advertisedBase}/providers/${encodeURIComponent(provider_id)}/context`
168
+ }));
169
+ return { providers, close: () => closeServer(server) };
170
+ }
171
+ async function serveProviderFixture(req, res, responses) {
172
+ const pathname = new URL(req.url ?? "/", "http://fixture.invalid").pathname;
173
+ const match = pathname.match(/^\/providers\/([^/]+)\/context$/);
174
+ if (req.method !== "POST" || !match) {
175
+ respondJson(res, req.method === "POST" ? 404 : 405, { error: "not_found" });
176
+ return;
177
+ }
178
+ try {
179
+ await consumeBody(req, MAX_PROVIDER_REQUEST_BYTES);
180
+ } catch {
181
+ respondJson(res, 413, { error: "request_too_large" });
182
+ return;
183
+ }
184
+ const providerId = decodeURIComponent(match[1]);
185
+ const response = responses.get(providerId);
186
+ if (!response) {
187
+ respondJson(res, 404, { error: "provider_not_found" });
188
+ return;
189
+ }
190
+ respondJson(res, 200, response);
191
+ }
192
+ async function postRouterContext(options, request, parentSignal) {
193
+ const routerUrl = `${options.router_url.replace(/\/$/, "")}/context`;
194
+ const parsed = new URL(routerUrl);
195
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
196
+ throw new Error("trusted_match_context_router_runner.router_url must use http or https.");
197
+ }
198
+ const controller = new AbortController();
199
+ const abort = () => controller.abort(parentSignal?.reason);
200
+ if (parentSignal?.aborted) abort();
201
+ parentSignal?.addEventListener("abort", abort, { once: true });
202
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
203
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
204
+ throw new Error("trusted_match_context_router_runner.timeoutMs must be a positive finite number.");
205
+ }
206
+ const timer = setTimeout(
207
+ () => controller.abort(new Error(`Context router replay timed out after ${timeoutMs}ms`)),
208
+ timeoutMs
209
+ );
210
+ try {
211
+ const response = await (options.fetchImpl ?? fetch)(routerUrl, {
212
+ method: "POST",
213
+ headers: { "content-type": "application/json", ...options.headers ?? {} },
214
+ body: JSON.stringify(request),
215
+ signal: controller.signal
216
+ });
217
+ const body = await readResponseBody(response, MAX_ROUTER_RESPONSE_BYTES);
218
+ return {
219
+ url: routerUrl,
220
+ status: response.status,
221
+ headers: responseHeaders(response.headers),
222
+ body,
223
+ ...!response.ok && { error: `Context router returned HTTP ${response.status}.` }
224
+ };
225
+ } finally {
226
+ clearTimeout(timer);
227
+ parentSignal?.removeEventListener("abort", abort);
228
+ }
229
+ }
230
+ function assertHttpUrl(value, field) {
231
+ const parsed = new URL(value);
232
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
233
+ throw new Error(`trusted_match_context_router_runner.${field} must use http or https.`);
234
+ }
235
+ }
236
+ async function readResponseBody(response, maxBytes) {
237
+ if (!response.body) return null;
238
+ const reader = response.body.getReader();
239
+ const chunks = [];
240
+ let size = 0;
241
+ while (true) {
242
+ const { value, done } = await reader.read();
243
+ if (done) break;
244
+ if (!value) continue;
245
+ size += value.byteLength;
246
+ if (size > maxBytes) {
247
+ await reader.cancel();
248
+ throw new Error(`Context router response exceeded ${maxBytes} bytes.`);
249
+ }
250
+ chunks.push(value);
251
+ }
252
+ const bytes = new Uint8Array(size);
253
+ let offset = 0;
254
+ for (const chunk of chunks) {
255
+ bytes.set(chunk, offset);
256
+ offset += chunk.byteLength;
257
+ }
258
+ const text = new TextDecoder().decode(bytes);
259
+ if (!text) return null;
260
+ try {
261
+ return JSON.parse(text);
262
+ } catch {
263
+ return text;
264
+ }
265
+ }
266
+ function listen(server, host, port) {
267
+ return new Promise((resolve, reject) => {
268
+ const onError = (error) => reject(error);
269
+ server.once("error", onError);
270
+ server.listen(port, host, () => {
271
+ server.off("error", onError);
272
+ resolve();
273
+ });
274
+ });
275
+ }
276
+ function closeServer(server) {
277
+ return new Promise((resolve, reject) => {
278
+ server.close((error) => error ? reject(error) : resolve());
279
+ });
280
+ }
281
+ function consumeBody(req, maxBytes) {
282
+ return new Promise((resolve, reject) => {
283
+ let size = 0;
284
+ req.on("data", (chunk) => {
285
+ size += Buffer.byteLength(chunk);
286
+ if (size > maxBytes) reject(new Error("request too large"));
287
+ });
288
+ req.on("end", resolve);
289
+ req.on("error", reject);
290
+ });
291
+ }
292
+ function respondJson(res, status, body) {
293
+ const encoded = JSON.stringify(body);
294
+ res.writeHead(status, { "content-type": "application/json", "content-length": Buffer.byteLength(encoded) });
295
+ res.end(encoded);
296
+ }
297
+ function formatHost(host) {
298
+ return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
299
+ }
300
+ function failedProbe(url, error) {
301
+ return { url, status: 0, headers: {}, body: null, error };
302
+ }
303
+ function responseHeaders(headers) {
304
+ const result = {};
305
+ headers.forEach((value, key) => {
306
+ result[key] = value;
307
+ });
308
+ return result;
309
+ }
310
+ function isRecord(value) {
311
+ return value !== null && typeof value === "object" && !Array.isArray(value);
312
+ }
313
+ export {
314
+ replayTrustedMatchContextVector
315
+ };
316
+ //# sourceMappingURL=trusted-match-context-replay.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/lib/testing/storyboard/trusted-match-context-replay.ts"],"sourcesContent":["import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { basename, isAbsolute, join } from 'node:path';\nimport { getComplianceCacheDir } from './compliance';\nimport type {\n HttpProbeResult,\n StoryboardStep,\n TrustedMatchContextProviderEndpoint,\n TrustedMatchContextRouterRunnerOptions,\n} from './types';\n\nconst DEFAULT_VECTOR_REF = 'test-vectors/trusted-match-context-merge/vectors.json';\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst MAX_ROUTER_RESPONSE_BYTES = 1024 * 1024;\nconst MAX_PROVIDER_REQUEST_BYTES = 64 * 1024;\n\ninterface TrustedMatchContextVector {\n profile: string;\n registrations: Array<{ provider_id: string }>;\n provider_responses: Array<{\n registration_provider_id: string;\n response: Record<string, unknown>;\n }>;\n request?: Record<string, unknown>;\n}\n\nexport interface TrustedMatchContextReplayResult {\n httpResult: HttpProbeResult;\n request: Record<string, unknown>;\n vectorSource?: string;\n}\n\nexport async function replayTrustedMatchContextVector(\n step: StoryboardStep,\n options: TrustedMatchContextRouterRunnerOptions,\n params: {\n requestOverride?: Record<string, unknown>;\n adcpVersion?: string;\n signal?: AbortSignal;\n } = {}\n): Promise<TrustedMatchContextReplayResult> {\n const vectorResult = loadVector(step.vector_ref, options.vectorsRoot, params.adcpVersion);\n const emptyRequest = params.requestOverride ?? step.sample_request ?? {};\n if ('error' in vectorResult) {\n return {\n request: emptyRequest,\n httpResult: failedProbe(options.router_url, vectorResult.error),\n };\n }\n\n const request = params.requestOverride ?? vectorResult.vector.request ?? step.sample_request;\n if (!request || typeof request !== 'object' || Array.isArray(request)) {\n return {\n request: {},\n vectorSource: vectorResult.path,\n httpResult: failedProbe(\n options.router_url,\n '`replay_trusted_match_context_vector` requires vector.request or step.sample_request.'\n ),\n };\n }\n\n let fixtureServer: ProviderFixtureServer | undefined;\n let cleanupRegistration: void | (() => void | Promise<void>) = undefined;\n let result: TrustedMatchContextReplayResult;\n try {\n fixtureServer = await createProviderFixtureServer(vectorResult.vector, options.provider_server);\n cleanupRegistration = await options.registerProviders({\n profile: vectorResult.vector.profile,\n providers: fixtureServer.providers,\n request: structuredClone(request),\n });\n const httpResult = await postRouterContext(options, request, params.signal);\n result = { httpResult, request, vectorSource: vectorResult.path };\n } catch (error) {\n result = {\n request,\n vectorSource: vectorResult.path,\n httpResult: failedProbe(options.router_url, error instanceof Error ? error.message : String(error)),\n };\n } finally {\n const cleanupErrors: string[] = [];\n try {\n if (cleanupRegistration !== undefined) {\n if (typeof cleanupRegistration !== 'function') {\n throw new Error('registerProviders must return void or a cleanup function.');\n }\n await cleanupRegistration();\n }\n } catch (error) {\n cleanupErrors.push(`registration cleanup failed: ${error instanceof Error ? error.message : String(error)}`);\n }\n try {\n await fixtureServer?.close();\n } catch (error) {\n cleanupErrors.push(`provider fixture cleanup failed: ${error instanceof Error ? error.message : String(error)}`);\n }\n if (cleanupErrors.length > 0) {\n const priorError = result!.httpResult.error;\n result!.httpResult.error = [...(priorError ? [priorError] : []), ...cleanupErrors].join('; ');\n }\n }\n return result!;\n}\n\nfunction loadVector(\n vectorRef: string | undefined,\n vectorsRoot: string | undefined,\n adcpVersion: string | undefined\n): { vector: TrustedMatchContextVector; path: string } | { error: string } {\n const rawRef = (vectorRef ?? DEFAULT_VECTOR_REF).split('#', 1)[0]!;\n if (!rawRef || isAbsolute(rawRef) || rawRef.split(/[\\\\/]/).includes('..')) {\n return { error: `Invalid Trusted Match Context vector_ref: ${vectorRef ?? rawRef}` };\n }\n\n const withoutStatic = rawRef.replace(/^static\\//, '').replace(/^compliance\\/source\\//, '');\n const candidates: string[] = [];\n if (vectorsRoot) {\n candidates.push(join(vectorsRoot, rawRef), join(vectorsRoot, withoutStatic), join(vectorsRoot, basename(rawRef)));\n }\n const complianceDir = getComplianceCacheDir({ ...(adcpVersion && { version: adcpVersion }) });\n candidates.push(\n join(complianceDir, withoutStatic),\n join(complianceDir, rawRef),\n join(process.cwd(), rawRef),\n join(process.cwd(), withoutStatic)\n );\n\n for (const candidate of [...new Set(candidates)]) {\n if (!existsSync(candidate)) continue;\n try {\n const parsed = JSON.parse(readFileSync(candidate, 'utf8')) as unknown;\n const validationError = validateVector(parsed);\n if (validationError) return { error: `${candidate}: ${validationError}` };\n return { vector: parsed as TrustedMatchContextVector, path: candidate };\n } catch (error) {\n return { error: `Failed to read Trusted Match Context vector ${candidate}: ${String(error)}` };\n }\n }\n return { error: `Trusted Match Context vector file not found for ${rawRef}` };\n}\n\nfunction validateVector(value: unknown): string | undefined {\n if (!isRecord(value)) return 'vector must be an object';\n if (typeof value.profile !== 'string' || value.profile.length === 0) return 'profile must be a non-empty string';\n if (!Array.isArray(value.registrations) || value.registrations.length === 0) {\n return 'registrations must be a non-empty array';\n }\n if (!Array.isArray(value.provider_responses) || value.provider_responses.length === 0) {\n return 'provider_responses must be a non-empty array';\n }\n\n const ids = new Set<string>();\n for (const registration of value.registrations) {\n if (!isRecord(registration) || typeof registration.provider_id !== 'string' || !registration.provider_id) {\n return 'every registration requires a non-empty provider_id';\n }\n if (ids.has(registration.provider_id)) return `duplicate provider_id: ${registration.provider_id}`;\n ids.add(registration.provider_id);\n }\n const responseIds = new Set<string>();\n for (const entry of value.provider_responses) {\n if (!isRecord(entry) || typeof entry.registration_provider_id !== 'string' || !isRecord(entry.response)) {\n return 'every provider response requires registration_provider_id and an object response';\n }\n if (!ids.has(entry.registration_provider_id)) {\n return `provider response has no matching registration: ${entry.registration_provider_id}`;\n }\n if (responseIds.has(entry.registration_provider_id)) {\n return `duplicate provider response: ${entry.registration_provider_id}`;\n }\n responseIds.add(entry.registration_provider_id);\n }\n for (const providerId of ids) {\n if (!responseIds.has(providerId)) return `registration has no provider response: ${providerId}`;\n }\n if ('request' in value && !isRecord(value.request)) return 'request must be an object when present';\n return undefined;\n}\n\ninterface ProviderFixtureServer {\n providers: TrustedMatchContextProviderEndpoint[];\n close(): Promise<void>;\n}\n\nasync function createProviderFixtureServer(\n vector: TrustedMatchContextVector,\n config: TrustedMatchContextRouterRunnerOptions['provider_server']\n): Promise<ProviderFixtureServer> {\n const host = config?.host ?? '127.0.0.1';\n const port = config?.port ?? 0;\n const mode = config?.mode ?? 'loopback_mock';\n if (mode === 'proxy_url' && !config?.public_url) {\n throw new Error('trusted_match_context_router_runner.provider_server.public_url is required in proxy_url mode.');\n }\n if (config?.public_url) assertHttpUrl(config.public_url, 'provider_server.public_url');\n\n const responses = new Map(\n vector.provider_responses.map(entry => [entry.registration_provider_id, entry.response] as const)\n );\n const server = createServer((req, res) => {\n void serveProviderFixture(req, res, responses).catch(error => {\n if (res.headersSent) {\n res.destroy(error instanceof Error ? error : new Error(String(error)));\n } else {\n respondJson(res, 500, { error: 'fixture_server_error' });\n }\n });\n });\n await listen(server, host, port);\n const address = server.address();\n if (!address || typeof address === 'string') {\n await closeServer(server);\n throw new Error('Provider fixture server did not expose a TCP address.');\n }\n const localBase = `http://${formatHost(host)}:${address.port}`;\n const advertisedBase = (mode === 'proxy_url' ? config!.public_url! : localBase).replace(/\\/$/, '');\n const providers = vector.registrations.map(({ provider_id }) => ({\n provider_id,\n context_url: `${advertisedBase}/providers/${encodeURIComponent(provider_id)}/context`,\n }));\n return { providers, close: () => closeServer(server) };\n}\n\nasync function serveProviderFixture(\n req: IncomingMessage,\n res: ServerResponse,\n responses: Map<string, Record<string, unknown>>\n): Promise<void> {\n const pathname = new URL(req.url ?? '/', 'http://fixture.invalid').pathname;\n const match = pathname.match(/^\\/providers\\/([^/]+)\\/context$/);\n if (req.method !== 'POST' || !match) {\n respondJson(res, req.method === 'POST' ? 404 : 405, { error: 'not_found' });\n return;\n }\n try {\n await consumeBody(req, MAX_PROVIDER_REQUEST_BYTES);\n } catch {\n respondJson(res, 413, { error: 'request_too_large' });\n return;\n }\n const providerId = decodeURIComponent(match[1]!);\n const response = responses.get(providerId);\n if (!response) {\n respondJson(res, 404, { error: 'provider_not_found' });\n return;\n }\n respondJson(res, 200, response);\n}\n\nasync function postRouterContext(\n options: TrustedMatchContextRouterRunnerOptions,\n request: Record<string, unknown>,\n parentSignal?: AbortSignal\n): Promise<HttpProbeResult> {\n const routerUrl = `${options.router_url.replace(/\\/$/, '')}/context`;\n const parsed = new URL(routerUrl);\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new Error('trusted_match_context_router_runner.router_url must use http or https.');\n }\n const controller = new AbortController();\n const abort = () => controller.abort(parentSignal?.reason);\n if (parentSignal?.aborted) abort();\n parentSignal?.addEventListener('abort', abort, { once: true });\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n throw new Error('trusted_match_context_router_runner.timeoutMs must be a positive finite number.');\n }\n const timer = setTimeout(\n () => controller.abort(new Error(`Context router replay timed out after ${timeoutMs}ms`)),\n timeoutMs\n );\n try {\n const response = await (options.fetchImpl ?? fetch)(routerUrl, {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...(options.headers ?? {}) },\n body: JSON.stringify(request),\n signal: controller.signal,\n });\n const body = await readResponseBody(response, MAX_ROUTER_RESPONSE_BYTES);\n return {\n url: routerUrl,\n status: response.status,\n headers: responseHeaders(response.headers),\n body,\n ...(!response.ok && { error: `Context router returned HTTP ${response.status}.` }),\n };\n } finally {\n clearTimeout(timer);\n parentSignal?.removeEventListener('abort', abort);\n }\n}\n\nfunction assertHttpUrl(value: string, field: string): void {\n const parsed = new URL(value);\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new Error(`trusted_match_context_router_runner.${field} must use http or https.`);\n }\n}\n\nasync function readResponseBody(response: Response, maxBytes: number): Promise<unknown> {\n if (!response.body) return null;\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let size = 0;\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n if (!value) continue;\n size += value.byteLength;\n if (size > maxBytes) {\n await reader.cancel();\n throw new Error(`Context router response exceeded ${maxBytes} bytes.`);\n }\n chunks.push(value);\n }\n const bytes = new Uint8Array(size);\n let offset = 0;\n for (const chunk of chunks) {\n bytes.set(chunk, offset);\n offset += chunk.byteLength;\n }\n const text = new TextDecoder().decode(bytes);\n if (!text) return null;\n try {\n return JSON.parse(text) as unknown;\n } catch {\n return text;\n }\n}\n\nfunction listen(server: Server, host: string, port: number): Promise<void> {\n return new Promise((resolve, reject) => {\n const onError = (error: Error) => reject(error);\n server.once('error', onError);\n server.listen(port, host, () => {\n server.off('error', onError);\n resolve();\n });\n });\n}\n\nfunction closeServer(server: Server): Promise<void> {\n return new Promise((resolve, reject) => {\n server.close(error => (error ? reject(error) : resolve()));\n });\n}\n\nfunction consumeBody(req: IncomingMessage, maxBytes: number): Promise<void> {\n return new Promise((resolve, reject) => {\n let size = 0;\n req.on('data', chunk => {\n size += Buffer.byteLength(chunk);\n if (size > maxBytes) reject(new Error('request too large'));\n });\n req.on('end', resolve);\n req.on('error', reject);\n });\n}\n\nfunction respondJson(res: ServerResponse, status: number, body: unknown): void {\n const encoded = JSON.stringify(body);\n res.writeHead(status, { 'content-type': 'application/json', 'content-length': Buffer.byteLength(encoded) });\n res.end(encoded);\n}\n\nfunction formatHost(host: string): string {\n return host.includes(':') && !host.startsWith('[') ? `[${host}]` : host;\n}\n\nfunction failedProbe(url: string, error: string): HttpProbeResult {\n return { url, status: 0, headers: {}, body: null, error };\n}\n\nfunction responseHeaders(headers: Headers): Record<string, string> {\n const result: Record<string, string> = {};\n headers.forEach((value, key) => {\n result[key] = value;\n });\n return result;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n"],"mappings":"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","names":[]}
@@ -83,6 +83,15 @@ export interface Storyboard {
83
83
  * declares this requirement implicitly. Authors do not need to
84
84
  * write `requires: [webhook_receiver]` — the tokens are
85
85
  * self-describing.
86
+ * - `webhook_replay_receiver` — the runner must be configured with an
87
+ * inbound buyer/orchestrator receiver URL
88
+ * (`StoryboardRunOptions.webhook_replay_receiver.url`). Autodetected
89
+ * from any `replay_webhook_vector` step and graded not applicable when
90
+ * absent. Spec: adcp-client#2356.
91
+ * - `trusted_match_context_router_runner` — the runner must be configured
92
+ * with a raw Context Match router URL and operator registration seam.
93
+ * Autodetected from `replay_trusted_match_context_vector`; absent
94
+ * harnesses grade not_applicable. Spec: adcp-client#2479.
86
95
  * - `request_signer` — the agent under test MUST advertise
87
96
  * `request_signing.supported: true` in `get_adcp_capabilities`.
88
97
  * Autodetected: any storyboard whose `id === 'signed_requests'`
@@ -827,7 +836,7 @@ export type StoryboardValidationCheck = 'response_schema' | 'field_present' | 'f
827
836
  * envelope fields such as `adcp_major_version` should use
828
837
  * `envelope_field_value`.
829
838
  */
830
- | 'envelope_field_pattern' | 'field_contains' | 'status_code' | 'error_code' | 'http_status' | 'http_status_in' | 'on_401_require_header' | 'resource_equals_agent_url' | 'any_of' | 'a2a_submitted_artifact' | 'a2a_context_continuity' | 'refs_resolve'
839
+ | 'envelope_field_pattern' | 'field_contains' | 'status_code' | 'error_code' | 'http_status' | 'http_status_in' | 'on_401_require_header' | 'resource_equals_agent_url' | 'oauth_metadata_graph' | 'any_of' | 'a2a_submitted_artifact' | 'a2a_context_continuity' | 'refs_resolve'
831
840
  /**
832
841
  * Assert a numeric field in the current step's response is strictly less than
833
842
  * a comparand. The comparand is either a context-captured runtime value
@@ -1308,6 +1317,51 @@ export interface AgentEntry {
1308
1317
  /** Per-tenant transport override; defaults to `StoryboardRunOptions.protocol`. */
1309
1318
  transport?: 'mcp' | 'a2a';
1310
1319
  }
1320
+ /** One runner-hosted Context Match provider fixture exposed to the router under test. */
1321
+ export interface TrustedMatchContextProviderEndpoint {
1322
+ /** Publisher-controlled registration id from the packaged conformance vector. */
1323
+ provider_id: string;
1324
+ /** Absolute URL serving this provider's vector response at `POST /context`. */
1325
+ context_url: string;
1326
+ }
1327
+ /** Input to the operator-owned router registration seam. */
1328
+ export interface TrustedMatchContextRegistrationContext {
1329
+ /** Vector profile, currently `adcp/trusted-match/context-targeting-merge/v1`. */
1330
+ profile: string;
1331
+ /** Fixed provider ids paired with the runner-hosted fixture URLs. */
1332
+ providers: TrustedMatchContextProviderEndpoint[];
1333
+ /** Exact Context Match request the runner will send after registration completes. */
1334
+ request: Record<string, unknown>;
1335
+ }
1336
+ export interface TrustedMatchContextRouterRunnerOptions {
1337
+ /** Router base URL. The runner posts the vector request to `<router_url>/context`. */
1338
+ router_url: string;
1339
+ /**
1340
+ * Operator-owned seam that installs the supplied provider registrations in
1341
+ * the router under test. It may return a cleanup callback, which the runner
1342
+ * invokes after the replay even when the router request fails.
1343
+ */
1344
+ registerProviders(context: TrustedMatchContextRegistrationContext): void | (() => void | Promise<void>) | Promise<void | (() => void | Promise<void>)>;
1345
+ /** Extra headers sent to the router's `/context` endpoint. */
1346
+ headers?: Record<string, string>;
1347
+ /** Router request timeout in milliseconds. Defaults to 10000. */
1348
+ timeoutMs?: number;
1349
+ /** Optional root directory for resolving the packaged vector file. */
1350
+ vectorsRoot?: string;
1351
+ /** Override fetch for tests or custom runtimes. */
1352
+ fetchImpl?: typeof fetch;
1353
+ /** Configuration for the ephemeral provider fixture server. */
1354
+ provider_server?: {
1355
+ /** Loopback by default; proxy mode advertises `public_url` to remote routers. */
1356
+ mode?: 'loopback_mock' | 'proxy_url';
1357
+ /** Bind host for the local fixture listener. Defaults to `127.0.0.1`. */
1358
+ host?: string;
1359
+ /** Bind port. `0` (default) lets the kernel assign one. */
1360
+ port?: number;
1361
+ /** Public base URL routed to the local listener; required in proxy mode. */
1362
+ public_url?: string;
1363
+ };
1364
+ }
1311
1365
  export interface StoryboardRunOptions extends TestOptions {
1312
1366
  /** Initial context (e.g., from a previous step invocation) */
1313
1367
  context?: StoryboardContext;
@@ -1540,6 +1594,14 @@ export interface StoryboardRunOptions extends TestOptions {
1540
1594
  /** Override fetch for tests or custom runtimes. */
1541
1595
  fetchImpl?: typeof fetch;
1542
1596
  };
1597
+ /**
1598
+ * Raw-HTTP Trusted Match Context router harness used by
1599
+ * `replay_trusted_match_context_vector`. The runner hosts the packaged
1600
+ * provider fixtures, calls `registerProviders`, then posts the vector
1601
+ * request to `<router_url>/context`. Without this option the storyboard
1602
+ * grades `not_applicable`; it never falls back to MCP or A2A.
1603
+ */
1604
+ trusted_match_context_router_runner?: TrustedMatchContextRouterRunnerOptions;
1543
1605
  /**
1544
1606
  * Test-kit contract ids that are in scope for this run. A step with
1545
1607
  * `requires_contract: <id>` grades `not_applicable` when the id is not
@@ -1634,7 +1696,16 @@ export interface RunnerExtractionRecord {
1634
1696
  * whether a skip is informative (agent didn't claim the protocol) or masking
1635
1697
  * (runner couldn't apply the storyboard even though the agent claimed it).
1636
1698
  */
1637
- export type RunnerSkipReason = 'not_applicable' | 'no_phases' | 'prerequisite_failed' | 'missing_tool' | 'missing_test_controller' | 'unsatisfied_contract'
1699
+ export type RunnerSkipReason = 'not_applicable' | 'no_phases' | 'prerequisite_failed' | 'missing_tool' | 'missing_test_controller'
1700
+ /**
1701
+ * The runner selected the pathway but its test kit cannot synthesize a
1702
+ * valid input for the seller-declared contract. This is a runner-owned
1703
+ * coverage gap: it never fails validations or cascades prerequisites.
1704
+ * Spec: AdCP runner-output-contract v2.8.0,
1705
+ * `skip_result.reasons.fixture_unavailable` (adcp#6276). Early preflight
1706
+ * and whole-storyboard termination semantics: adcp#6278.
1707
+ */
1708
+ | 'fixture_unavailable' | 'unsatisfied_contract'
1638
1709
  /**
1639
1710
  * A storyboard-level `requires:` tag named a requirement that is not
1640
1711
  * available on the current run (e.g. `controller` when the agent doesn't
@@ -1666,7 +1737,7 @@ export type RunnerSkipReason = 'not_applicable' | 'no_phases' | 'prerequisite_fa
1666
1737
  */
1667
1738
  | 'peer_substituted';
1668
1739
  /**
1669
- * Grader-specific skip reasons. These are narrower than the six canonical
1740
+ * Grader-specific skip reasons. These are narrower than the canonical
1670
1741
  * `RunnerSkipReason` values — they carry runner-local context (which probe,
1671
1742
  * which operator opt-out) that the contract neither requires nor forbids.
1672
1743
  * The runner records them on `skip_reason` for legacy consumers, and also
@@ -1726,7 +1797,7 @@ export type RunnerDetailedSkipReason = 'probe_skipped' | 'rate_abuse_opt_out' |
1726
1797
  */
1727
1798
  | 'force_scenario_unsupported';
1728
1799
  /**
1729
- * Map detailed grader skip reasons onto the six canonical spec values so
1800
+ * Map detailed grader skip reasons onto the canonical spec values so
1730
1801
  * consumers reading `skip.reason` get a stable enum regardless of which
1731
1802
  * subsystem produced the skip.
1732
1803
  */
@@ -1757,9 +1828,9 @@ export interface RunnerSelectionResult {
1757
1828
  * surface change; coordinate with the upstream spec proposal before
1758
1829
  * extending.
1759
1830
  *
1760
- * Spec: adcp-client#1626, adcp-client#2281.
1831
+ * Spec: adcp-client#1626, adcp-client#2281, adcp-client#2356.
1761
1832
  */
1762
- export type RequirementName = 'controller' | 'seeded_state' | 'real_wire' | 'webhook_receiver' | 'request_signer' | 'multi_agent';
1833
+ export type RequirementName = 'controller' | 'seeded_state' | 'real_wire' | 'webhook_receiver' | 'webhook_replay_receiver' | 'trusted_match_context_router_runner' | 'request_signer' | 'multi_agent';
1763
1834
  /**
1764
1835
  * Enumeration of every requirement this SDK knows how to satisfy. The loader
1765
1836
  * accepts other non-empty strings for forward compatibility; the runner treats
@@ -1901,13 +1972,11 @@ export interface ValidationResult {
1901
1972
  /** Optional remediation hint. */
1902
1973
  remediation?: string;
1903
1974
  /**
1904
- * Forward-compat marker: set when the runner did not implement the
1905
- * authored check kind and graded it as `not_applicable` (passed: true)
1906
- * to preserve forward compatibility with future spec additions. The
1907
- * companion `note` describes the coverage gap. Per
1908
- * runner-output-contract.yaml v2.0.0 these contribute to the run
1909
- * summary's `validations_not_applicable` counter so consumers can
1910
- * distinguish "runner is older than the storyboard" from clean passes.
1975
+ * Marker for an implemented check whose declared applicability conditions
1976
+ * were not met. Unknown authored check kinds fail closed; they never set
1977
+ * this flag or contribute a false green result. Entries carrying this flag
1978
+ * contribute to `validations_not_applicable` so consumers can distinguish
1979
+ * inapplicable coverage from clean passes.
1911
1980
  */
1912
1981
  not_applicable?: boolean;
1913
1982
  /**
@@ -1989,8 +2058,8 @@ export interface StoryboardStepResult {
1989
2058
  /** True when the step was not executed */
1990
2059
  skipped?: boolean;
1991
2060
  /**
1992
- * Skip reason. Accepts either a canonical `RunnerSkipReason` (the six
1993
- * spec-required values) or one of the grader-specific variants introduced
2061
+ * Skip reason. Accepts either a canonical `RunnerSkipReason` or one of the
2062
+ * grader-specific variants introduced
1994
2063
  * by the RFC 9421 request-signing grader (#585, #617). The structured
1995
2064
  * `skip` field below always carries the canonical spec reason so consumers
1996
2065
  * of the runner-output contract don't need to know the grader vocabulary.