@adcp/sdk 14.0.0-beta.12 → 14.0.0-beta.13
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.
- package/dist/lib/index.d.mts +3 -3
- package/dist/lib/index.d.ts +3 -3
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +2 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/index.mjs +3 -1
- package/dist/lib/index.mjs.map +1 -1
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/create-adcp-server.d.mts +2 -2
- package/dist/lib/server/create-adcp-server.d.ts +2 -2
- package/dist/lib/server/create-adcp-server.d.ts.map +1 -1
- package/dist/lib/server/create-adcp-server.js +6 -1
- package/dist/lib/server/create-adcp-server.js.map +1 -1
- package/dist/lib/server/create-adcp-server.mjs +6 -1
- package/dist/lib/server/create-adcp-server.mjs.map +1 -1
- package/dist/lib/server/decisioning/index.d.mts +3 -3
- package/dist/lib/server/decisioning/index.d.ts +3 -3
- package/dist/lib/server/decisioning/index.d.ts.map +1 -1
- package/dist/lib/server/decisioning/index.js.map +1 -1
- package/dist/lib/server/decisioning/index.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/from-platform.d.mts +24 -17
- package/dist/lib/server/decisioning/runtime/from-platform.d.ts +24 -17
- package/dist/lib/server/decisioning/runtime/from-platform.d.ts.map +1 -1
- package/dist/lib/server/decisioning/runtime/from-platform.js +71 -42
- package/dist/lib/server/decisioning/runtime/from-platform.js.map +1 -1
- package/dist/lib/server/decisioning/runtime/from-platform.mjs +75 -43
- package/dist/lib/server/decisioning/runtime/from-platform.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/postgres-task-registry.d.mts +15 -21
- package/dist/lib/server/decisioning/runtime/postgres-task-registry.d.ts +15 -21
- package/dist/lib/server/decisioning/runtime/postgres-task-registry.d.ts.map +1 -1
- package/dist/lib/server/decisioning/runtime/postgres-task-registry.js +100 -30
- package/dist/lib/server/decisioning/runtime/postgres-task-registry.js.map +1 -1
- package/dist/lib/server/decisioning/runtime/postgres-task-registry.mjs +100 -30
- package/dist/lib/server/decisioning/runtime/postgres-task-registry.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/task-registry.d.mts +30 -16
- package/dist/lib/server/decisioning/runtime/task-registry.d.ts +30 -16
- package/dist/lib/server/decisioning/runtime/task-registry.d.ts.map +1 -1
- package/dist/lib/server/decisioning/runtime/task-registry.js +40 -20
- package/dist/lib/server/decisioning/runtime/task-registry.js.map +1 -1
- package/dist/lib/server/decisioning/runtime/task-registry.mjs +38 -19
- package/dist/lib/server/decisioning/runtime/task-registry.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/to-context.d.mts +2 -2
- package/dist/lib/server/decisioning/runtime/to-context.d.ts +2 -2
- package/dist/lib/server/decisioning/runtime/to-context.d.ts.map +1 -1
- package/dist/lib/server/decisioning/runtime/to-context.js +2 -2
- package/dist/lib/server/decisioning/runtime/to-context.js.map +1 -1
- package/dist/lib/server/decisioning/runtime/to-context.mjs +2 -2
- package/dist/lib/server/decisioning/runtime/to-context.mjs.map +1 -1
- package/dist/lib/server/decisioning/specialisms/creative-ad-server.d.mts +3 -2
- package/dist/lib/server/decisioning/specialisms/creative-ad-server.d.ts +3 -2
- package/dist/lib/server/decisioning/specialisms/creative-ad-server.d.ts.map +1 -1
- package/dist/lib/server/decisioning/specialisms/creative-ad-server.js.map +1 -1
- package/dist/lib/server/decisioning/specialisms/creative.d.mts +10 -10
- package/dist/lib/server/decisioning/specialisms/creative.d.ts +10 -10
- package/dist/lib/server/decisioning/specialisms/creative.d.ts.map +1 -1
- package/dist/lib/server/decisioning/specialisms/creative.js.map +1 -1
- package/dist/lib/server/index.d.mts +1 -1
- package/dist/lib/server/index.d.ts +1 -1
- package/dist/lib/server/index.d.ts.map +1 -1
- package/dist/lib/server/index.js.map +1 -1
- package/dist/lib/server/index.mjs.map +1 -1
- package/dist/lib/testing/storyboard/default-invariants.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/default-invariants.js +11 -2
- package/dist/lib/testing/storyboard/default-invariants.js.map +1 -1
- package/dist/lib/testing/storyboard/default-invariants.mjs +11 -2
- package/dist/lib/testing/storyboard/default-invariants.mjs.map +1 -1
- package/dist/lib/testing/storyboard/index.d.mts +1 -1
- package/dist/lib/testing/storyboard/index.d.ts +1 -1
- package/dist/lib/testing/storyboard/index.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/index.js.map +1 -1
- package/dist/lib/testing/storyboard/index.mjs.map +1 -1
- package/dist/lib/testing/storyboard/loader.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/loader.js +14 -0
- package/dist/lib/testing/storyboard/loader.js.map +1 -1
- package/dist/lib/testing/storyboard/loader.mjs +14 -0
- package/dist/lib/testing/storyboard/loader.mjs.map +1 -1
- package/dist/lib/testing/storyboard/phase-condition.d.mts +41 -0
- package/dist/lib/testing/storyboard/phase-condition.d.ts +42 -0
- package/dist/lib/testing/storyboard/phase-condition.d.ts.map +1 -0
- package/dist/lib/testing/storyboard/phase-condition.js +215 -0
- package/dist/lib/testing/storyboard/phase-condition.js.map +1 -0
- package/dist/lib/testing/storyboard/phase-condition.mjs +189 -0
- package/dist/lib/testing/storyboard/phase-condition.mjs.map +1 -0
- package/dist/lib/testing/storyboard/request-builder.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/request-builder.js +15 -2
- package/dist/lib/testing/storyboard/request-builder.js.map +1 -1
- package/dist/lib/testing/storyboard/request-builder.mjs +15 -2
- package/dist/lib/testing/storyboard/request-builder.mjs.map +1 -1
- package/dist/lib/testing/storyboard/runner.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/runner.js +57 -23
- package/dist/lib/testing/storyboard/runner.js.map +1 -1
- package/dist/lib/testing/storyboard/runner.mjs +57 -23
- package/dist/lib/testing/storyboard/runner.mjs.map +1 -1
- package/dist/lib/testing/storyboard/task-map.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/task-map.js +6 -2
- package/dist/lib/testing/storyboard/task-map.js.map +1 -1
- package/dist/lib/testing/storyboard/task-map.mjs +6 -2
- package/dist/lib/testing/storyboard/task-map.mjs.map +1 -1
- package/dist/lib/testing/storyboard/types.d.mts +5 -4
- package/dist/lib/testing/storyboard/types.d.ts +5 -4
- package/dist/lib/testing/storyboard/types.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/types.js.map +1 -1
- package/dist/lib/testing/storyboard/types.mjs.map +1 -1
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/testing/storyboard/webhook-assertions.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/webhook-assertions.js +60 -4
- package/dist/lib/testing/storyboard/webhook-assertions.js.map +1 -1
- package/dist/lib/testing/storyboard/webhook-assertions.mjs +60 -4
- package/dist/lib/testing/storyboard/webhook-assertions.mjs.map +1 -1
- package/dist/lib/testing/storyboard/webhook-receiver.d.mts +15 -0
- package/dist/lib/testing/storyboard/webhook-receiver.d.ts +15 -0
- package/dist/lib/testing/storyboard/webhook-receiver.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/webhook-receiver.js +57 -6
- package/dist/lib/testing/storyboard/webhook-receiver.js.map +1 -1
- package/dist/lib/testing/storyboard/webhook-receiver.mjs +57 -6
- package/dist/lib/testing/storyboard/webhook-receiver.mjs.map +1 -1
- package/dist/lib/testing/types.d.mts +2 -0
- package/dist/lib/testing/types.d.ts +2 -0
- package/dist/lib/testing/types.d.ts.map +1 -1
- package/dist/lib/testing/types.js.map +1 -1
- package/dist/lib/types/core.generated.d.mts +5 -2
- package/dist/lib/types/core.generated.d.ts +5 -2
- package/dist/lib/types/core.generated.d.ts.map +1 -1
- package/dist/lib/types/core.generated.js.map +1 -1
- package/dist/lib/types/index.d.mts +1 -1
- package/dist/lib/types/index.d.ts +1 -1
- package/dist/lib/types/index.d.ts.map +1 -1
- package/dist/lib/types/index.js.map +1 -1
- package/dist/lib/types/index.mjs.map +1 -1
- package/dist/lib/types/list-transformers.d.ts +9 -5
- package/dist/lib/types/schemas.generated.d.ts +21 -298
- package/dist/lib/types/schemas.generated.d.ts.map +1 -1
- package/dist/lib/types/schemas.generated.js +5 -2
- package/dist/lib/types/schemas.generated.js.map +1 -1
- package/dist/lib/types/schemas.generated.mjs +4 -2
- package/dist/lib/types/schemas.generated.mjs.map +1 -1
- package/dist/lib/types/server-payload-aliases.d.mts +2 -1
- package/dist/lib/types/server-payload-aliases.d.ts +2 -1
- package/dist/lib/types/server-payload-aliases.d.ts.map +1 -1
- package/dist/lib/types/server-payload-aliases.js.map +1 -1
- package/dist/lib/types/tools.generated.d.mts +5 -2
- package/dist/lib/types/tools.generated.d.ts +5 -2
- package/dist/lib/types/tools.generated.d.ts.map +1 -1
- package/dist/lib/types/tools.generated.js.map +1 -1
- package/dist/lib/utils/build-creative-return-builders.d.mts +5 -1
- package/dist/lib/utils/build-creative-return-builders.d.ts +5 -1
- package/dist/lib/utils/build-creative-return-builders.d.ts.map +1 -1
- package/dist/lib/utils/build-creative-return-builders.js +9 -3
- package/dist/lib/utils/build-creative-return-builders.js.map +1 -1
- package/dist/lib/utils/build-creative-return-builders.mjs +7 -2
- package/dist/lib/utils/build-creative-return-builders.mjs.map +1 -1
- package/dist/lib/utils/response-unwrapper.js +1 -1
- package/dist/lib/utils/response-unwrapper.js.map +1 -1
- package/dist/lib/utils/response-unwrapper.mjs +1 -1
- package/dist/lib/utils/response-unwrapper.mjs.map +1 -1
- package/dist/lib/validation/schema-loader.d.ts.map +1 -1
- package/dist/lib/validation/schema-loader.js +16 -7
- package/dist/lib/validation/schema-loader.js.map +1 -1
- package/dist/lib/validation/schema-loader.mjs +16 -7
- package/dist/lib/validation/schema-loader.mjs.map +1 -1
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.js.map +1 -1
- package/dist/lib/version.mjs +3 -3
- package/dist/lib/version.mjs.map +1 -1
- package/docs/llms.txt +2 -2
- package/docs/migration-5.x-to-6.x.md +3 -2
- package/docs/migration-task-registry-scoping.md +61 -0
- package/examples/hello_seller_adapter_guaranteed.ts +1 -1
- package/package.json +4 -4
- package/skills/build-decisioning-creative-template/SKILL.md +2 -2
- package/skills/build-decisioning-platform/SKILL.md +4 -2
- package/skills/build-decisioning-platform/advanced/REFERENCE.md +7 -6
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { getSchemaValidatorByRef } from "../../validation/schema-loader.mjs";
|
|
3
4
|
const MAX_BODY_BYTES = 1048576;
|
|
5
|
+
const MAX_CHALLENGE_BODY_BYTES = 16384;
|
|
4
6
|
const MAX_CAPTURED_TOTAL = 1e3;
|
|
5
7
|
const MAX_CAPTURED_PER_KEY = 100;
|
|
6
8
|
const HEADERS_TIMEOUT_MS = 1e4;
|
|
@@ -45,13 +47,15 @@ async function createWebhookReceiver(options = {}) {
|
|
|
45
47
|
const port = options.port ?? 0;
|
|
46
48
|
const proxyBase = mode === "proxy_url" ? validateProxyUrl(options.public_url) : void 0;
|
|
47
49
|
const captured = [];
|
|
50
|
+
const challenges = [];
|
|
48
51
|
const waiters = [];
|
|
49
52
|
const waitAllTimers = [];
|
|
50
53
|
const retryPolicies = /* @__PURE__ */ new Map();
|
|
51
54
|
const deliveryCounts = /* @__PURE__ */ new Map();
|
|
55
|
+
const challengeCounts = /* @__PURE__ */ new Map();
|
|
52
56
|
let closed = false;
|
|
53
57
|
const server = createServer(
|
|
54
|
-
(req, res) => handleRequest(req, res, { captured, waiters, retryPolicies, deliveryCounts })
|
|
58
|
+
(req, res) => handleRequest(req, res, { captured, challenges, waiters, retryPolicies, deliveryCounts, challengeCounts })
|
|
55
59
|
);
|
|
56
60
|
server.headersTimeout = HEADERS_TIMEOUT_MS;
|
|
57
61
|
server.requestTimeout = REQUEST_TIMEOUT_MS;
|
|
@@ -72,6 +76,7 @@ async function createWebhookReceiver(options = {}) {
|
|
|
72
76
|
base_url,
|
|
73
77
|
mode,
|
|
74
78
|
all: () => captured.slice(),
|
|
79
|
+
challenges: () => challenges.slice(),
|
|
75
80
|
matching: (filter) => captured.filter((w) => matchesFilter(w, filter)),
|
|
76
81
|
set_retry_replay: (key, policy) => {
|
|
77
82
|
retryPolicies.set(retryKeyString(key), { policy, delivered: 0 });
|
|
@@ -123,11 +128,52 @@ function handleRequest(req, res, state) {
|
|
|
123
128
|
}
|
|
124
129
|
const raw = Buffer.concat(chunks).toString("utf8");
|
|
125
130
|
const headers = normalizeHeaders(req.headers);
|
|
131
|
+
const parsedBody = parseBody(raw, headers["content-type"]);
|
|
132
|
+
if (isWebhookChallenge(parsedBody.body)) {
|
|
133
|
+
if (Buffer.byteLength(raw, "utf8") > MAX_CHALLENGE_BODY_BYTES) {
|
|
134
|
+
res.statusCode = 413;
|
|
135
|
+
res.end();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const challenge = parsedBody.body.challenge;
|
|
139
|
+
const schemaRef = parsedBody.body.scope === "agent" ? "core/agent-webhook-challenge.json" : "core/webhook-challenge.json";
|
|
140
|
+
const validate = getSchemaValidatorByRef(schemaRef);
|
|
141
|
+
if (!validate || !validate(parsedBody.body) || typeof challenge !== "string") {
|
|
142
|
+
res.statusCode = 400;
|
|
143
|
+
res.end();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const key2 = retryKeyString({ step_id, operation_id });
|
|
147
|
+
const perKey2 = state.challengeCounts.get(key2) ?? 0;
|
|
148
|
+
if (state.captured.length + state.challenges.length >= MAX_CAPTURED_TOTAL || perKey2 >= MAX_CAPTURED_PER_KEY) {
|
|
149
|
+
res.statusCode = 429;
|
|
150
|
+
res.setHeader("retry-after", "1");
|
|
151
|
+
res.end();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
state.challengeCounts.set(key2, perKey2 + 1);
|
|
155
|
+
state.challenges.push({
|
|
156
|
+
id: randomUUID(),
|
|
157
|
+
step_id,
|
|
158
|
+
operation_id,
|
|
159
|
+
received_at: Date.now(),
|
|
160
|
+
method: req.method ?? "POST",
|
|
161
|
+
path: req.url ?? "/",
|
|
162
|
+
headers: redactHeaders(headers),
|
|
163
|
+
raw_body: raw,
|
|
164
|
+
body: parsedBody.body,
|
|
165
|
+
response_status: 200
|
|
166
|
+
});
|
|
167
|
+
res.statusCode = 200;
|
|
168
|
+
res.setHeader("content-type", "application/json");
|
|
169
|
+
res.end(JSON.stringify({ challenge }));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
126
172
|
const key = retryKeyString({ step_id, operation_id });
|
|
127
173
|
const deliveryIndex = (state.deliveryCounts.get(key) ?? 0) + 1;
|
|
128
174
|
const status = nextResponseStatus(state.retryPolicies, key);
|
|
129
175
|
const perKey = state.deliveryCounts.get(key) ?? 0;
|
|
130
|
-
if (state.captured.length >= MAX_CAPTURED_TOTAL || perKey >= MAX_CAPTURED_PER_KEY) {
|
|
176
|
+
if (state.captured.length + state.challenges.length >= MAX_CAPTURED_TOTAL || perKey >= MAX_CAPTURED_PER_KEY) {
|
|
131
177
|
res.statusCode = status;
|
|
132
178
|
if (status === 429 || status >= 500) res.setHeader("retry-after", "1");
|
|
133
179
|
res.end();
|
|
@@ -144,7 +190,7 @@ function handleRequest(req, res, state) {
|
|
|
144
190
|
path: req.url ?? "/",
|
|
145
191
|
headers: redactHeaders(headers),
|
|
146
192
|
raw_body: raw,
|
|
147
|
-
...
|
|
193
|
+
...parsedBody,
|
|
148
194
|
response_status: status
|
|
149
195
|
};
|
|
150
196
|
state.captured.push(webhook);
|
|
@@ -166,10 +212,15 @@ function handleRequest(req, res, state) {
|
|
|
166
212
|
}
|
|
167
213
|
});
|
|
168
214
|
}
|
|
169
|
-
function
|
|
215
|
+
function isWebhookChallenge(body) {
|
|
216
|
+
return body !== null && typeof body === "object" && !Array.isArray(body) && body.type === "webhook.challenge";
|
|
217
|
+
}
|
|
218
|
+
function requestPathname(reqUrl) {
|
|
170
219
|
const q = reqUrl.indexOf("?");
|
|
171
|
-
|
|
172
|
-
|
|
220
|
+
return q === -1 ? reqUrl : reqUrl.slice(0, q);
|
|
221
|
+
}
|
|
222
|
+
function parseStepPath(reqUrl) {
|
|
223
|
+
const match = STEP_PATH_RE.exec(requestPathname(reqUrl));
|
|
173
224
|
if (!match) return void 0;
|
|
174
225
|
return { step_id: match[1], operation_id: match[2] };
|
|
175
226
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/lib/testing/storyboard/webhook-receiver.ts"],"sourcesContent":["/**\n * Ephemeral webhook receiver for storyboard runs.\n *\n * The runner binds an HTTP listener, mints per-step URLs under\n * `/step/<step_id>/<operation_id>`, and captures inbound POSTs so\n * `expect_webhook*` pseudo-steps can observe outbound webhook behavior\n * (idempotency_key presence, retry-key stability, 9421 signature validity).\n *\n * Matches the `webhook_receiver_runner` test-kit contract in\n * adcontextprotocol/adcp#2431:\n * - Per-step URL template `{{runner.webhook_base}}/step/<step_id>/<operation_id>`.\n * - Retry-replay: the receiver returns a configurable 5xx for the first N\n * deliveries on a given `(step_id, operation_id)`, then 2xx, so senders\n * are forced to retry and retry-key stability can be graded.\n * - Loopback-mock mode (HTTP on 127.0.0.1, zero external deps) by default;\n * proxy-URL mode swaps in an operator-supplied public base.\n */\n\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport { randomUUID } from 'node:crypto';\nimport type { AddressInfo } from 'node:net';\n\n/**\n * Size cap on any single webhook body. A non-conformant sender retrying\n * with oversize payloads shouldn't be able to exhaust the runner's memory.\n */\nconst MAX_BODY_BYTES = 1_048_576; // 1 MiB\n\n/**\n * Caps on cumulative capture state. `MAX_CAPTURED_TOTAL` bounds the total\n * number of webhooks retained for the run; `MAX_CAPTURED_PER_KEY` bounds\n * per-(step_id, operation_id) deliveries — a publisher stuck in a retry\n * loop can't exhaust memory. Once a cap is hit the receiver stops retaining\n * bodies but continues the configured retry-status sequence (or returns 204\n * when no rejection remains), so the cap cannot trap a sender in a retry loop.\n */\nconst MAX_CAPTURED_TOTAL = 1000;\nconst MAX_CAPTURED_PER_KEY = 100;\n\n/**\n * HTTP transport hardening. Defaults in Node 20 are generous (headers\n * 60s, no body timeout). A hostile / slow publisher shouldn't be able to\n * hold the runner's event loop with trickled requests or exhaust the\n * socket table.\n */\nconst HEADERS_TIMEOUT_MS = 10_000;\nconst REQUEST_TIMEOUT_MS = 30_000;\nconst KEEP_ALIVE_TIMEOUT_MS = 5_000;\nconst MAX_CONNECTIONS = 64;\nconst MAX_HEADERS_COUNT = 64;\n\n/** Default path shape under the receiver base. */\nconst STEP_PATH_RE = /^\\/step\\/([A-Za-z0-9_]+)\\/([A-Za-z0-9_-]+)\\/?$/;\n\n/**\n * Header names whose values are redacted before a captured webhook lands\n * in a compliance report. Mirrors the `SECRET_KEY_PATTERN` in runner.ts\n * so both paths honor the contract's \"Secrets SHOULD be redacted\" rule.\n */\nconst SECRET_HEADER_PATTERN =\n /^(authorization|credentials?|token|api[_-]?key|x-api[_-]?key|x-auth[_-]?token|password|secret|client[_-]secret|refresh[_-]token|access[_-]token|bearer|session[_-]token|cookie|set[_-]cookie)$/i;\n\n/**\n * Strip user-info, scheme-policed, and CRLF/NUL-rejected proxy URLs so a\n * misconfigured operator (or a compromised storyboard loader) can't\n * advertise an `http://169.254.169.254`, `file://…`, or header-splitting\n * URL that would then be embedded in outbound `push_notification_config.url`.\n */\nfunction validateProxyUrl(raw: string): string {\n if (/[\\r\\n\\x00]/.test(raw)) {\n throw new Error('webhook_receiver.public_url must not contain CR/LF/NUL');\n }\n let parsed: URL;\n try {\n parsed = new URL(raw);\n } catch {\n throw new Error(`webhook_receiver.public_url is not a valid URL: ${raw}`);\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new Error(`webhook_receiver.public_url must be http(s); got ${parsed.protocol}`);\n }\n if (parsed.username || parsed.password) {\n throw new Error('webhook_receiver.public_url must not include userinfo');\n }\n return raw.replace(/\\/$/, '');\n}\n\nexport interface CapturedWebhook {\n /** Runner-assigned id; stable across filter matches. */\n id: string;\n /** Step id extracted from the URL path. */\n step_id: string;\n /** Operation id extracted from the URL path. */\n operation_id: string;\n /** Delivery sequence number for this (step_id, operation_id) — 1-based. */\n delivery_index: number;\n /** Wall-clock millis at receipt. */\n received_at: number;\n method: string;\n /** Path + query. */\n path: string;\n /** Lowercased header names; duplicate values joined with \", \". */\n headers: Record<string, string>;\n /** Raw request body — needed for RFC 9421 content-digest recompute. */\n raw_body: string;\n /** Parsed JSON body; undefined on parse failure. */\n body?: unknown;\n /** Parse error message when raw_body was non-empty but not valid JSON. */\n parse_error?: string;\n /** HTTP status the receiver returned (reflects retry-replay policy). */\n response_status: number;\n}\n\n/** Match predicate applied when waiting for a webhook. */\nexport interface WebhookFilter {\n /** Restrict matches to this step id's URL. */\n step_id?: string;\n /** Restrict matches to this operation id. */\n operation_id?: string;\n /** Dotted-path → expected-value pairs compared against the parsed body. */\n body?: Record<string, unknown>;\n}\n\nexport type WebhookWaitResult =\n | { webhook: CapturedWebhook; timed_out?: false }\n | { timed_out: true; webhook?: undefined };\n\nexport interface RetryReplayPolicy {\n /** How many deliveries to reject before accepting. */\n count: number;\n /** HTTP status to return on rejection. */\n http_status: number;\n}\n\nexport interface WebhookReceiver {\n /** Public base URL (`http://host:port` for loopback, operator URL for proxy). */\n readonly base_url: string;\n /** Whether the receiver is in loopback-mock or proxy-url mode. */\n readonly mode: 'loopback_mock' | 'proxy_url';\n /** All webhooks captured so far, in arrival order. */\n all(): CapturedWebhook[];\n /** Webhooks matching a filter, in arrival order. */\n matching(filter: WebhookFilter): CapturedWebhook[];\n /**\n * Configure the receiver to return `http_status` for the first `count`\n * deliveries at `(step_id, operation_id)` and 2xx afterwards. Idempotent:\n * setting a new policy replaces any existing one for the same key.\n */\n set_retry_replay(key: { step_id: string; operation_id: string }, policy: RetryReplayPolicy): void;\n /** Resolve with the first match within `timeout_ms`, or `timed_out: true`. */\n wait(filter: WebhookFilter, timeout_ms: number): Promise<WebhookWaitResult>;\n /**\n * Resolve with every match observed between call time and deadline. Used\n * by `expect_webhook_retry_keys_stable` which needs the full sequence of\n * deliveries for the retry-replay assertion.\n */\n wait_all(filter: WebhookFilter, timeout_ms: number): Promise<CapturedWebhook[]>;\n /** Stop the listener. Idempotent. */\n close(): Promise<void>;\n}\n\nexport interface CreateWebhookReceiverOptions {\n mode?: 'loopback_mock' | 'proxy_url';\n /** Bind host for the local listener. Defaults to `127.0.0.1`. */\n host?: string;\n /** Bind port. `0` (default) lets the kernel assign one. */\n port?: number;\n /** Public URL to advertise when `mode: 'proxy_url'`. */\n public_url?: string;\n}\n\ninterface RetryKey {\n step_id: string;\n operation_id: string;\n}\n\nfunction retryKeyString(k: RetryKey): string {\n return `${k.step_id}::${k.operation_id}`;\n}\n\nexport async function createWebhookReceiver(options: CreateWebhookReceiverOptions = {}): Promise<WebhookReceiver> {\n const mode = options.mode ?? 'loopback_mock';\n if (mode === 'proxy_url' && !options.public_url) {\n throw new Error('webhook_receiver.mode=proxy_url requires `public_url`');\n }\n\n const host = options.host ?? '127.0.0.1';\n // A loopback_mock receiver bound on 0.0.0.0 is publicly reachable from any\n // interface — which would turn a CI runner into an open POST endpoint.\n // Operators who genuinely want public exposure use mode=proxy_url.\n if (mode === 'loopback_mock' && (host === '0.0.0.0' || host === '::')) {\n throw new Error(\n `webhook_receiver host ${host} is not permitted in loopback_mock mode. ` +\n 'Use mode=proxy_url with an explicit public_url for publicly-reachable runs.'\n );\n }\n const port = options.port ?? 0;\n const proxyBase = mode === 'proxy_url' ? validateProxyUrl(options.public_url!) : undefined;\n\n const captured: CapturedWebhook[] = [];\n const waiters: Array<{\n filter: WebhookFilter;\n resolve: (result: WebhookWaitResult) => void;\n timer: NodeJS.Timeout;\n }> = [];\n const waitAllTimers: Array<{\n filter: WebhookFilter;\n resolve: (result: CapturedWebhook[]) => void;\n timer: NodeJS.Timeout;\n }> = [];\n const retryPolicies = new Map<string, { policy: RetryReplayPolicy; delivered: number }>();\n const deliveryCounts = new Map<string, number>();\n let closed = false;\n\n const server = createServer((req, res) =>\n handleRequest(req, res, { captured, waiters, retryPolicies, deliveryCounts })\n );\n // Transport-level hardening — trim Node's generous defaults so a slow /\n // hostile publisher can't wedge the runner.\n server.headersTimeout = HEADERS_TIMEOUT_MS;\n server.requestTimeout = REQUEST_TIMEOUT_MS;\n server.keepAliveTimeout = KEEP_ALIVE_TIMEOUT_MS;\n server.maxConnections = MAX_CONNECTIONS;\n server.maxHeadersCount = MAX_HEADERS_COUNT;\n\n await new Promise<void>((resolve, reject) => {\n const onError = (err: Error) => reject(err);\n server.once('error', onError);\n server.listen(port, host, () => {\n server.off('error', onError);\n resolve();\n });\n });\n\n const bound = server.address() as AddressInfo;\n const base_url = proxyBase ?? `http://${formatHost(bound.address)}:${bound.port}`;\n\n return {\n base_url,\n mode,\n all: () => captured.slice(),\n matching: filter => captured.filter(w => matchesFilter(w, filter)),\n set_retry_replay: (key, policy) => {\n retryPolicies.set(retryKeyString(key), { policy, delivered: 0 });\n },\n wait: (filter, timeout_ms) => {\n if (!closed) return wait(filter, timeout_ms, captured, waiters);\n const already = captured.find(w => matchesFilter(w, filter));\n return Promise.resolve(already ? { webhook: already } : { timed_out: true });\n },\n wait_all: (filter, timeout_ms) =>\n closed\n ? Promise.resolve(captured.filter(w => matchesFilter(w, filter)))\n : waitAll(filter, timeout_ms, captured, waitAllTimers),\n close: () => {\n closed = true;\n return closeServer(server, captured, waiters, waitAllTimers);\n },\n };\n}\n\n// ────────────────────────────────────────────────────────────\n// Request handling\n// ────────────────────────────────────────────────────────────\n\ninterface HandlerState {\n captured: CapturedWebhook[];\n waiters: Array<{\n filter: WebhookFilter;\n resolve: (r: WebhookWaitResult) => void;\n timer: NodeJS.Timeout;\n }>;\n retryPolicies: Map<string, { policy: RetryReplayPolicy; delivered: number }>;\n deliveryCounts: Map<string, number>;\n}\n\nfunction handleRequest(req: IncomingMessage, res: ServerResponse, state: HandlerState): void {\n if (req.method !== 'POST') {\n res.statusCode = 405;\n res.end();\n return;\n }\n\n const pathParts = parseStepPath(req.url ?? '');\n if (!pathParts) {\n res.statusCode = 404;\n res.end();\n return;\n }\n const { step_id, operation_id } = pathParts;\n\n let size = 0;\n const chunks: Buffer[] = [];\n let tooLarge = false;\n\n req.on('data', (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_BODY_BYTES) {\n tooLarge = true;\n req.destroy();\n return;\n }\n chunks.push(chunk);\n });\n\n req.on('end', () => {\n if (tooLarge) {\n if (!res.headersSent) {\n res.statusCode = 413;\n res.end();\n }\n return;\n }\n const raw = Buffer.concat(chunks).toString('utf8');\n const headers = normalizeHeaders(req.headers);\n\n const key = retryKeyString({ step_id, operation_id });\n const deliveryIndex = (state.deliveryCounts.get(key) ?? 0) + 1;\n const status = nextResponseStatus(state.retryPolicies, key);\n\n // Stop retaining bodies once a capture cap is exhausted, but preserve the\n // configured retry-replay status sequence. Unconditionally returning 503\n // here masks the policy's eventual 2xx forever and can amplify a sender's\n // retries after the runner has already reached its memory bound (#2653).\n const perKey = state.deliveryCounts.get(key) ?? 0;\n if (state.captured.length >= MAX_CAPTURED_TOTAL || perKey >= MAX_CAPTURED_PER_KEY) {\n res.statusCode = status;\n if (status === 429 || status >= 500) res.setHeader('retry-after', '1');\n res.end();\n return;\n }\n\n state.deliveryCounts.set(key, deliveryIndex);\n\n const webhook: CapturedWebhook = {\n id: randomUUID(),\n step_id,\n operation_id,\n delivery_index: deliveryIndex,\n received_at: Date.now(),\n method: req.method ?? 'POST',\n path: req.url ?? '/',\n headers: redactHeaders(headers),\n raw_body: raw,\n ...parseBody(raw, headers['content-type']),\n response_status: status,\n };\n state.captured.push(webhook);\n\n // Deliver to any matching waiters. First-match-wins per waiter; later\n // arrivals may still deliver to other waiters in the queue.\n for (let i = state.waiters.length - 1; i >= 0; i--) {\n const waiter = state.waiters[i]!;\n if (matchesFilter(webhook, waiter.filter)) {\n clearTimeout(waiter.timer);\n state.waiters.splice(i, 1);\n waiter.resolve({ webhook });\n }\n }\n\n res.statusCode = status;\n res.end();\n });\n\n req.on('error', () => {\n if (!res.headersSent) {\n res.statusCode = 400;\n res.end();\n }\n });\n}\n\nfunction parseStepPath(reqUrl: string): { step_id: string; operation_id: string } | undefined {\n const q = reqUrl.indexOf('?');\n const pathname = q === -1 ? reqUrl : reqUrl.slice(0, q);\n const match = STEP_PATH_RE.exec(pathname);\n if (!match) return undefined;\n return { step_id: match[1]!, operation_id: match[2]! };\n}\n\nfunction normalizeHeaders(raw: IncomingMessage['headers']): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(raw)) {\n if (value === undefined) continue;\n out[key.toLowerCase()] = Array.isArray(value) ? value.join(', ') : value;\n }\n return out;\n}\n\nfunction redactHeaders(headers: Record<string, string>): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [k, v] of Object.entries(headers)) {\n out[k] = SECRET_HEADER_PATTERN.test(k) ? '[redacted]' : v;\n }\n return out;\n}\n\nfunction parseBody(raw: string, contentType: string | undefined): Pick<CapturedWebhook, 'body' | 'parse_error'> {\n if (raw.length === 0) return {};\n const looksJson = !contentType || /json/i.test(contentType);\n if (!looksJson) return {};\n try {\n return { body: JSON.parse(raw) };\n } catch (err) {\n return { parse_error: err instanceof Error ? err.message : String(err) };\n }\n}\n\n// ────────────────────────────────────────────────────────────\n// Filter matching\n// ────────────────────────────────────────────────────────────\n\nfunction matchesFilter(webhook: CapturedWebhook, filter: WebhookFilter): boolean {\n if (filter.step_id && webhook.step_id !== filter.step_id) return false;\n if (filter.operation_id && webhook.operation_id !== filter.operation_id) return false;\n if (filter.body && Object.keys(filter.body).length > 0) {\n if (webhook.body === undefined) return false;\n for (const [path, expected] of Object.entries(filter.body)) {\n const actual = resolveDottedPath(webhook.body, path);\n if (!deepEqual(actual, expected)) return false;\n }\n }\n return true;\n}\n\nfunction resolveDottedPath(obj: unknown, path: string): unknown {\n if (obj === null || obj === undefined) return undefined;\n const segments = path.split('.');\n let cursor: unknown = obj;\n for (const seg of segments) {\n if (cursor === null || cursor === undefined) return undefined;\n if (typeof cursor !== 'object') return undefined;\n cursor = (cursor as Record<string, unknown>)[seg];\n }\n return cursor;\n}\n\n/**\n * Structural deep-equal. Stringify-based comparison is key-order-sensitive\n * (`{a:1,b:2}` !== `{b:2,a:1}`); for filter bodies that's a false negative\n * when a publisher's JSON serializer emits fields in a different order\n * than the storyboard author. Recurse explicitly instead.\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a !== typeof b) return false;\n if (a === null || b === null) return a === b;\n if (typeof a !== 'object') return false;\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b)) return false;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!deepEqual(a[i], b[i])) return false;\n }\n return true;\n }\n const keysA = Object.keys(a as Record<string, unknown>);\n const keysB = Object.keys(b as Record<string, unknown>);\n if (keysA.length !== keysB.length) return false;\n for (const k of keysA) {\n if (!Object.prototype.hasOwnProperty.call(b, k)) return false;\n if (!deepEqual((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k])) return false;\n }\n return true;\n}\n\n// ────────────────────────────────────────────────────────────\n// Retry-replay policy\n// ────────────────────────────────────────────────────────────\n\nfunction nextResponseStatus(\n retryPolicies: Map<string, { policy: RetryReplayPolicy; delivered: number }>,\n key: string\n): number {\n const entry = retryPolicies.get(key);\n if (!entry) return 204;\n if (entry.delivered < entry.policy.count) {\n entry.delivered += 1;\n return entry.policy.http_status;\n }\n return 204;\n}\n\n// ────────────────────────────────────────────────────────────\n// wait / wait_all\n// ────────────────────────────────────────────────────────────\n\nfunction wait(\n filter: WebhookFilter,\n timeout_ms: number,\n captured: CapturedWebhook[],\n waiters: Array<{ filter: WebhookFilter; resolve: (r: WebhookWaitResult) => void; timer: NodeJS.Timeout }>\n): Promise<WebhookWaitResult> {\n const already = captured.find(w => matchesFilter(w, filter));\n if (already) return Promise.resolve({ webhook: already });\n return new Promise<WebhookWaitResult>(resolve => {\n const timer = setTimeout(\n () => {\n const idx = waiters.findIndex(w => w.timer === timer);\n if (idx >= 0) waiters.splice(idx, 1);\n resolve({ timed_out: true });\n },\n Math.max(0, timeout_ms)\n );\n timer.unref?.();\n waiters.push({ filter, resolve, timer });\n });\n}\n\n/**\n * Poll until the deadline, then return every capture matching `filter`.\n * Used when the assertion needs the whole delivery sequence (retry-stability).\n * Waits a minimum of `timeout_ms` even if matches arrive earlier — callers\n * that need \"resolve on first match\" should use `wait` instead.\n */\nfunction waitAll(\n filter: WebhookFilter,\n timeout_ms: number,\n captured: CapturedWebhook[],\n waitAllTimers: Array<{\n filter: WebhookFilter;\n resolve: (result: CapturedWebhook[]) => void;\n timer: NodeJS.Timeout;\n }>\n): Promise<CapturedWebhook[]> {\n return new Promise<CapturedWebhook[]>(resolve => {\n const timer = setTimeout(\n () => {\n const idx = waitAllTimers.findIndex(entry => entry.timer === timer);\n if (idx >= 0) waitAllTimers.splice(idx, 1);\n resolve(captured.filter(w => matchesFilter(w, filter)));\n },\n Math.max(0, timeout_ms)\n );\n timer.unref?.();\n waitAllTimers.push({ filter, resolve, timer });\n });\n}\n\n// ────────────────────────────────────────────────────────────\n// close\n// ────────────────────────────────────────────────────────────\n\nfunction closeServer(\n server: Server,\n captured: CapturedWebhook[],\n waiters: Array<{ filter: WebhookFilter; resolve: (r: WebhookWaitResult) => void; timer: NodeJS.Timeout }>,\n waitAllTimers: Array<{\n filter: WebhookFilter;\n resolve: (result: CapturedWebhook[]) => void;\n timer: NodeJS.Timeout;\n }>\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n server.close(err => {\n if (err && (err as NodeJS.ErrnoException).code !== 'ERR_SERVER_NOT_RUNNING') reject(err);\n else resolve();\n });\n // Stop accepting new sockets before force-closing existing keep-alive\n // connections; the opposite order leaves a small acceptance race.\n server.closeAllConnections?.();\n while (waiters.length > 0) {\n const w = waiters.pop()!;\n clearTimeout(w.timer);\n w.resolve({ timed_out: true });\n }\n while (waitAllTimers.length > 0) {\n const pending = waitAllTimers.pop()!;\n clearTimeout(pending.timer);\n pending.resolve(captured.filter(w => matchesFilter(w, pending.filter)));\n }\n });\n}\n\nfunction formatHost(address: string): string {\n return address.includes(':') ? `[${address}]` : address;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/lib/testing/storyboard/webhook-receiver.ts"],"sourcesContent":["/**\n * Ephemeral webhook receiver for storyboard runs.\n *\n * The runner binds an HTTP listener, mints per-step URLs under\n * `/step/<step_id>/<operation_id>`, and captures inbound POSTs so\n * `expect_webhook*` pseudo-steps can observe outbound webhook behavior\n * (idempotency_key presence, retry-key stability, 9421 signature validity).\n *\n * Matches the `webhook_receiver_runner` test-kit contract in\n * adcontextprotocol/adcp#2431:\n * - Per-step URL template `{{runner.webhook_base}}/step/<step_id>/<operation_id>`.\n * - Retry-replay: the receiver returns a configurable 5xx for the first N\n * deliveries on a given `(step_id, operation_id)`, then 2xx, so senders\n * are forced to retry and retry-key stability can be graded.\n * - Loopback-mock mode (HTTP on 127.0.0.1, zero external deps) by default;\n * proxy-URL mode swaps in an operator-supplied public base.\n */\n\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport { randomUUID } from 'node:crypto';\nimport type { AddressInfo } from 'node:net';\nimport { getSchemaValidatorByRef } from '../../validation/schema-loader';\n\n/**\n * Size cap on any single webhook body. A non-conformant sender retrying\n * with oversize payloads shouldn't be able to exhaust the runner's memory.\n */\nconst MAX_BODY_BYTES = 1_048_576; // 1 MiB\nconst MAX_CHALLENGE_BODY_BYTES = 16_384; // 16 KiB; challenge envelopes are tiny\n\n/**\n * Caps on cumulative capture state. `MAX_CAPTURED_TOTAL` bounds the total\n * number of webhooks retained for the run; `MAX_CAPTURED_PER_KEY` bounds\n * per-(step_id, operation_id) deliveries — a publisher stuck in a retry\n * loop can't exhaust memory. Once a cap is hit the receiver stops retaining\n * bodies but continues the configured retry-status sequence (or returns 204\n * when no rejection remains), so the cap cannot trap a sender in a retry loop.\n */\nconst MAX_CAPTURED_TOTAL = 1000;\nconst MAX_CAPTURED_PER_KEY = 100;\n\n/**\n * HTTP transport hardening. Defaults in Node 20 are generous (headers\n * 60s, no body timeout). A hostile / slow publisher shouldn't be able to\n * hold the runner's event loop with trickled requests or exhaust the\n * socket table.\n */\nconst HEADERS_TIMEOUT_MS = 10_000;\nconst REQUEST_TIMEOUT_MS = 30_000;\nconst KEEP_ALIVE_TIMEOUT_MS = 5_000;\nconst MAX_CONNECTIONS = 64;\nconst MAX_HEADERS_COUNT = 64;\n\n/** Default path shape under the receiver base. */\nconst STEP_PATH_RE = /^\\/step\\/([A-Za-z0-9_]+)\\/([A-Za-z0-9_-]+)\\/?$/;\n\n/**\n * Header names whose values are redacted before a captured webhook lands\n * in a compliance report. Mirrors the `SECRET_KEY_PATTERN` in runner.ts\n * so both paths honor the contract's \"Secrets SHOULD be redacted\" rule.\n */\nconst SECRET_HEADER_PATTERN =\n /^(authorization|credentials?|token|api[_-]?key|x-api[_-]?key|x-auth[_-]?token|password|secret|client[_-]secret|refresh[_-]token|access[_-]token|bearer|session[_-]token|cookie|set[_-]cookie)$/i;\n\n/**\n * Strip user-info, scheme-policed, and CRLF/NUL-rejected proxy URLs so a\n * misconfigured operator (or a compromised storyboard loader) can't\n * advertise an `http://169.254.169.254`, `file://…`, or header-splitting\n * URL that would then be embedded in outbound `push_notification_config.url`.\n */\nfunction validateProxyUrl(raw: string): string {\n if (/[\\r\\n\\x00]/.test(raw)) {\n throw new Error('webhook_receiver.public_url must not contain CR/LF/NUL');\n }\n let parsed: URL;\n try {\n parsed = new URL(raw);\n } catch {\n throw new Error(`webhook_receiver.public_url is not a valid URL: ${raw}`);\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new Error(`webhook_receiver.public_url must be http(s); got ${parsed.protocol}`);\n }\n if (parsed.username || parsed.password) {\n throw new Error('webhook_receiver.public_url must not include userinfo');\n }\n return raw.replace(/\\/$/, '');\n}\n\nexport interface CapturedWebhook {\n /** Runner-assigned id; stable across filter matches. */\n id: string;\n /** Step id extracted from the URL path. */\n step_id: string;\n /** Operation id extracted from the URL path. */\n operation_id: string;\n /** Delivery sequence number for this (step_id, operation_id) — 1-based. */\n delivery_index: number;\n /** Wall-clock millis at receipt. */\n received_at: number;\n method: string;\n /** Path + query. */\n path: string;\n /** Lowercased header names; duplicate values joined with \", \". */\n headers: Record<string, string>;\n /** Raw request body — needed for RFC 9421 content-digest recompute. */\n raw_body: string;\n /** Parsed JSON body; undefined on parse failure. */\n body?: unknown;\n /** Parse error message when raw_body was non-empty but not valid JSON. */\n parse_error?: string;\n /** HTTP status the receiver returned (reflects retry-replay policy). */\n response_status: number;\n}\n\n/** Proof-of-control challenge retained separately from event deliveries. */\nexport interface CapturedWebhookChallenge {\n id: string;\n step_id: string;\n operation_id: string;\n received_at: number;\n method: string;\n path: string;\n headers: Record<string, string>;\n raw_body: string;\n body: Record<string, unknown>;\n response_status: 200;\n}\n\n/** Match predicate applied when waiting for a webhook. */\nexport interface WebhookFilter {\n /** Restrict matches to this step id's URL. */\n step_id?: string;\n /** Restrict matches to this operation id. */\n operation_id?: string;\n /** Dotted-path → expected-value pairs compared against the parsed body. */\n body?: Record<string, unknown>;\n}\n\nexport type WebhookWaitResult =\n | { webhook: CapturedWebhook; timed_out?: false }\n | { timed_out: true; webhook?: undefined };\n\nexport interface RetryReplayPolicy {\n /** How many deliveries to reject before accepting. */\n count: number;\n /** HTTP status to return on rejection. */\n http_status: number;\n}\n\nexport interface WebhookReceiver {\n /** Public base URL (`http://host:port` for loopback, operator URL for proxy). */\n readonly base_url: string;\n /** Whether the receiver is in loopback-mock or proxy-url mode. */\n readonly mode: 'loopback_mock' | 'proxy_url';\n /** All webhooks captured so far, in arrival order. */\n all(): CapturedWebhook[];\n /** Schema-valid proof-of-control challenges, kept separate from event deliveries. */\n challenges(): CapturedWebhookChallenge[];\n /** Webhooks matching a filter, in arrival order. */\n matching(filter: WebhookFilter): CapturedWebhook[];\n /**\n * Configure the receiver to return `http_status` for the first `count`\n * deliveries at `(step_id, operation_id)` and 2xx afterwards. Idempotent:\n * setting a new policy replaces any existing one for the same key.\n */\n set_retry_replay(key: { step_id: string; operation_id: string }, policy: RetryReplayPolicy): void;\n /** Resolve with the first match within `timeout_ms`, or `timed_out: true`. */\n wait(filter: WebhookFilter, timeout_ms: number): Promise<WebhookWaitResult>;\n /**\n * Resolve with every match observed between call time and deadline. Used\n * by `expect_webhook_retry_keys_stable` which needs the full sequence of\n * deliveries for the retry-replay assertion.\n */\n wait_all(filter: WebhookFilter, timeout_ms: number): Promise<CapturedWebhook[]>;\n /** Stop the listener. Idempotent. */\n close(): Promise<void>;\n}\n\nexport interface CreateWebhookReceiverOptions {\n mode?: 'loopback_mock' | 'proxy_url';\n /** Bind host for the local listener. Defaults to `127.0.0.1`. */\n host?: string;\n /** Bind port. `0` (default) lets the kernel assign one. */\n port?: number;\n /** Public URL to advertise when `mode: 'proxy_url'`. */\n public_url?: string;\n}\n\ninterface RetryKey {\n step_id: string;\n operation_id: string;\n}\n\nfunction retryKeyString(k: RetryKey): string {\n return `${k.step_id}::${k.operation_id}`;\n}\n\nexport async function createWebhookReceiver(options: CreateWebhookReceiverOptions = {}): Promise<WebhookReceiver> {\n const mode = options.mode ?? 'loopback_mock';\n if (mode === 'proxy_url' && !options.public_url) {\n throw new Error('webhook_receiver.mode=proxy_url requires `public_url`');\n }\n\n const host = options.host ?? '127.0.0.1';\n // A loopback_mock receiver bound on 0.0.0.0 is publicly reachable from any\n // interface — which would turn a CI runner into an open POST endpoint.\n // Operators who genuinely want public exposure use mode=proxy_url.\n if (mode === 'loopback_mock' && (host === '0.0.0.0' || host === '::')) {\n throw new Error(\n `webhook_receiver host ${host} is not permitted in loopback_mock mode. ` +\n 'Use mode=proxy_url with an explicit public_url for publicly-reachable runs.'\n );\n }\n const port = options.port ?? 0;\n const proxyBase = mode === 'proxy_url' ? validateProxyUrl(options.public_url!) : undefined;\n\n const captured: CapturedWebhook[] = [];\n const challenges: CapturedWebhookChallenge[] = [];\n const waiters: Array<{\n filter: WebhookFilter;\n resolve: (result: WebhookWaitResult) => void;\n timer: NodeJS.Timeout;\n }> = [];\n const waitAllTimers: Array<{\n filter: WebhookFilter;\n resolve: (result: CapturedWebhook[]) => void;\n timer: NodeJS.Timeout;\n }> = [];\n const retryPolicies = new Map<string, { policy: RetryReplayPolicy; delivered: number }>();\n const deliveryCounts = new Map<string, number>();\n const challengeCounts = new Map<string, number>();\n let closed = false;\n\n const server = createServer((req, res) =>\n handleRequest(req, res, { captured, challenges, waiters, retryPolicies, deliveryCounts, challengeCounts })\n );\n // Transport-level hardening — trim Node's generous defaults so a slow /\n // hostile publisher can't wedge the runner.\n server.headersTimeout = HEADERS_TIMEOUT_MS;\n server.requestTimeout = REQUEST_TIMEOUT_MS;\n server.keepAliveTimeout = KEEP_ALIVE_TIMEOUT_MS;\n server.maxConnections = MAX_CONNECTIONS;\n server.maxHeadersCount = MAX_HEADERS_COUNT;\n\n await new Promise<void>((resolve, reject) => {\n const onError = (err: Error) => reject(err);\n server.once('error', onError);\n server.listen(port, host, () => {\n server.off('error', onError);\n resolve();\n });\n });\n\n const bound = server.address() as AddressInfo;\n const base_url = proxyBase ?? `http://${formatHost(bound.address)}:${bound.port}`;\n\n return {\n base_url,\n mode,\n all: () => captured.slice(),\n challenges: () => challenges.slice(),\n matching: filter => captured.filter(w => matchesFilter(w, filter)),\n set_retry_replay: (key, policy) => {\n retryPolicies.set(retryKeyString(key), { policy, delivered: 0 });\n },\n wait: (filter, timeout_ms) => {\n if (!closed) return wait(filter, timeout_ms, captured, waiters);\n const already = captured.find(w => matchesFilter(w, filter));\n return Promise.resolve(already ? { webhook: already } : { timed_out: true });\n },\n wait_all: (filter, timeout_ms) =>\n closed\n ? Promise.resolve(captured.filter(w => matchesFilter(w, filter)))\n : waitAll(filter, timeout_ms, captured, waitAllTimers),\n close: () => {\n closed = true;\n return closeServer(server, captured, waiters, waitAllTimers);\n },\n };\n}\n\n// ────────────────────────────────────────────────────────────\n// Request handling\n// ────────────────────────────────────────────────────────────\n\ninterface HandlerState {\n captured: CapturedWebhook[];\n challenges: CapturedWebhookChallenge[];\n waiters: Array<{\n filter: WebhookFilter;\n resolve: (r: WebhookWaitResult) => void;\n timer: NodeJS.Timeout;\n }>;\n retryPolicies: Map<string, { policy: RetryReplayPolicy; delivered: number }>;\n deliveryCounts: Map<string, number>;\n challengeCounts: Map<string, number>;\n}\n\nfunction handleRequest(req: IncomingMessage, res: ServerResponse, state: HandlerState): void {\n if (req.method !== 'POST') {\n res.statusCode = 405;\n res.end();\n return;\n }\n\n const pathParts = parseStepPath(req.url ?? '');\n if (!pathParts) {\n res.statusCode = 404;\n res.end();\n return;\n }\n const { step_id, operation_id } = pathParts;\n\n let size = 0;\n const chunks: Buffer[] = [];\n let tooLarge = false;\n\n req.on('data', (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_BODY_BYTES) {\n tooLarge = true;\n req.destroy();\n return;\n }\n chunks.push(chunk);\n });\n\n req.on('end', () => {\n if (tooLarge) {\n if (!res.headersSent) {\n res.statusCode = 413;\n res.end();\n }\n return;\n }\n const raw = Buffer.concat(chunks).toString('utf8');\n const headers = normalizeHeaders(req.headers);\n const parsedBody = parseBody(raw, headers['content-type']);\n\n // Notification-config proof is POSTed to the configured webhook URL\n // itself. Validate the complete protocol envelope, echo only the\n // challenge field, and retain the request separately so conformance\n // checks can inspect its RFC 9421 headers without treating it as an\n // event delivery.\n if (isWebhookChallenge(parsedBody.body)) {\n if (Buffer.byteLength(raw, 'utf8') > MAX_CHALLENGE_BODY_BYTES) {\n res.statusCode = 413;\n res.end();\n return;\n }\n const challenge = parsedBody.body.challenge;\n const schemaRef =\n parsedBody.body.scope === 'agent' ? 'core/agent-webhook-challenge.json' : 'core/webhook-challenge.json';\n const validate = getSchemaValidatorByRef(schemaRef);\n if (!validate || !validate(parsedBody.body) || typeof challenge !== 'string') {\n res.statusCode = 400;\n res.end();\n return;\n }\n const key = retryKeyString({ step_id, operation_id });\n const perKey = state.challengeCounts.get(key) ?? 0;\n if (state.captured.length + state.challenges.length >= MAX_CAPTURED_TOTAL || perKey >= MAX_CAPTURED_PER_KEY) {\n res.statusCode = 429;\n res.setHeader('retry-after', '1');\n res.end();\n return;\n }\n state.challengeCounts.set(key, perKey + 1);\n state.challenges.push({\n id: randomUUID(),\n step_id,\n operation_id,\n received_at: Date.now(),\n method: req.method ?? 'POST',\n path: req.url ?? '/',\n headers: redactHeaders(headers),\n raw_body: raw,\n body: parsedBody.body as Record<string, unknown>,\n response_status: 200,\n });\n res.statusCode = 200;\n res.setHeader('content-type', 'application/json');\n res.end(JSON.stringify({ challenge }));\n return;\n }\n\n const key = retryKeyString({ step_id, operation_id });\n const deliveryIndex = (state.deliveryCounts.get(key) ?? 0) + 1;\n const status = nextResponseStatus(state.retryPolicies, key);\n\n // Stop retaining bodies once a capture cap is exhausted, but preserve the\n // configured retry-replay status sequence. Unconditionally returning 503\n // here masks the policy's eventual 2xx forever and can amplify a sender's\n // retries after the runner has already reached its memory bound (#2653).\n const perKey = state.deliveryCounts.get(key) ?? 0;\n if (state.captured.length + state.challenges.length >= MAX_CAPTURED_TOTAL || perKey >= MAX_CAPTURED_PER_KEY) {\n res.statusCode = status;\n if (status === 429 || status >= 500) res.setHeader('retry-after', '1');\n res.end();\n return;\n }\n\n state.deliveryCounts.set(key, deliveryIndex);\n\n const webhook: CapturedWebhook = {\n id: randomUUID(),\n step_id,\n operation_id,\n delivery_index: deliveryIndex,\n received_at: Date.now(),\n method: req.method ?? 'POST',\n path: req.url ?? '/',\n headers: redactHeaders(headers),\n raw_body: raw,\n ...parsedBody,\n response_status: status,\n };\n state.captured.push(webhook);\n\n // Deliver to any matching waiters. First-match-wins per waiter; later\n // arrivals may still deliver to other waiters in the queue.\n for (let i = state.waiters.length - 1; i >= 0; i--) {\n const waiter = state.waiters[i]!;\n if (matchesFilter(webhook, waiter.filter)) {\n clearTimeout(waiter.timer);\n state.waiters.splice(i, 1);\n waiter.resolve({ webhook });\n }\n }\n\n res.statusCode = status;\n res.end();\n });\n\n req.on('error', () => {\n if (!res.headersSent) {\n res.statusCode = 400;\n res.end();\n }\n });\n}\n\nfunction isWebhookChallenge(\n body: unknown\n): body is { type: 'webhook.challenge'; challenge: unknown } & Record<string, unknown> {\n return (\n body !== null &&\n typeof body === 'object' &&\n !Array.isArray(body) &&\n (body as Record<string, unknown>).type === 'webhook.challenge'\n );\n}\n\nfunction requestPathname(reqUrl: string): string {\n const q = reqUrl.indexOf('?');\n return q === -1 ? reqUrl : reqUrl.slice(0, q);\n}\n\nfunction parseStepPath(reqUrl: string): { step_id: string; operation_id: string } | undefined {\n const match = STEP_PATH_RE.exec(requestPathname(reqUrl));\n if (!match) return undefined;\n return { step_id: match[1]!, operation_id: match[2]! };\n}\n\nfunction normalizeHeaders(raw: IncomingMessage['headers']): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(raw)) {\n if (value === undefined) continue;\n out[key.toLowerCase()] = Array.isArray(value) ? value.join(', ') : value;\n }\n return out;\n}\n\nfunction redactHeaders(headers: Record<string, string>): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [k, v] of Object.entries(headers)) {\n out[k] = SECRET_HEADER_PATTERN.test(k) ? '[redacted]' : v;\n }\n return out;\n}\n\nfunction parseBody(raw: string, contentType: string | undefined): Pick<CapturedWebhook, 'body' | 'parse_error'> {\n if (raw.length === 0) return {};\n const looksJson = !contentType || /json/i.test(contentType);\n if (!looksJson) return {};\n try {\n return { body: JSON.parse(raw) };\n } catch (err) {\n return { parse_error: err instanceof Error ? err.message : String(err) };\n }\n}\n\n// ────────────────────────────────────────────────────────────\n// Filter matching\n// ────────────────────────────────────────────────────────────\n\nfunction matchesFilter(webhook: CapturedWebhook, filter: WebhookFilter): boolean {\n if (filter.step_id && webhook.step_id !== filter.step_id) return false;\n if (filter.operation_id && webhook.operation_id !== filter.operation_id) return false;\n if (filter.body && Object.keys(filter.body).length > 0) {\n if (webhook.body === undefined) return false;\n for (const [path, expected] of Object.entries(filter.body)) {\n const actual = resolveDottedPath(webhook.body, path);\n if (!deepEqual(actual, expected)) return false;\n }\n }\n return true;\n}\n\nfunction resolveDottedPath(obj: unknown, path: string): unknown {\n if (obj === null || obj === undefined) return undefined;\n const segments = path.split('.');\n let cursor: unknown = obj;\n for (const seg of segments) {\n if (cursor === null || cursor === undefined) return undefined;\n if (typeof cursor !== 'object') return undefined;\n cursor = (cursor as Record<string, unknown>)[seg];\n }\n return cursor;\n}\n\n/**\n * Structural deep-equal. Stringify-based comparison is key-order-sensitive\n * (`{a:1,b:2}` !== `{b:2,a:1}`); for filter bodies that's a false negative\n * when a publisher's JSON serializer emits fields in a different order\n * than the storyboard author. Recurse explicitly instead.\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a !== typeof b) return false;\n if (a === null || b === null) return a === b;\n if (typeof a !== 'object') return false;\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b)) return false;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!deepEqual(a[i], b[i])) return false;\n }\n return true;\n }\n const keysA = Object.keys(a as Record<string, unknown>);\n const keysB = Object.keys(b as Record<string, unknown>);\n if (keysA.length !== keysB.length) return false;\n for (const k of keysA) {\n if (!Object.prototype.hasOwnProperty.call(b, k)) return false;\n if (!deepEqual((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k])) return false;\n }\n return true;\n}\n\n// ────────────────────────────────────────────────────────────\n// Retry-replay policy\n// ────────────────────────────────────────────────────────────\n\nfunction nextResponseStatus(\n retryPolicies: Map<string, { policy: RetryReplayPolicy; delivered: number }>,\n key: string\n): number {\n const entry = retryPolicies.get(key);\n if (!entry) return 204;\n if (entry.delivered < entry.policy.count) {\n entry.delivered += 1;\n return entry.policy.http_status;\n }\n return 204;\n}\n\n// ────────────────────────────────────────────────────────────\n// wait / wait_all\n// ────────────────────────────────────────────────────────────\n\nfunction wait(\n filter: WebhookFilter,\n timeout_ms: number,\n captured: CapturedWebhook[],\n waiters: Array<{ filter: WebhookFilter; resolve: (r: WebhookWaitResult) => void; timer: NodeJS.Timeout }>\n): Promise<WebhookWaitResult> {\n const already = captured.find(w => matchesFilter(w, filter));\n if (already) return Promise.resolve({ webhook: already });\n return new Promise<WebhookWaitResult>(resolve => {\n const timer = setTimeout(\n () => {\n const idx = waiters.findIndex(w => w.timer === timer);\n if (idx >= 0) waiters.splice(idx, 1);\n resolve({ timed_out: true });\n },\n Math.max(0, timeout_ms)\n );\n timer.unref?.();\n waiters.push({ filter, resolve, timer });\n });\n}\n\n/**\n * Poll until the deadline, then return every capture matching `filter`.\n * Used when the assertion needs the whole delivery sequence (retry-stability).\n * Waits a minimum of `timeout_ms` even if matches arrive earlier — callers\n * that need \"resolve on first match\" should use `wait` instead.\n */\nfunction waitAll(\n filter: WebhookFilter,\n timeout_ms: number,\n captured: CapturedWebhook[],\n waitAllTimers: Array<{\n filter: WebhookFilter;\n resolve: (result: CapturedWebhook[]) => void;\n timer: NodeJS.Timeout;\n }>\n): Promise<CapturedWebhook[]> {\n return new Promise<CapturedWebhook[]>(resolve => {\n const timer = setTimeout(\n () => {\n const idx = waitAllTimers.findIndex(entry => entry.timer === timer);\n if (idx >= 0) waitAllTimers.splice(idx, 1);\n resolve(captured.filter(w => matchesFilter(w, filter)));\n },\n Math.max(0, timeout_ms)\n );\n timer.unref?.();\n waitAllTimers.push({ filter, resolve, timer });\n });\n}\n\n// ────────────────────────────────────────────────────────────\n// close\n// ────────────────────────────────────────────────────────────\n\nfunction closeServer(\n server: Server,\n captured: CapturedWebhook[],\n waiters: Array<{ filter: WebhookFilter; resolve: (r: WebhookWaitResult) => void; timer: NodeJS.Timeout }>,\n waitAllTimers: Array<{\n filter: WebhookFilter;\n resolve: (result: CapturedWebhook[]) => void;\n timer: NodeJS.Timeout;\n }>\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n server.close(err => {\n if (err && (err as NodeJS.ErrnoException).code !== 'ERR_SERVER_NOT_RUNNING') reject(err);\n else resolve();\n });\n // Stop accepting new sockets before force-closing existing keep-alive\n // connections; the opposite order leaves a small acceptance race.\n server.closeAllConnections?.();\n while (waiters.length > 0) {\n const w = waiters.pop()!;\n clearTimeout(w.timer);\n w.resolve({ timed_out: true });\n }\n while (waitAllTimers.length > 0) {\n const pending = waitAllTimers.pop()!;\n clearTimeout(pending.timer);\n pending.resolve(captured.filter(w => matchesFilter(w, pending.filter)));\n }\n });\n}\n\nfunction formatHost(address: string): string {\n return address.includes(':') ? `[${address}]` : address;\n}\n"],"mappings":"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","names":["key","perKey"]}
|
|
@@ -187,6 +187,8 @@ export interface TestOptions {
|
|
|
187
187
|
/**
|
|
188
188
|
* Fictional-entity test-kit data loaded from `test-kits/<name>.yaml`.
|
|
189
189
|
* Storyboard phases may skip based on fields here (e.g. `skip_if: "!test_kit.auth.api_key"`).
|
|
190
|
+
* Guards are evaluated together with the accumulated storyboard `context.*`
|
|
191
|
+
* at each phase boundary.
|
|
190
192
|
*/
|
|
191
193
|
test_kit?: {
|
|
192
194
|
auth?: {
|
|
@@ -187,6 +187,8 @@ export interface TestOptions {
|
|
|
187
187
|
/**
|
|
188
188
|
* Fictional-entity test-kit data loaded from `test-kits/<name>.yaml`.
|
|
189
189
|
* Storyboard phases may skip based on fields here (e.g. `skip_if: "!test_kit.auth.api_key"`).
|
|
190
|
+
* Guards are evaluated together with the accumulated storyboard `context.*`
|
|
191
|
+
* at each phase boundary.
|
|
190
192
|
*/
|
|
191
193
|
test_kit?: {
|
|
192
194
|
auth?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/testing/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/testing/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/testing/types.ts"],"sourcesContent":["/**\n * Types for AdCP Agent E2E Testing\n */\n\nimport type { FormatReferenceStructuredObject as FormatID } from '../types/core.generated';\nimport type { ControllerDetection } from './test-controller';\nimport type { WebhookReceiver } from './storyboard/webhook-receiver';\nimport type { AdcpVersion } from '../version';\nimport type { TransportOptions, VersionEnvelopeMode } from '../protocols';\nimport type { AgentRequestSigningConfig } from '../types/adcp';\n\n// Test scenarios that can be run\nexport type TestScenario =\n | 'health_check' // Just check if agent responds\n | 'discovery' // get_products, list_creative_formats, list_authorized_properties\n | 'create_media_buy' // Discovery + create a test media buy\n | 'full_sales_flow' // Full lifecycle: discovery -> create -> update -> delivery\n | 'reporting_flow' // Dedicated: create media buy -> get_media_buy_delivery validation\n | 'creative_sync' // Test sync_creatives flow\n | 'creative_inline' // Test inline creatives in create_media_buy\n | 'creative_reference' // Build -> sync -> reference a creative via creative_ids\n | 'pricing_models' // Test different pricing models the agent supports\n | 'creative_flow' // Creative agent: list_formats -> build -> preview\n | 'creative_lifecycle' // Creative agent: formats -> sync multiple -> list with/without snapshot -> build/preview\n | 'signals_flow' // Signals agent: get_signals -> activate\n // Edge case testing scenarios\n | 'error_handling' // Test agent returns proper error responses\n | 'validation' // Test schema validation (invalid inputs should be rejected)\n | 'pricing_edge_cases' // Test auction vs fixed pricing, min spend, bid_price requirements\n | 'temporal_validation' // Test date/time ordering and format validation\n // Behavioral analysis scenarios\n | 'behavior_analysis' // Analyze agent behavior: auth requirements, brief relevance, filtering\n // Response consistency scenarios\n | 'response_consistency' // Check for schema errors, pagination bugs, data mismatches\n // v3 Governance protocol scenarios\n | 'governance_property_lists' // Property list CRUD operations\n | 'governance_content_standards' // Content standards lifecycle\n | 'property_list_filters' // Property list filter round-trip: GARM, MFA, custom_tags, feature_requirements\n // v3 Campaign governance scenarios\n | 'campaign_governance' // Full lifecycle: sync_plans -> check -> execute -> report outcome\n | 'campaign_governance_denied' // Denied flow: over-budget, unauthorized market\n | 'campaign_governance_conditions' // Conditions flow: apply conditions -> re-check\n | 'campaign_governance_delivery' // Delivery monitoring with drift detection\n | 'seller_governance_context' // Seller persistence of governance_context\n // v3 SI (Sponsored Intelligence) protocol scenarios\n | 'si_session_lifecycle' // Full SI session: initiate -> messages -> terminate\n | 'si_availability' // Check SI offering availability\n | 'si_handoff' // ACP handoff flow: initiate -> purchase intent -> terminate with handoff_transaction\n // v3 Capability discovery\n | 'capability_discovery' // Verify get_adcp_capabilities response\n // Schema compliance\n | 'schema_compliance' // Validate v3 channel enum, pricing field names, format assets structure\n // Audience management\n | 'sync_audiences' // Test CRM audience sync flow\n // State machine compliance\n | 'media_buy_lifecycle' // Pause -> resume -> cancel state transitions\n | 'terminal_state_enforcement' // Verify agents reject updates to terminal-state media buys\n | 'package_lifecycle' // Package-level pause/resume independent of media buy status\n // Error compliance (transport error mapping spec)\n | 'error_codes' // Validate standard AdCP error codes in responses\n | 'error_structure' // Validate error JSON structure against error.json schema\n | 'error_transport' // Validate transport binding (structuredContent, text fallback)\n // Deterministic state machine scenarios (require comply_test_controller)\n | 'deterministic_creative' // Force creative status transitions via test controller\n | 'deterministic_media_buy' // Force media buy status transitions via test controller\n | 'deterministic_account' // Force account status transitions + operation gates\n | 'deterministic_session' // Force SI session timeout/termination\n | 'deterministic_delivery' // Simulate delivery data and verify reporting\n | 'deterministic_budget' // Simulate budget spend and verify financials\n | 'controller_validation' // Validate the test controller itself (error codes, edge cases)\n // Brand rights protocol scenarios\n | 'brand_identity' // Brand identity discovery (public and authorized access tiers)\n | 'brand_rights_flow' // Brand rights: get_rights -> acquire_rights lifecycle\n | 'creative_approval'; // Creative approval workflow for brand compliance\n\nexport interface TestOptions {\n // Protocol to use for testing (default: 'mcp')\n protocol?: 'mcp' | 'a2a';\n /** Optional cancellation signal for discovery and storyboard helper calls. */\n signal?: AbortSignal;\n /**\n * Runtime-only transport configuration for discovery and every storyboard\n * or compliance tool call. In particular, `fetchFn` lets hosted runners\n * enforce a request-scoped network policy without replacing global fetch.\n */\n transport?: TransportOptions;\n /**\n * RFC 9421 identity used for ordinary functional test/storyboard calls.\n * Distinct from `StoryboardRunOptions.request_signing`, which configures\n * the dedicated verifier-vector grader. Compliance runners populate this\n * automatically from `signed-requests-runner.yaml > functional_dispatch`\n * only for sandbox runs; callers may provide their own key/provider for a\n * different trusted environment.\n */\n functional_request_signing?: AgentRequestSigningConfig;\n /**\n * AdCP protocol version the test client should speak. Storyboard runners\n * set this from the compliance cache version instead of relying on the\n * installed package default.\n */\n adcpVersion?: AdcpVersion | (string & {});\n /**\n * Route compact media-buy storyboard steps through the SDK compatibility\n * coordinator. This lets the same compact-first storyboard exercise native\n * 3.2 sellers and established v2.5–3.2 sellers without task-name branching.\n * Mutating guarantee losses remain fail-closed unless named here.\n */\n mediaBuyLifecycleCompatibility?: import('../media-buy/compatibility').MediaBuyLifecycleCoordinatorOptions;\n /**\n * Optional wire-only AdCP version envelope override. Validation and schema\n * selection continue to use `adcpVersion`; request envelopes use this value.\n * Intended for hosted stable-line badges backed by prerelease caches.\n */\n wireAdcpVersion?: AdcpVersion | (string & {});\n /**\n * Version-envelope emission mode. Defaults to `auto`; 3.0 storyboards emit\n * the legacy major marker only, while 3.1 storyboards also emit the exact\n * `adcp_version` marker. Compliance discovery may negotiate `major-only`\n * for strict pre-3.1 agents.\n */\n versionEnvelope?: VersionEnvelopeMode;\n /**\n * External schema bundle root used for request/response validation during\n * storyboard and compliance runs. The root is registered against\n * `adcpVersion` (or the storyboard/compliance version when omitted) and is\n * authoritative over the SDK's generated Zod snapshot for the run.\n */\n schemaRoot?: string;\n /**\n * Whether strict AJV response-schema failures contribute to storyboard\n * grading. Defaults to `true`, matching the hosted compliance grader.\n *\n * Set to `false` only for packaged-schema diagnostic migrations that need\n * the historical lenient-Zod grade while inspecting\n * `strict_validation_summary` and the per-validation `strict` verdicts.\n * Strict validation still runs in that mode; it is informational rather\n * than grading. An explicit external `schemaRoot` remains authoritative.\n */\n strictResponseSchemaValidation?: boolean;\n /** Custom User-Agent string sent with all outbound requests */\n userAgent?: string;\n // Brand reference for product discovery (preferred over brand_manifest)\n brand?: { domain: string; brand_id?: string };\n // Custom brief for product discovery\n brief?: string;\n // Budget for test media buy (default: 1000)\n budget?: number;\n // Specific format IDs to test\n format_ids?: string[];\n // Test session ID for isolation\n test_session_id?: string;\n // Channels to focus on (if not specified, tests all agent supports)\n channels?: string[];\n // Specific pricing models to test\n pricing_models?: string[];\n /**\n * Authentication for agents that require it.\n *\n * - `bearer`: raw token sent as `Authorization: Bearer <token>`.\n * - `basic`: cleartext `username` and `password`, encoded internally.\n * - `oauth`: saved OAuth tokens (access_token + refresh_token). MCP only.\n * The library auto-refreshes on 401. Obtain tokens interactively via\n * `adcp --save-auth <alias> --oauth`, then pass the saved blob here for\n * non-interactive reuse.\n * - `oauth_client_credentials`: RFC 6749 §4.4 machine-to-machine flow.\n * The library exchanges the secret for a fresh access token before each\n * call (cached while valid). Supply `tokens` to seed the cache; omit to\n * exchange on first call.\n */\n auth?:\n | { type: 'bearer'; token: string }\n | { type: 'basic'; username: string; password: string }\n | {\n type: 'oauth';\n tokens: import('../types/adcp').AgentOAuthTokens;\n client?: import('../types/adcp').AgentOAuthClient;\n }\n | {\n type: 'oauth_client_credentials';\n credentials: import('../types/adcp').AgentOAuthClientCredentials;\n tokens?: import('../types/adcp').AgentOAuthTokens;\n };\n /**\n * Extra HTTP headers applied to every outbound request to the agent.\n * Forwarded into `AgentConfig.headers`, so MCP and A2A transports both\n * see them. `Authorization` and `x-adcp-auth` are reserved — auth wins.\n *\n * Typical use: tenant-routing headers (`x-adcp-tenant`, `Apx-Incoming-Host`)\n * for multi-tenant agents fronted by a reverse proxy. The CLI exposes these\n * via `-H KEY=VALUE` (repeatable) and persists them in `~/.adcp/config.json`.\n */\n headers?: Record<string, string>;\n // Brand manifest for creative testing\n brand_manifest?: {\n name: string;\n url?: string;\n tagline?: string;\n logos?: Array<{\n url: string;\n orientation?: 'square' | 'horizontal' | 'vertical' | 'stacked';\n background?: 'dark-bg' | 'light-bg' | 'transparent-bg';\n variant?: 'primary' | 'secondary' | 'icon' | 'wordmark' | 'full-lockup';\n tags?: string[];\n usage?: string;\n width?: number;\n height?: number;\n }>;\n colors?: Record<string, string>;\n tone?: {\n voice?: string;\n attributes?: string[];\n dos?: string[];\n donts?: string[];\n };\n assets?: Array<{\n asset_id: string;\n asset_type: string;\n url: string;\n width?: number;\n height?: number;\n tags?: string[];\n }>;\n };\n // For creative testing: test multiple formats programmatically\n test_all_formats?: boolean;\n // For creative testing: max formats to test when test_all_formats is true\n max_formats_to_test?: number;\n // For signals testing: specific signal types to test\n signal_types?: string[];\n // For governance testing: name for test property list\n property_list_name?: string;\n // For governance testing: specific content standards to test\n content_standards_id?: string;\n // For SI testing: specific offering ID to test\n si_offering_id?: string;\n // For SI testing: initial conversation context\n si_context?: string;\n /**\n * Shared explicit account id for test scenarios that need an account-scoped\n * request. Domain-specific account ids below take precedence when provided.\n */\n account_id?: string;\n /**\n * For media-buy testing: account ID to use with get_products,\n * create_media_buy, sync_creatives, and related media-buy flows.\n */\n media_buy_account_id?: string;\n // For audience testing: account ID to use with sync_audiences\n audience_account_id?: string;\n // When true, use sandbox mode. For implicit accounts, uses the natural key with\n // sandbox: true. For explicit accounts, discovers sandbox accounts via list_accounts.\n sandbox?: boolean;\n /**\n * When true, the runner injects `ext.adcp.disable_sandbox: true` on every\n * outgoing request, signaling the agent under test to bypass any internal\n * sandbox routing (env-var fallbacks, brand-domain heuristics, fixture\n * substitutes) and exercise its real adapter path. Distinct from `sandbox`\n * (which sets `account.sandbox: false` — a value, not a routing hint).\n *\n * Honored by adopters who explicitly read `ext.adcp.disable_sandbox`.\n * Agents that don't recognize the field ignore it (per spec, `ext` is\n * accepted-without-error). The pair (sandbox=false + disable_sandbox=true)\n * is the strongest \"production path only\" signal the runner can send;\n * `--no-sandbox` on `adcp storyboard run` sets both. Issue #841.\n */\n disable_sandbox?: boolean;\n /**\n * Fictional-entity test-kit data loaded from `test-kits/<name>.yaml`.\n * Storyboard phases may skip based on fields here (e.g. `skip_if: \"!test_kit.auth.api_key\"`).\n */\n test_kit?: {\n auth?: {\n /** API key the runner presents on API-key probes. */\n api_key?: string;\n /** HTTP Basic credential the runner presents on Basic-auth probes. */\n basic?: {\n /** Username portion of the Basic credential. */\n username?: string;\n /** Password portion of the Basic credential. */\n password?: string;\n /** Unencoded `username:password` pair. */\n credentials?: string;\n };\n /**\n * Auth-required, read-only tool the runner uses for unauth + invalid-key probes.\n * Required whenever `auth` is declared — no default is substituted. Must be one of\n * the values in `PROBE_TASK_ALLOWLIST`. Kits that miss this or pick a task outside\n * the allowlist fail at `comply()` / `runStoryboard()` entry with\n * `TestKitValidationError`.\n */\n probe_task: string;\n };\n [key: string]: unknown;\n };\n /** @internal Pre-created client from comply() — avoids per-scenario MCP reconnection */\n _client?: unknown;\n /**\n * @internal Pre-discovered profile from comply() — skips per-scenario discovery.\n * @deprecated Use `StoryboardRunOptions.profile` for storyboard profile reuse.\n */\n _profile?: AgentProfile;\n /**\n * @internal Server-declared AdCP version learned during capability discovery.\n * Storyboard response validation uses this for version-skew compatibility\n * without forcing the transport client to pin a schema bundle that may not\n * ship with the installed SDK.\n */\n _serverAdcpVersion?: string;\n /** @internal Test controller capabilities from comply() — set when comply_test_controller detected */\n _controllerCapabilities?: ControllerDetection;\n /** @internal Pre-created webhook receiver, used for unit-test injection. When\n * present, the runner skips creating its own listener and does NOT close\n * the receiver after the run (caller-owned). Production callers pass\n * `webhook_receiver` instead. */\n _webhookReceiver?: WebhookReceiver;\n}\n\nexport interface TestStepResult {\n step: string;\n task?: string;\n passed: boolean;\n duration_ms: number;\n details?: string;\n error?: string;\n response_preview?: string;\n // Structured data for collectObservations() — decoupled from response_preview display\n observation_data?: Record<string, unknown>;\n // For tracking what was created (for cleanup or follow-up)\n created_id?: string;\n // Deprecation or other warnings\n warnings?: string[];\n /** True when the step was skipped (including re-graded branch-set peers). */\n skipped?: boolean;\n /** Canonical skip reason when `skipped` is true; maps to `RunnerSkipReason`. */\n skip_reason?: string;\n /**\n * Selection reason when a skipped-looking step was intentionally outside\n * the caller's requested run. Summary artifacts use this to keep\n * not-selected steps out of actionable skip-cause rollups.\n */\n selection_reason?: string;\n /**\n * Names the unmet runtime requirement when `skip_reason` is\n * `'requirement_unmet'` (per adcp-client#1626). Carries the same value\n * the storyboard authored in `Storyboard.requires`. Lets skip-cause\n * aggregators sub-group not-applicable scenarios per-requirement\n * without parsing the warning text. Absent for every other skip reason.\n */\n requirement?: string;\n}\n\nexport interface AgentProfile {\n name: string;\n tools: string[];\n channels?: string[];\n pricing_models?: string[];\n format_ids?: string[];\n delivery_types?: string[];\n // For creative agents\n supported_formats?: Array<{\n format_id: FormatID;\n name?: string;\n required_assets?: string[];\n optional_assets?: string[];\n }>;\n // For signals agents\n supported_signals?: Array<{\n signal_id: string;\n name?: string;\n type?: string;\n }>;\n // v3 capabilities\n adcp_version?: 'v2' | 'v3';\n /**\n * AdCP major versions the agent declared in `get_adcp_capabilities.adcp.major_versions`.\n * Drives version-gated storyboard filtering so a v3.0 agent isn't failed against a\n * storyboard introduced in a later minor version.\n */\n adcp_major_versions?: number[];\n /**\n * Exact/release-precision versions from\n * `get_adcp_capabilities.adcp.supported_versions`.\n */\n adcp_supported_versions?: string[];\n /**\n * Seller's full AdCP build version from\n * `get_adcp_capabilities.adcp.build_version`.\n */\n adcp_build_version?: string;\n supported_protocols?: string[];\n /** Specialism claims from get_adcp_capabilities.specialisms */\n specialisms?: string[];\n supports_governance?: boolean;\n supports_si?: boolean;\n /**\n * Populated when the agent advertises `get_adcp_capabilities` but the call failed\n * or returned no data. Signals the compliance runner that the universal-only\n * result is due to a broken caps probe, not an agent that lacks v3 support.\n */\n capabilities_probe_error?: string;\n /**\n * Schema violations found during the preflight `get_adcp_capabilities`\n * call. Kept separately from `capabilities_probe_error`: an invalid\n * response is still available for best-effort storyboard selection, while\n * the runner surfaces each violation as a structured notice.\n */\n capabilities_schema_issues?: Array<{\n /** RFC 6901 pointer into the capabilities response. */\n pointer: string;\n /** Validator-authored explanation of the violation. */\n message: string;\n }>;\n /**\n * Raw `get_adcp_capabilities` response body. Used by the storyboard runner to\n * evaluate `requires_capability` predicates (e.g. `adcp.idempotency.supported`)\n * that reference fields not extracted into the normalised profile shape above.\n */\n raw_capabilities?: unknown;\n /**\n * SDK version string the agent self-reported in `get_adcp_capabilities`, e.g.\n * `\"@adcp/client@5.14.0\"`. Populated opportunistically — absent for hand-rolled\n * agents that don't emit the field. Used by the storyboard runner to suffix\n * shape-drift hints when the reported version predates the recommended helper.\n */\n library_version?: string;\n}\n\nexport interface TestResult {\n agent_url: string;\n scenario: TestScenario;\n overall_passed: boolean;\n steps?: TestStepResult[];\n summary: string;\n total_duration_ms: number;\n tested_at: string;\n // Agent profile discovered during testing\n agent_profile?: AgentProfile;\n}\n\nexport interface SuiteResult {\n agent_url: string;\n agent_profile: AgentProfile;\n /** Scenarios that were run */\n scenarios_run: TestScenario[];\n /** Scenarios skipped because the agent does not advertise the required tools */\n scenarios_skipped: TestScenario[];\n results: TestResult[];\n /**\n * True only when at least one scenario ran and none failed.\n * False for both \"all failed\" and \"no applicable scenarios found\".\n */\n overall_passed: boolean;\n passed_count: number;\n failed_count: number;\n /** Wall-clock time including capability discovery and all scenario runs */\n total_duration_ms: number;\n tested_at: string;\n}\n\n// Generic task result from executeTask\nexport interface TaskResult {\n success: boolean;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- response shape varies by tool; typed per-access in scenarios\n data?: any;\n error?: string;\n /** Structured AdCP error forwarded from the transport layer. Mirrors `TaskResultFailure.adcpError`. */\n adcp_error?: import('../core/ConversationTypes').AdcpErrorInfo;\n /**\n * Internal: which MCP extraction path produced `data`. Set by the response\n * unwrapper and the raw MCP probe so the storyboard runner can surface it\n * in its output contract. Consumers outside the runner should treat this\n * as implementation detail — it's NOT part of the public `AdCPResponse`.\n */\n _extraction_path?: 'structured_content' | 'text_fallback' | 'error' | 'none';\n}\n\n// Logger interface for library use\nexport interface Logger {\n info: (context: object, message: string) => void;\n error: (context: object, message: string) => void;\n warn: (context: object, message: string) => void;\n debug: (context: object, message: string) => void;\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/testing/types.ts"],"sourcesContent":["/**\n * Types for AdCP Agent E2E Testing\n */\n\nimport type { FormatReferenceStructuredObject as FormatID } from '../types/core.generated';\nimport type { ControllerDetection } from './test-controller';\nimport type { WebhookReceiver } from './storyboard/webhook-receiver';\nimport type { AdcpVersion } from '../version';\nimport type { TransportOptions, VersionEnvelopeMode } from '../protocols';\nimport type { AgentRequestSigningConfig } from '../types/adcp';\n\n// Test scenarios that can be run\nexport type TestScenario =\n | 'health_check' // Just check if agent responds\n | 'discovery' // get_products, list_creative_formats, list_authorized_properties\n | 'create_media_buy' // Discovery + create a test media buy\n | 'full_sales_flow' // Full lifecycle: discovery -> create -> update -> delivery\n | 'reporting_flow' // Dedicated: create media buy -> get_media_buy_delivery validation\n | 'creative_sync' // Test sync_creatives flow\n | 'creative_inline' // Test inline creatives in create_media_buy\n | 'creative_reference' // Build -> sync -> reference a creative via creative_ids\n | 'pricing_models' // Test different pricing models the agent supports\n | 'creative_flow' // Creative agent: list_formats -> build -> preview\n | 'creative_lifecycle' // Creative agent: formats -> sync multiple -> list with/without snapshot -> build/preview\n | 'signals_flow' // Signals agent: get_signals -> activate\n // Edge case testing scenarios\n | 'error_handling' // Test agent returns proper error responses\n | 'validation' // Test schema validation (invalid inputs should be rejected)\n | 'pricing_edge_cases' // Test auction vs fixed pricing, min spend, bid_price requirements\n | 'temporal_validation' // Test date/time ordering and format validation\n // Behavioral analysis scenarios\n | 'behavior_analysis' // Analyze agent behavior: auth requirements, brief relevance, filtering\n // Response consistency scenarios\n | 'response_consistency' // Check for schema errors, pagination bugs, data mismatches\n // v3 Governance protocol scenarios\n | 'governance_property_lists' // Property list CRUD operations\n | 'governance_content_standards' // Content standards lifecycle\n | 'property_list_filters' // Property list filter round-trip: GARM, MFA, custom_tags, feature_requirements\n // v3 Campaign governance scenarios\n | 'campaign_governance' // Full lifecycle: sync_plans -> check -> execute -> report outcome\n | 'campaign_governance_denied' // Denied flow: over-budget, unauthorized market\n | 'campaign_governance_conditions' // Conditions flow: apply conditions -> re-check\n | 'campaign_governance_delivery' // Delivery monitoring with drift detection\n | 'seller_governance_context' // Seller persistence of governance_context\n // v3 SI (Sponsored Intelligence) protocol scenarios\n | 'si_session_lifecycle' // Full SI session: initiate -> messages -> terminate\n | 'si_availability' // Check SI offering availability\n | 'si_handoff' // ACP handoff flow: initiate -> purchase intent -> terminate with handoff_transaction\n // v3 Capability discovery\n | 'capability_discovery' // Verify get_adcp_capabilities response\n // Schema compliance\n | 'schema_compliance' // Validate v3 channel enum, pricing field names, format assets structure\n // Audience management\n | 'sync_audiences' // Test CRM audience sync flow\n // State machine compliance\n | 'media_buy_lifecycle' // Pause -> resume -> cancel state transitions\n | 'terminal_state_enforcement' // Verify agents reject updates to terminal-state media buys\n | 'package_lifecycle' // Package-level pause/resume independent of media buy status\n // Error compliance (transport error mapping spec)\n | 'error_codes' // Validate standard AdCP error codes in responses\n | 'error_structure' // Validate error JSON structure against error.json schema\n | 'error_transport' // Validate transport binding (structuredContent, text fallback)\n // Deterministic state machine scenarios (require comply_test_controller)\n | 'deterministic_creative' // Force creative status transitions via test controller\n | 'deterministic_media_buy' // Force media buy status transitions via test controller\n | 'deterministic_account' // Force account status transitions + operation gates\n | 'deterministic_session' // Force SI session timeout/termination\n | 'deterministic_delivery' // Simulate delivery data and verify reporting\n | 'deterministic_budget' // Simulate budget spend and verify financials\n | 'controller_validation' // Validate the test controller itself (error codes, edge cases)\n // Brand rights protocol scenarios\n | 'brand_identity' // Brand identity discovery (public and authorized access tiers)\n | 'brand_rights_flow' // Brand rights: get_rights -> acquire_rights lifecycle\n | 'creative_approval'; // Creative approval workflow for brand compliance\n\nexport interface TestOptions {\n // Protocol to use for testing (default: 'mcp')\n protocol?: 'mcp' | 'a2a';\n /** Optional cancellation signal for discovery and storyboard helper calls. */\n signal?: AbortSignal;\n /**\n * Runtime-only transport configuration for discovery and every storyboard\n * or compliance tool call. In particular, `fetchFn` lets hosted runners\n * enforce a request-scoped network policy without replacing global fetch.\n */\n transport?: TransportOptions;\n /**\n * RFC 9421 identity used for ordinary functional test/storyboard calls.\n * Distinct from `StoryboardRunOptions.request_signing`, which configures\n * the dedicated verifier-vector grader. Compliance runners populate this\n * automatically from `signed-requests-runner.yaml > functional_dispatch`\n * only for sandbox runs; callers may provide their own key/provider for a\n * different trusted environment.\n */\n functional_request_signing?: AgentRequestSigningConfig;\n /**\n * AdCP protocol version the test client should speak. Storyboard runners\n * set this from the compliance cache version instead of relying on the\n * installed package default.\n */\n adcpVersion?: AdcpVersion | (string & {});\n /**\n * Route compact media-buy storyboard steps through the SDK compatibility\n * coordinator. This lets the same compact-first storyboard exercise native\n * 3.2 sellers and established v2.5–3.2 sellers without task-name branching.\n * Mutating guarantee losses remain fail-closed unless named here.\n */\n mediaBuyLifecycleCompatibility?: import('../media-buy/compatibility').MediaBuyLifecycleCoordinatorOptions;\n /**\n * Optional wire-only AdCP version envelope override. Validation and schema\n * selection continue to use `adcpVersion`; request envelopes use this value.\n * Intended for hosted stable-line badges backed by prerelease caches.\n */\n wireAdcpVersion?: AdcpVersion | (string & {});\n /**\n * Version-envelope emission mode. Defaults to `auto`; 3.0 storyboards emit\n * the legacy major marker only, while 3.1 storyboards also emit the exact\n * `adcp_version` marker. Compliance discovery may negotiate `major-only`\n * for strict pre-3.1 agents.\n */\n versionEnvelope?: VersionEnvelopeMode;\n /**\n * External schema bundle root used for request/response validation during\n * storyboard and compliance runs. The root is registered against\n * `adcpVersion` (or the storyboard/compliance version when omitted) and is\n * authoritative over the SDK's generated Zod snapshot for the run.\n */\n schemaRoot?: string;\n /**\n * Whether strict AJV response-schema failures contribute to storyboard\n * grading. Defaults to `true`, matching the hosted compliance grader.\n *\n * Set to `false` only for packaged-schema diagnostic migrations that need\n * the historical lenient-Zod grade while inspecting\n * `strict_validation_summary` and the per-validation `strict` verdicts.\n * Strict validation still runs in that mode; it is informational rather\n * than grading. An explicit external `schemaRoot` remains authoritative.\n */\n strictResponseSchemaValidation?: boolean;\n /** Custom User-Agent string sent with all outbound requests */\n userAgent?: string;\n // Brand reference for product discovery (preferred over brand_manifest)\n brand?: { domain: string; brand_id?: string };\n // Custom brief for product discovery\n brief?: string;\n // Budget for test media buy (default: 1000)\n budget?: number;\n // Specific format IDs to test\n format_ids?: string[];\n // Test session ID for isolation\n test_session_id?: string;\n // Channels to focus on (if not specified, tests all agent supports)\n channels?: string[];\n // Specific pricing models to test\n pricing_models?: string[];\n /**\n * Authentication for agents that require it.\n *\n * - `bearer`: raw token sent as `Authorization: Bearer <token>`.\n * - `basic`: cleartext `username` and `password`, encoded internally.\n * - `oauth`: saved OAuth tokens (access_token + refresh_token). MCP only.\n * The library auto-refreshes on 401. Obtain tokens interactively via\n * `adcp --save-auth <alias> --oauth`, then pass the saved blob here for\n * non-interactive reuse.\n * - `oauth_client_credentials`: RFC 6749 §4.4 machine-to-machine flow.\n * The library exchanges the secret for a fresh access token before each\n * call (cached while valid). Supply `tokens` to seed the cache; omit to\n * exchange on first call.\n */\n auth?:\n | { type: 'bearer'; token: string }\n | { type: 'basic'; username: string; password: string }\n | {\n type: 'oauth';\n tokens: import('../types/adcp').AgentOAuthTokens;\n client?: import('../types/adcp').AgentOAuthClient;\n }\n | {\n type: 'oauth_client_credentials';\n credentials: import('../types/adcp').AgentOAuthClientCredentials;\n tokens?: import('../types/adcp').AgentOAuthTokens;\n };\n /**\n * Extra HTTP headers applied to every outbound request to the agent.\n * Forwarded into `AgentConfig.headers`, so MCP and A2A transports both\n * see them. `Authorization` and `x-adcp-auth` are reserved — auth wins.\n *\n * Typical use: tenant-routing headers (`x-adcp-tenant`, `Apx-Incoming-Host`)\n * for multi-tenant agents fronted by a reverse proxy. The CLI exposes these\n * via `-H KEY=VALUE` (repeatable) and persists them in `~/.adcp/config.json`.\n */\n headers?: Record<string, string>;\n // Brand manifest for creative testing\n brand_manifest?: {\n name: string;\n url?: string;\n tagline?: string;\n logos?: Array<{\n url: string;\n orientation?: 'square' | 'horizontal' | 'vertical' | 'stacked';\n background?: 'dark-bg' | 'light-bg' | 'transparent-bg';\n variant?: 'primary' | 'secondary' | 'icon' | 'wordmark' | 'full-lockup';\n tags?: string[];\n usage?: string;\n width?: number;\n height?: number;\n }>;\n colors?: Record<string, string>;\n tone?: {\n voice?: string;\n attributes?: string[];\n dos?: string[];\n donts?: string[];\n };\n assets?: Array<{\n asset_id: string;\n asset_type: string;\n url: string;\n width?: number;\n height?: number;\n tags?: string[];\n }>;\n };\n // For creative testing: test multiple formats programmatically\n test_all_formats?: boolean;\n // For creative testing: max formats to test when test_all_formats is true\n max_formats_to_test?: number;\n // For signals testing: specific signal types to test\n signal_types?: string[];\n // For governance testing: name for test property list\n property_list_name?: string;\n // For governance testing: specific content standards to test\n content_standards_id?: string;\n // For SI testing: specific offering ID to test\n si_offering_id?: string;\n // For SI testing: initial conversation context\n si_context?: string;\n /**\n * Shared explicit account id for test scenarios that need an account-scoped\n * request. Domain-specific account ids below take precedence when provided.\n */\n account_id?: string;\n /**\n * For media-buy testing: account ID to use with get_products,\n * create_media_buy, sync_creatives, and related media-buy flows.\n */\n media_buy_account_id?: string;\n // For audience testing: account ID to use with sync_audiences\n audience_account_id?: string;\n // When true, use sandbox mode. For implicit accounts, uses the natural key with\n // sandbox: true. For explicit accounts, discovers sandbox accounts via list_accounts.\n sandbox?: boolean;\n /**\n * When true, the runner injects `ext.adcp.disable_sandbox: true` on every\n * outgoing request, signaling the agent under test to bypass any internal\n * sandbox routing (env-var fallbacks, brand-domain heuristics, fixture\n * substitutes) and exercise its real adapter path. Distinct from `sandbox`\n * (which sets `account.sandbox: false` — a value, not a routing hint).\n *\n * Honored by adopters who explicitly read `ext.adcp.disable_sandbox`.\n * Agents that don't recognize the field ignore it (per spec, `ext` is\n * accepted-without-error). The pair (sandbox=false + disable_sandbox=true)\n * is the strongest \"production path only\" signal the runner can send;\n * `--no-sandbox` on `adcp storyboard run` sets both. Issue #841.\n */\n disable_sandbox?: boolean;\n /**\n * Fictional-entity test-kit data loaded from `test-kits/<name>.yaml`.\n * Storyboard phases may skip based on fields here (e.g. `skip_if: \"!test_kit.auth.api_key\"`).\n * Guards are evaluated together with the accumulated storyboard `context.*`\n * at each phase boundary.\n */\n test_kit?: {\n auth?: {\n /** API key the runner presents on API-key probes. */\n api_key?: string;\n /** HTTP Basic credential the runner presents on Basic-auth probes. */\n basic?: {\n /** Username portion of the Basic credential. */\n username?: string;\n /** Password portion of the Basic credential. */\n password?: string;\n /** Unencoded `username:password` pair. */\n credentials?: string;\n };\n /**\n * Auth-required, read-only tool the runner uses for unauth + invalid-key probes.\n * Required whenever `auth` is declared — no default is substituted. Must be one of\n * the values in `PROBE_TASK_ALLOWLIST`. Kits that miss this or pick a task outside\n * the allowlist fail at `comply()` / `runStoryboard()` entry with\n * `TestKitValidationError`.\n */\n probe_task: string;\n };\n [key: string]: unknown;\n };\n /** @internal Pre-created client from comply() — avoids per-scenario MCP reconnection */\n _client?: unknown;\n /**\n * @internal Pre-discovered profile from comply() — skips per-scenario discovery.\n * @deprecated Use `StoryboardRunOptions.profile` for storyboard profile reuse.\n */\n _profile?: AgentProfile;\n /**\n * @internal Server-declared AdCP version learned during capability discovery.\n * Storyboard response validation uses this for version-skew compatibility\n * without forcing the transport client to pin a schema bundle that may not\n * ship with the installed SDK.\n */\n _serverAdcpVersion?: string;\n /** @internal Test controller capabilities from comply() — set when comply_test_controller detected */\n _controllerCapabilities?: ControllerDetection;\n /** @internal Pre-created webhook receiver, used for unit-test injection. When\n * present, the runner skips creating its own listener and does NOT close\n * the receiver after the run (caller-owned). Production callers pass\n * `webhook_receiver` instead. */\n _webhookReceiver?: WebhookReceiver;\n}\n\nexport interface TestStepResult {\n step: string;\n task?: string;\n passed: boolean;\n duration_ms: number;\n details?: string;\n error?: string;\n response_preview?: string;\n // Structured data for collectObservations() — decoupled from response_preview display\n observation_data?: Record<string, unknown>;\n // For tracking what was created (for cleanup or follow-up)\n created_id?: string;\n // Deprecation or other warnings\n warnings?: string[];\n /** True when the step was skipped (including re-graded branch-set peers). */\n skipped?: boolean;\n /** Canonical skip reason when `skipped` is true; maps to `RunnerSkipReason`. */\n skip_reason?: string;\n /**\n * Selection reason when a skipped-looking step was intentionally outside\n * the caller's requested run. Summary artifacts use this to keep\n * not-selected steps out of actionable skip-cause rollups.\n */\n selection_reason?: string;\n /**\n * Names the unmet runtime requirement when `skip_reason` is\n * `'requirement_unmet'` (per adcp-client#1626). Carries the same value\n * the storyboard authored in `Storyboard.requires`. Lets skip-cause\n * aggregators sub-group not-applicable scenarios per-requirement\n * without parsing the warning text. Absent for every other skip reason.\n */\n requirement?: string;\n}\n\nexport interface AgentProfile {\n name: string;\n tools: string[];\n channels?: string[];\n pricing_models?: string[];\n format_ids?: string[];\n delivery_types?: string[];\n // For creative agents\n supported_formats?: Array<{\n format_id: FormatID;\n name?: string;\n required_assets?: string[];\n optional_assets?: string[];\n }>;\n // For signals agents\n supported_signals?: Array<{\n signal_id: string;\n name?: string;\n type?: string;\n }>;\n // v3 capabilities\n adcp_version?: 'v2' | 'v3';\n /**\n * AdCP major versions the agent declared in `get_adcp_capabilities.adcp.major_versions`.\n * Drives version-gated storyboard filtering so a v3.0 agent isn't failed against a\n * storyboard introduced in a later minor version.\n */\n adcp_major_versions?: number[];\n /**\n * Exact/release-precision versions from\n * `get_adcp_capabilities.adcp.supported_versions`.\n */\n adcp_supported_versions?: string[];\n /**\n * Seller's full AdCP build version from\n * `get_adcp_capabilities.adcp.build_version`.\n */\n adcp_build_version?: string;\n supported_protocols?: string[];\n /** Specialism claims from get_adcp_capabilities.specialisms */\n specialisms?: string[];\n supports_governance?: boolean;\n supports_si?: boolean;\n /**\n * Populated when the agent advertises `get_adcp_capabilities` but the call failed\n * or returned no data. Signals the compliance runner that the universal-only\n * result is due to a broken caps probe, not an agent that lacks v3 support.\n */\n capabilities_probe_error?: string;\n /**\n * Schema violations found during the preflight `get_adcp_capabilities`\n * call. Kept separately from `capabilities_probe_error`: an invalid\n * response is still available for best-effort storyboard selection, while\n * the runner surfaces each violation as a structured notice.\n */\n capabilities_schema_issues?: Array<{\n /** RFC 6901 pointer into the capabilities response. */\n pointer: string;\n /** Validator-authored explanation of the violation. */\n message: string;\n }>;\n /**\n * Raw `get_adcp_capabilities` response body. Used by the storyboard runner to\n * evaluate `requires_capability` predicates (e.g. `adcp.idempotency.supported`)\n * that reference fields not extracted into the normalised profile shape above.\n */\n raw_capabilities?: unknown;\n /**\n * SDK version string the agent self-reported in `get_adcp_capabilities`, e.g.\n * `\"@adcp/client@5.14.0\"`. Populated opportunistically — absent for hand-rolled\n * agents that don't emit the field. Used by the storyboard runner to suffix\n * shape-drift hints when the reported version predates the recommended helper.\n */\n library_version?: string;\n}\n\nexport interface TestResult {\n agent_url: string;\n scenario: TestScenario;\n overall_passed: boolean;\n steps?: TestStepResult[];\n summary: string;\n total_duration_ms: number;\n tested_at: string;\n // Agent profile discovered during testing\n agent_profile?: AgentProfile;\n}\n\nexport interface SuiteResult {\n agent_url: string;\n agent_profile: AgentProfile;\n /** Scenarios that were run */\n scenarios_run: TestScenario[];\n /** Scenarios skipped because the agent does not advertise the required tools */\n scenarios_skipped: TestScenario[];\n results: TestResult[];\n /**\n * True only when at least one scenario ran and none failed.\n * False for both \"all failed\" and \"no applicable scenarios found\".\n */\n overall_passed: boolean;\n passed_count: number;\n failed_count: number;\n /** Wall-clock time including capability discovery and all scenario runs */\n total_duration_ms: number;\n tested_at: string;\n}\n\n// Generic task result from executeTask\nexport interface TaskResult {\n success: boolean;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- response shape varies by tool; typed per-access in scenarios\n data?: any;\n error?: string;\n /** Structured AdCP error forwarded from the transport layer. Mirrors `TaskResultFailure.adcpError`. */\n adcp_error?: import('../core/ConversationTypes').AdcpErrorInfo;\n /**\n * Internal: which MCP extraction path produced `data`. Set by the response\n * unwrapper and the raw MCP probe so the storyboard runner can surface it\n * in its output contract. Consumers outside the runner should treat this\n * as implementation detail — it's NOT part of the public `AdCPResponse`.\n */\n _extraction_path?: 'structured_content' | 'text_fallback' | 'error' | 'none';\n}\n\n// Logger interface for library use\nexport interface Logger {\n info: (context: object, message: string) => void;\n error: (context: object, message: string) => void;\n warn: (context: object, message: string) => void;\n debug: (context: object, message: string) => void;\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
|
@@ -28180,6 +28180,9 @@ export interface TasksListResponse {
|
|
|
28180
28180
|
/**
|
|
28181
28181
|
* Descriptor for one configuration knob a transformer exposes. Returned inside transformer.json `params[]` from list_transformers. The descriptor declares the knob's shape; the buyer supplies its value in build_creative `config` keyed by `field`. For `value_source: enumerable` params (e.g. account-specific voices), the legal `options[]` are account-scoped and dynamic — they are returned only when the param's `field` is named in the list_transformers `expand_params` request, and may be paginated via `options_cursor`.
|
|
28182
28182
|
*/
|
|
28183
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
28184
|
+
[key: string]: JsonValue;
|
|
28185
|
+
};
|
|
28183
28186
|
export interface TransformerParam {
|
|
28184
28187
|
/**
|
|
28185
28188
|
* The config key. Buyers set the value under this exact key in build_creative `config`.
|
|
@@ -28217,7 +28220,7 @@ export interface TransformerParam {
|
|
|
28217
28220
|
/**
|
|
28218
28221
|
* The value the buyer passes in `config` for this field.
|
|
28219
28222
|
*/
|
|
28220
|
-
value:
|
|
28223
|
+
value: JsonValue;
|
|
28221
28224
|
/**
|
|
28222
28225
|
* Human-readable label for this option.
|
|
28223
28226
|
*/
|
|
@@ -28234,7 +28237,7 @@ export interface TransformerParam {
|
|
|
28234
28237
|
/**
|
|
28235
28238
|
* The value applied when the buyer omits this field from `config`. Type matches `type`.
|
|
28236
28239
|
*/
|
|
28237
|
-
default?:
|
|
28240
|
+
default?: JsonValue;
|
|
28238
28241
|
/**
|
|
28239
28242
|
* Whether the buyer MUST supply this field in `config`. When false and no `default` is declared, the agent chooses.
|
|
28240
28243
|
*/
|
|
@@ -28180,6 +28180,9 @@ export interface TasksListResponse {
|
|
|
28180
28180
|
/**
|
|
28181
28181
|
* Descriptor for one configuration knob a transformer exposes. Returned inside transformer.json `params[]` from list_transformers. The descriptor declares the knob's shape; the buyer supplies its value in build_creative `config` keyed by `field`. For `value_source: enumerable` params (e.g. account-specific voices), the legal `options[]` are account-scoped and dynamic — they are returned only when the param's `field` is named in the list_transformers `expand_params` request, and may be paginated via `options_cursor`.
|
|
28182
28182
|
*/
|
|
28183
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
28184
|
+
[key: string]: JsonValue;
|
|
28185
|
+
};
|
|
28183
28186
|
export interface TransformerParam {
|
|
28184
28187
|
/**
|
|
28185
28188
|
* The config key. Buyers set the value under this exact key in build_creative `config`.
|
|
@@ -28217,7 +28220,7 @@ export interface TransformerParam {
|
|
|
28217
28220
|
/**
|
|
28218
28221
|
* The value the buyer passes in `config` for this field.
|
|
28219
28222
|
*/
|
|
28220
|
-
value:
|
|
28223
|
+
value: JsonValue;
|
|
28221
28224
|
/**
|
|
28222
28225
|
* Human-readable label for this option.
|
|
28223
28226
|
*/
|
|
@@ -28234,7 +28237,7 @@ export interface TransformerParam {
|
|
|
28234
28237
|
/**
|
|
28235
28238
|
* The value applied when the buyer omits this field from `config`. Type matches `type`.
|
|
28236
28239
|
*/
|
|
28237
|
-
default?:
|
|
28240
|
+
default?: JsonValue;
|
|
28238
28241
|
/**
|
|
28239
28242
|
* Whether the buyer MUST supply this field in `config`. When false and no `default` is declared, the agent chooses.
|
|
28240
28243
|
*/
|