@adcp/sdk 14.0.0-beta.19 → 14.0.0-beta.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/bin/adcp.js +154 -6
  2. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  3. package/dist/lib/server/decisioning/runtime/postgres-task-settlement.d.mts +10 -0
  4. package/dist/lib/server/decisioning/runtime/postgres-task-settlement.d.ts +10 -0
  5. package/dist/lib/server/decisioning/runtime/postgres-task-settlement.js +21 -0
  6. package/dist/lib/server/decisioning/runtime/postgres-task-settlement.mjs +21 -0
  7. package/dist/lib/server/serve.d.mts +11 -0
  8. package/dist/lib/server/serve.d.ts +11 -0
  9. package/dist/lib/server/serve.js +35 -5
  10. package/dist/lib/server/serve.mjs +35 -5
  11. package/dist/lib/signing/brand-jwks.d.mts +44 -1
  12. package/dist/lib/signing/brand-jwks.d.ts +44 -1
  13. package/dist/lib/signing/brand-jwks.js +53 -15
  14. package/dist/lib/signing/brand-jwks.mjs +51 -14
  15. package/dist/lib/signing/server.d.mts +1 -1
  16. package/dist/lib/signing/server.d.ts +1 -1
  17. package/dist/lib/signing/server.js +2 -0
  18. package/dist/lib/signing/server.mjs +3 -1
  19. package/dist/lib/testing/storyboard/runner.js +6 -2
  20. package/dist/lib/testing/storyboard/runner.mjs +6 -2
  21. package/dist/lib/testing/storyboard/types.d.mts +6 -0
  22. package/dist/lib/testing/storyboard/types.d.ts +6 -0
  23. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  24. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  25. package/dist/lib/testing/storyboard/webhook-assertions.js +6 -1
  26. package/dist/lib/testing/storyboard/webhook-assertions.mjs +6 -1
  27. package/dist/lib/testing/storyboard/webhook-receiver.d.mts +15 -0
  28. package/dist/lib/testing/storyboard/webhook-receiver.d.ts +15 -0
  29. package/dist/lib/testing/storyboard/webhook-receiver.js +55 -15
  30. package/dist/lib/testing/storyboard/webhook-receiver.mjs +55 -15
  31. package/dist/lib/version.d.mts +3 -3
  32. package/dist/lib/version.d.ts +3 -3
  33. package/dist/lib/version.js +3 -3
  34. package/dist/lib/version.mjs +3 -3
  35. package/docs/llms.txt +1 -1
  36. package/package.json +1 -1
@@ -10,7 +10,13 @@ export type BrandJsonResolverErrorCode = 'invalid_url' | 'invalid_house' | 'redi
10
10
  */
11
11
  export declare class BrandJsonResolverError extends Error {
12
12
  readonly code: BrandJsonResolverErrorCode;
13
- constructor(code: BrandJsonResolverErrorCode, message: string);
13
+ readonly cause?: unknown;
14
+ /** HTTP status for `fetch_failed` responses, when a response was received. */
15
+ readonly httpStatus?: number;
16
+ constructor(code: BrandJsonResolverErrorCode, message: string, details?: {
17
+ httpStatus?: number;
18
+ cause?: unknown;
19
+ });
14
20
  }
15
21
  export interface BrandJsonJwksResolverOptions {
16
22
  /** Functional role of the agent whose keys we want to resolve. */
@@ -99,3 +105,40 @@ export declare class BrandJsonJwksResolver implements JwksResolver {
99
105
  private refresh;
100
106
  private doRefresh;
101
107
  }
108
+ export interface FetchedBrandJson {
109
+ status: 'ok' | 'not_modified';
110
+ finalUrl: string;
111
+ data: unknown;
112
+ etag?: string;
113
+ cacheControl?: string;
114
+ }
115
+ export interface FetchBrandJsonOptions {
116
+ /** Entry-point URL. HTTPS and public addresses are required by default. */
117
+ startUrl: string;
118
+ /** ETag sent only to the entry URL for cache revalidation. */
119
+ currentEtag?: string;
120
+ /** Maximum JSON-level `authoritative_location` / `house` hops. Default 3, hard maximum 10. */
121
+ maxRedirects?: number;
122
+ /** Permit HTTP and private addresses for controlled development environments. */
123
+ allowPrivateIp?: boolean;
124
+ /** Whole-request deadline per hop. Default and hard maximum 10 seconds. */
125
+ timeoutMs?: number;
126
+ /** Response-body cap per hop. Default and hard maximum 256 KiB. */
127
+ maxBodyBytes?: number;
128
+ }
129
+ /**
130
+ * Fetch brand.json from `startUrl`, following `authoritative_location` and
131
+ * `house` string redirect variants up to `maxRedirects` hops. Each hop goes
132
+ * through the SSRF-safe fetch primitive so an attacker-supplied chain can't
133
+ * land on a private address or IMDS. Redirect targets are structurally
134
+ * validated before dispatch — an attacker-controlled brand.json that emits
135
+ * `{"house": "evil.com\\@victim.com"}` or `{"authoritative_location":
136
+ * "http://169.254.169.254/..."}` is rejected at parse time rather than
137
+ * relying on `ssrfSafeFetch` to catch every pathological shape.
138
+ *
139
+ * This low-level function is intentionally stateless. Callers MUST add
140
+ * response caching and a minimum refresh cooldown rather than invoking it on
141
+ * every authorization request. Prefer `BrandJsonJwksResolver` when resolving
142
+ * signing keys; it provides both safeguards.
143
+ */
144
+ export declare function fetchBrandJson(args: FetchBrandJsonOptions): Promise<FetchedBrandJson>;
@@ -19,17 +19,23 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var brand_jwks_exports = {};
20
20
  __export(brand_jwks_exports, {
21
21
  BrandJsonJwksResolver: () => BrandJsonJwksResolver,
22
- BrandJsonResolverError: () => BrandJsonResolverError
22
+ BrandJsonResolverError: () => BrandJsonResolverError,
23
+ fetchBrandJson: () => fetchBrandJson
23
24
  });
24
25
  module.exports = __toCommonJS(brand_jwks_exports);
25
26
  var import_net = require('../net/index.js');
26
27
  var import_jwks_https = require('./jwks-https.js');
27
28
  class BrandJsonResolverError extends Error {
28
29
  code;
29
- constructor(code, message) {
30
+ cause;
31
+ /** HTTP status for `fetch_failed` responses, when a response was received. */
32
+ httpStatus;
33
+ constructor(code, message, details = {}) {
30
34
  super(message);
31
35
  this.name = "BrandJsonResolverError";
32
36
  this.code = code;
37
+ this.httpStatus = details.httpStatus;
38
+ this.cause = details.cause;
33
39
  }
34
40
  }
35
41
  const DEFAULT_MIN_COOLDOWN_SECONDS = 30;
@@ -143,10 +149,26 @@ class BrandJsonJwksResolver {
143
149
  };
144
150
  }
145
151
  }
152
+ const MAX_BRAND_JSON_TIMEOUT_MS = 1e4;
153
+ const MAX_BRAND_JSON_BODY_BYTES = 262144;
154
+ const MAX_BRAND_JSON_REDIRECTS = 10;
146
155
  async function fetchBrandJson(args) {
156
+ const maxRedirects = boundedIntegerOption("maxRedirects", args.maxRedirects ?? DEFAULT_MAX_REDIRECTS, {
157
+ min: 0,
158
+ max: MAX_BRAND_JSON_REDIRECTS
159
+ });
160
+ const timeoutMs = boundedIntegerOption("timeoutMs", args.timeoutMs ?? MAX_BRAND_JSON_TIMEOUT_MS, {
161
+ min: 1,
162
+ max: MAX_BRAND_JSON_TIMEOUT_MS
163
+ });
164
+ const maxBodyBytes = boundedIntegerOption("maxBodyBytes", args.maxBodyBytes ?? MAX_BRAND_JSON_BODY_BYTES, {
165
+ min: 1,
166
+ max: MAX_BRAND_JSON_BODY_BYTES
167
+ });
168
+ const allowPrivateIp = args.allowPrivateIp === true;
147
169
  const seen = /* @__PURE__ */ new Set();
148
- let url = canonicalizeUrl(args.startUrl, args.allowPrivateIp);
149
- for (let hop = 0; hop <= args.maxRedirects; hop++) {
170
+ let url = canonicalizeUrl(args.startUrl, allowPrivateIp);
171
+ for (let hop = 0; hop <= maxRedirects; hop++) {
150
172
  if (seen.has(url)) {
151
173
  throw new BrandJsonResolverError("redirect_loop", `brand.json redirect loop detected`);
152
174
  }
@@ -155,11 +177,18 @@ async function fetchBrandJson(args) {
155
177
  accept: "application/json"
156
178
  };
157
179
  if (hop === 0 && args.currentEtag) headers["if-none-match"] = args.currentEtag;
158
- const res = await (0, import_net.ssrfSafeFetch)(url, {
159
- method: "GET",
160
- headers,
161
- allowPrivateIp: args.allowPrivateIp
162
- });
180
+ let res;
181
+ try {
182
+ res = await (0, import_net.ssrfSafeFetch)(url, {
183
+ method: "GET",
184
+ headers,
185
+ allowPrivateIp,
186
+ timeoutMs,
187
+ maxBodyBytes
188
+ });
189
+ } catch (cause) {
190
+ throw new BrandJsonResolverError("fetch_failed", "Unable to fetch brand.json", { cause });
191
+ }
163
192
  if (hop === 0 && res.status === 304) {
164
193
  return {
165
194
  status: "not_modified",
@@ -170,7 +199,9 @@ async function fetchBrandJson(args) {
170
199
  };
171
200
  }
172
201
  if (res.status !== 200) {
173
- throw new BrandJsonResolverError("fetch_failed", `brand.json fetch returned HTTP ${res.status}`);
202
+ throw new BrandJsonResolverError("fetch_failed", `brand.json fetch returned HTTP ${res.status}`, {
203
+ httpStatus: res.status
204
+ });
174
205
  }
175
206
  const text = Buffer.from(res.body).toString("utf8");
176
207
  let parsed;
@@ -186,20 +217,20 @@ async function fetchBrandJson(args) {
186
217
  const authoritative = typeof obj.authoritative_location === "string" ? obj.authoritative_location : void 0;
187
218
  const house = typeof obj.house === "string" ? obj.house : void 0;
188
219
  if (authoritative !== void 0) {
189
- if (hop === args.maxRedirects) {
220
+ if (hop === maxRedirects) {
190
221
  throw new BrandJsonResolverError("redirect_depth_exceeded", `brand.json redirect depth exceeded`);
191
222
  }
192
- url = canonicalizeUrl(authoritative, args.allowPrivateIp);
223
+ url = canonicalizeUrl(authoritative, allowPrivateIp);
193
224
  continue;
194
225
  }
195
226
  if (house !== void 0) {
196
227
  if (!BARE_HOSTNAME.test(house)) {
197
228
  throw new BrandJsonResolverError("invalid_house", `brand.json "house" is not a bare hostname`);
198
229
  }
199
- if (hop === args.maxRedirects) {
230
+ if (hop === maxRedirects) {
200
231
  throw new BrandJsonResolverError("redirect_depth_exceeded", `brand.json redirect depth exceeded`);
201
232
  }
202
- url = canonicalizeUrl(`https://${house}/.well-known/brand.json`, args.allowPrivateIp);
233
+ url = canonicalizeUrl(`https://${house}/.well-known/brand.json`, allowPrivateIp);
203
234
  continue;
204
235
  }
205
236
  assertBrandJsonShape(obj);
@@ -213,6 +244,12 @@ async function fetchBrandJson(args) {
213
244
  }
214
245
  throw new BrandJsonResolverError("redirect_depth_exceeded", `brand.json redirect depth exceeded`);
215
246
  }
247
+ function boundedIntegerOption(name, value, bounds) {
248
+ if (!Number.isInteger(value) || value < bounds.min || value > bounds.max) {
249
+ throw new TypeError(`fetchBrandJson: ${name} must be an integer between ${bounds.min} and ${bounds.max}`);
250
+ }
251
+ return value;
252
+ }
216
253
  function canonicalizeUrl(raw, allowPrivateIp) {
217
254
  let parsed;
218
255
  try {
@@ -346,5 +383,6 @@ function computeLifetime(cacheControl, maxAge) {
346
383
  // Annotate the CommonJS export names for ESM import in node:
347
384
  0 && (module.exports = {
348
385
  BrandJsonJwksResolver,
349
- BrandJsonResolverError
386
+ BrandJsonResolverError,
387
+ fetchBrandJson
350
388
  });
@@ -2,10 +2,15 @@ import { ssrfSafeFetch } from "../net/index.mjs";
2
2
  import { HttpsJwksResolver } from "./jwks-https.mjs";
3
3
  class BrandJsonResolverError extends Error {
4
4
  code;
5
- constructor(code, message) {
5
+ cause;
6
+ /** HTTP status for `fetch_failed` responses, when a response was received. */
7
+ httpStatus;
8
+ constructor(code, message, details = {}) {
6
9
  super(message);
7
10
  this.name = "BrandJsonResolverError";
8
11
  this.code = code;
12
+ this.httpStatus = details.httpStatus;
13
+ this.cause = details.cause;
9
14
  }
10
15
  }
11
16
  const DEFAULT_MIN_COOLDOWN_SECONDS = 30;
@@ -119,10 +124,26 @@ class BrandJsonJwksResolver {
119
124
  };
120
125
  }
121
126
  }
127
+ const MAX_BRAND_JSON_TIMEOUT_MS = 1e4;
128
+ const MAX_BRAND_JSON_BODY_BYTES = 262144;
129
+ const MAX_BRAND_JSON_REDIRECTS = 10;
122
130
  async function fetchBrandJson(args) {
131
+ const maxRedirects = boundedIntegerOption("maxRedirects", args.maxRedirects ?? DEFAULT_MAX_REDIRECTS, {
132
+ min: 0,
133
+ max: MAX_BRAND_JSON_REDIRECTS
134
+ });
135
+ const timeoutMs = boundedIntegerOption("timeoutMs", args.timeoutMs ?? MAX_BRAND_JSON_TIMEOUT_MS, {
136
+ min: 1,
137
+ max: MAX_BRAND_JSON_TIMEOUT_MS
138
+ });
139
+ const maxBodyBytes = boundedIntegerOption("maxBodyBytes", args.maxBodyBytes ?? MAX_BRAND_JSON_BODY_BYTES, {
140
+ min: 1,
141
+ max: MAX_BRAND_JSON_BODY_BYTES
142
+ });
143
+ const allowPrivateIp = args.allowPrivateIp === true;
123
144
  const seen = /* @__PURE__ */ new Set();
124
- let url = canonicalizeUrl(args.startUrl, args.allowPrivateIp);
125
- for (let hop = 0; hop <= args.maxRedirects; hop++) {
145
+ let url = canonicalizeUrl(args.startUrl, allowPrivateIp);
146
+ for (let hop = 0; hop <= maxRedirects; hop++) {
126
147
  if (seen.has(url)) {
127
148
  throw new BrandJsonResolverError("redirect_loop", `brand.json redirect loop detected`);
128
149
  }
@@ -131,11 +152,18 @@ async function fetchBrandJson(args) {
131
152
  accept: "application/json"
132
153
  };
133
154
  if (hop === 0 && args.currentEtag) headers["if-none-match"] = args.currentEtag;
134
- const res = await ssrfSafeFetch(url, {
135
- method: "GET",
136
- headers,
137
- allowPrivateIp: args.allowPrivateIp
138
- });
155
+ let res;
156
+ try {
157
+ res = await ssrfSafeFetch(url, {
158
+ method: "GET",
159
+ headers,
160
+ allowPrivateIp,
161
+ timeoutMs,
162
+ maxBodyBytes
163
+ });
164
+ } catch (cause) {
165
+ throw new BrandJsonResolverError("fetch_failed", "Unable to fetch brand.json", { cause });
166
+ }
139
167
  if (hop === 0 && res.status === 304) {
140
168
  return {
141
169
  status: "not_modified",
@@ -146,7 +174,9 @@ async function fetchBrandJson(args) {
146
174
  };
147
175
  }
148
176
  if (res.status !== 200) {
149
- throw new BrandJsonResolverError("fetch_failed", `brand.json fetch returned HTTP ${res.status}`);
177
+ throw new BrandJsonResolverError("fetch_failed", `brand.json fetch returned HTTP ${res.status}`, {
178
+ httpStatus: res.status
179
+ });
150
180
  }
151
181
  const text = Buffer.from(res.body).toString("utf8");
152
182
  let parsed;
@@ -162,20 +192,20 @@ async function fetchBrandJson(args) {
162
192
  const authoritative = typeof obj.authoritative_location === "string" ? obj.authoritative_location : void 0;
163
193
  const house = typeof obj.house === "string" ? obj.house : void 0;
164
194
  if (authoritative !== void 0) {
165
- if (hop === args.maxRedirects) {
195
+ if (hop === maxRedirects) {
166
196
  throw new BrandJsonResolverError("redirect_depth_exceeded", `brand.json redirect depth exceeded`);
167
197
  }
168
- url = canonicalizeUrl(authoritative, args.allowPrivateIp);
198
+ url = canonicalizeUrl(authoritative, allowPrivateIp);
169
199
  continue;
170
200
  }
171
201
  if (house !== void 0) {
172
202
  if (!BARE_HOSTNAME.test(house)) {
173
203
  throw new BrandJsonResolverError("invalid_house", `brand.json "house" is not a bare hostname`);
174
204
  }
175
- if (hop === args.maxRedirects) {
205
+ if (hop === maxRedirects) {
176
206
  throw new BrandJsonResolverError("redirect_depth_exceeded", `brand.json redirect depth exceeded`);
177
207
  }
178
- url = canonicalizeUrl(`https://${house}/.well-known/brand.json`, args.allowPrivateIp);
208
+ url = canonicalizeUrl(`https://${house}/.well-known/brand.json`, allowPrivateIp);
179
209
  continue;
180
210
  }
181
211
  assertBrandJsonShape(obj);
@@ -189,6 +219,12 @@ async function fetchBrandJson(args) {
189
219
  }
190
220
  throw new BrandJsonResolverError("redirect_depth_exceeded", `brand.json redirect depth exceeded`);
191
221
  }
222
+ function boundedIntegerOption(name, value, bounds) {
223
+ if (!Number.isInteger(value) || value < bounds.min || value > bounds.max) {
224
+ throw new TypeError(`fetchBrandJson: ${name} must be an integer between ${bounds.min} and ${bounds.max}`);
225
+ }
226
+ return value;
227
+ }
192
228
  function canonicalizeUrl(raw, allowPrivateIp) {
193
229
  let parsed;
194
230
  try {
@@ -321,5 +357,6 @@ function computeLifetime(cacheControl, maxAge) {
321
357
  }
322
358
  export {
323
359
  BrandJsonJwksResolver,
324
- BrandJsonResolverError
360
+ BrandJsonResolverError,
361
+ fetchBrandJson
325
362
  };
@@ -15,7 +15,7 @@ export { jwkToPublicKey, verifySignature } from './crypto.mjs';
15
15
  export { RequestSignatureError, type RequestSignatureErrorCode, ResponseSignatureError, type ResponseSignatureErrorCode, WebhookSignatureError, type WebhookSignatureErrorCode, } from './errors.mjs';
16
16
  export { StaticJwksResolver, type JwksResolution, type JwksResolver } from './jwks.mjs';
17
17
  export { HttpsJwksResolver, type HttpsJwksResolverOptions } from './jwks-https.mjs';
18
- export { BrandJsonJwksResolver, BrandJsonResolverError, type BrandAgentType, type BrandJsonJwksResolverOptions, type BrandJsonResolverErrorCode, } from './brand-jwks.mjs';
18
+ export { BrandJsonJwksResolver, BrandJsonResolverError, fetchBrandJson, type BrandAgentType, type FetchedBrandJson, type FetchBrandJsonOptions, type BrandJsonJwksResolverOptions, type BrandJsonResolverErrorCode, } from './brand-jwks.mjs';
19
19
  export { parseSignature, parseSignatureInput, type ParsedSignature, type ParsedSignatureInput } from './parser.mjs';
20
20
  export { InMemoryReplayStore, type InMemoryReplayStoreOptions, type ReplayInsertResult, type ReplayStore, } from './replay.mjs';
21
21
  export { PostgresReplayStore, REPLAY_CACHE_MIGRATION, getReplayStoreMigration, sweepExpiredReplays, type PostgresReplayStoreOptions, type SweepExpiredReplaysOptions, } from './postgres-replay-store.mjs';
@@ -15,7 +15,7 @@ export { jwkToPublicKey, verifySignature } from './crypto';
15
15
  export { RequestSignatureError, type RequestSignatureErrorCode, ResponseSignatureError, type ResponseSignatureErrorCode, WebhookSignatureError, type WebhookSignatureErrorCode, } from './errors';
16
16
  export { StaticJwksResolver, type JwksResolution, type JwksResolver } from './jwks';
17
17
  export { HttpsJwksResolver, type HttpsJwksResolverOptions } from './jwks-https';
18
- export { BrandJsonJwksResolver, BrandJsonResolverError, type BrandAgentType, type BrandJsonJwksResolverOptions, type BrandJsonResolverErrorCode, } from './brand-jwks';
18
+ export { BrandJsonJwksResolver, BrandJsonResolverError, fetchBrandJson, type BrandAgentType, type FetchedBrandJson, type FetchBrandJsonOptions, type BrandJsonJwksResolverOptions, type BrandJsonResolverErrorCode, } from './brand-jwks';
19
19
  export { parseSignature, parseSignatureInput, type ParsedSignature, type ParsedSignatureInput } from './parser';
20
20
  export { InMemoryReplayStore, type InMemoryReplayStoreOptions, type ReplayInsertResult, type ReplayStore, } from './replay';
21
21
  export { PostgresReplayStore, REPLAY_CACHE_MIGRATION, getReplayStoreMigration, sweepExpiredReplays, type PostgresReplayStoreOptions, type SweepExpiredReplaysOptions, } from './postgres-replay-store';
@@ -54,6 +54,7 @@ __export(server_exports, {
54
54
  createAgentJwksSet: () => import_agent_resolver.createAgentJwksSet,
55
55
  createExpressVerifier: () => import_middleware.createExpressVerifier,
56
56
  createWebhookVerifier: () => import_webhook_verifier.createWebhookVerifier,
57
+ fetchBrandJson: () => import_brand_jwks.fetchBrandJson,
57
58
  finalizeResponseSignature: () => import_signer.finalizeResponseSignature,
58
59
  formatSignatureParams: () => import_canonicalize.formatSignatureParams,
59
60
  getAgentJwks: () => import_agent_resolver.getAgentJwks,
@@ -138,6 +139,7 @@ var import_agent_resolver = require('./agent-resolver/index.js');
138
139
  createAgentJwksSet,
139
140
  createExpressVerifier,
140
141
  createWebhookVerifier,
142
+ fetchBrandJson,
141
143
  finalizeResponseSignature,
142
144
  formatSignatureParams,
143
145
  getAgentJwks,
@@ -23,7 +23,8 @@ import { StaticJwksResolver } from "./jwks.mjs";
23
23
  import { HttpsJwksResolver } from "./jwks-https.mjs";
24
24
  import {
25
25
  BrandJsonJwksResolver,
26
- BrandJsonResolverError
26
+ BrandJsonResolverError,
27
+ fetchBrandJson
27
28
  } from "./brand-jwks.mjs";
28
29
  import { parseSignature, parseSignatureInput } from "./parser.mjs";
29
30
  import {
@@ -112,6 +113,7 @@ export {
112
113
  createAgentJwksSet,
113
114
  createExpressVerifier,
114
115
  createWebhookVerifier,
116
+ fetchBrandJson,
115
117
  finalizeResponseSignature,
116
118
  formatSignatureParams,
117
119
  getAgentJwks,
@@ -1529,7 +1529,9 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
1529
1529
  ...options.webhook_receiver.mode && { mode: options.webhook_receiver.mode },
1530
1530
  ...options.webhook_receiver.host !== void 0 && { host: options.webhook_receiver.host },
1531
1531
  ...options.webhook_receiver.port !== void 0 && { port: options.webhook_receiver.port },
1532
- ...options.webhook_receiver.public_url !== void 0 && { public_url: options.webhook_receiver.public_url }
1532
+ ...options.webhook_receiver.public_url !== void 0 && { public_url: options.webhook_receiver.public_url },
1533
+ allowHttp: options.allow_http === true,
1534
+ ...options.webhook_receiver.tls !== void 0 && { tls: options.webhook_receiver.tls }
1533
1535
  }) : void 0;
1534
1536
  const runnerVars = (0, import_context.createRunnerVariables)({
1535
1537
  ...webhookReceiver && { webhookBase: webhookReceiver.base_url }
@@ -2602,7 +2604,9 @@ async function runStoryboardStepBody(agentUrl, storyboard, stepId, options) {
2602
2604
  ...options.webhook_receiver.port !== void 0 && { port: options.webhook_receiver.port },
2603
2605
  ...options.webhook_receiver.public_url !== void 0 && {
2604
2606
  public_url: options.webhook_receiver.public_url
2605
- }
2607
+ },
2608
+ allowHttp: options.allow_http === true,
2609
+ ...options.webhook_receiver.tls !== void 0 && { tls: options.webhook_receiver.tls }
2606
2610
  }) : void 0);
2607
2611
  const ownsWebhookReceiver = !injectedReceiver && !!webhookReceiver;
2608
2612
  const runnerVars = (0, import_context.createRunnerVariables)({
@@ -1546,7 +1546,9 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
1546
1546
  ...options.webhook_receiver.mode && { mode: options.webhook_receiver.mode },
1547
1547
  ...options.webhook_receiver.host !== void 0 && { host: options.webhook_receiver.host },
1548
1548
  ...options.webhook_receiver.port !== void 0 && { port: options.webhook_receiver.port },
1549
- ...options.webhook_receiver.public_url !== void 0 && { public_url: options.webhook_receiver.public_url }
1549
+ ...options.webhook_receiver.public_url !== void 0 && { public_url: options.webhook_receiver.public_url },
1550
+ allowHttp: options.allow_http === true,
1551
+ ...options.webhook_receiver.tls !== void 0 && { tls: options.webhook_receiver.tls }
1550
1552
  }) : void 0;
1551
1553
  const runnerVars = createRunnerVariables({
1552
1554
  ...webhookReceiver && { webhookBase: webhookReceiver.base_url }
@@ -2619,7 +2621,9 @@ async function runStoryboardStepBody(agentUrl, storyboard, stepId, options) {
2619
2621
  ...options.webhook_receiver.port !== void 0 && { port: options.webhook_receiver.port },
2620
2622
  ...options.webhook_receiver.public_url !== void 0 && {
2621
2623
  public_url: options.webhook_receiver.public_url
2622
- }
2624
+ },
2625
+ allowHttp: options.allow_http === true,
2626
+ ...options.webhook_receiver.tls !== void 0 && { tls: options.webhook_receiver.tls }
2623
2627
  }) : void 0);
2624
2628
  const ownsWebhookReceiver = !injectedReceiver && !!webhookReceiver;
2625
2629
  const runnerVars = createRunnerVariables({
@@ -1718,6 +1718,12 @@ export interface StoryboardRunOptions extends TestOptions {
1718
1718
  * validate reachability.
1719
1719
  */
1720
1720
  public_url?: string;
1721
+ /** TLS material for direct HTTPS serving; omit behind a TLS-terminating proxy. */
1722
+ tls?: {
1723
+ cert: string | Buffer;
1724
+ key: string | Buffer;
1725
+ passphrase?: string;
1726
+ };
1721
1727
  };
1722
1728
  /**
1723
1729
  * Target receiver for `replay_webhook_vector` storyboards. This is separate
@@ -1718,6 +1718,12 @@ export interface StoryboardRunOptions extends TestOptions {
1718
1718
  * validate reachability.
1719
1719
  */
1720
1720
  public_url?: string;
1721
+ /** TLS material for direct HTTPS serving; omit behind a TLS-terminating proxy. */
1722
+ tls?: {
1723
+ cert: string | Buffer;
1724
+ key: string | Buffer;
1725
+ passphrase?: string;
1726
+ };
1721
1727
  };
1722
1728
  /**
1723
1729
  * Target receiver for `replay_webhook_vector` storyboards. This is separate
@@ -197,7 +197,7 @@ export interface UpstreamTrafficQueryResult {
197
197
  */
198
198
  export declare function runValidations(validations: StoryboardValidation[], context: ValidationContext): ValidationResult[];
199
199
  /** Capability semver emitted in run summaries and used for advisory expiry. */
200
- export declare const RUNNER_CAPABILITY_VERSION = "14.0.0-beta.19";
200
+ export declare const RUNNER_CAPABILITY_VERSION = "14.0.0-beta.20";
201
201
  /** True when a failed validation contributes to the owning step's grade. */
202
202
  export declare function validationFailsStep(result: ValidationResult): boolean;
203
203
  /**
@@ -197,7 +197,7 @@ export interface UpstreamTrafficQueryResult {
197
197
  */
198
198
  export declare function runValidations(validations: StoryboardValidation[], context: ValidationContext): ValidationResult[];
199
199
  /** Capability semver emitted in run summaries and used for advisory expiry. */
200
- export declare const RUNNER_CAPABILITY_VERSION = "14.0.0-beta.19";
200
+ export declare const RUNNER_CAPABILITY_VERSION = "14.0.0-beta.20";
201
201
  /** True when a failed validation contributes to the owning step's grade. */
202
202
  export declare function validationFailsStep(result: ValidationResult): boolean;
203
203
  /**
@@ -64,7 +64,12 @@ const DEFAULT_RETRY_COUNT = 3;
64
64
  const DEFAULT_RETRY_HTTP_STATUS = 503;
65
65
  const DEFAULT_MIN_DELIVERIES = 2;
66
66
  function webhookTimeoutDetail(receiver, base) {
67
- return receiver.mode === "loopback_mock" && receiver.all().length === 0 && receiver.challenges().length === 0 ? `${base} No request reached the loopback receiver; an out-of-process agent must use webhook_receiver.mode="proxy_url".` : base;
67
+ if (receiver.all().length > 0 || receiver.challenges().length > 0) return base;
68
+ const bindHost = receiver.bind_host ?? "the configured local address";
69
+ if (receiver.mode === "loopback_mock") {
70
+ return `${base} No request reached the receiver bound to ${bindHost}; an out-of-process agent must use webhook_receiver.mode="proxy_url".`;
71
+ }
72
+ return `${base} No request reached the receiver bound to ${bindHost}; verify that the advertised callback URL routes to this container and bind address.`;
68
73
  }
69
74
  const DEFAULT_WEBHOOK_SIGNING_TAG = "adcp/webhook-signing/v1";
70
75
  const MAX_RETRY_REPLAY_COUNT = 10;
@@ -39,7 +39,12 @@ const DEFAULT_RETRY_COUNT = 3;
39
39
  const DEFAULT_RETRY_HTTP_STATUS = 503;
40
40
  const DEFAULT_MIN_DELIVERIES = 2;
41
41
  function webhookTimeoutDetail(receiver, base) {
42
- return receiver.mode === "loopback_mock" && receiver.all().length === 0 && receiver.challenges().length === 0 ? `${base} No request reached the loopback receiver; an out-of-process agent must use webhook_receiver.mode="proxy_url".` : base;
42
+ if (receiver.all().length > 0 || receiver.challenges().length > 0) return base;
43
+ const bindHost = receiver.bind_host ?? "the configured local address";
44
+ if (receiver.mode === "loopback_mock") {
45
+ return `${base} No request reached the receiver bound to ${bindHost}; an out-of-process agent must use webhook_receiver.mode="proxy_url".`;
46
+ }
47
+ return `${base} No request reached the receiver bound to ${bindHost}; verify that the advertised callback URL routes to this container and bind address.`;
43
48
  }
44
49
  const DEFAULT_WEBHOOK_SIGNING_TAG = "adcp/webhook-signing/v1";
45
50
  const MAX_RETRY_REPLAY_COUNT = 10;
@@ -80,6 +80,8 @@ export interface WebhookReceiver {
80
80
  readonly base_url: string;
81
81
  /** Whether the receiver is in loopback-mock or proxy-url mode. */
82
82
  readonly mode: 'loopback_mock' | 'proxy_url';
83
+ /** Local address the listener is bound to. */
84
+ readonly bind_host?: string;
83
85
  /** All webhooks captured so far, in arrival order. */
84
86
  all(): CapturedWebhook[];
85
87
  /** Schema-valid proof-of-control challenges, kept separate from event deliveries. */
@@ -114,5 +116,18 @@ export interface CreateWebhookReceiverOptions {
114
116
  port?: number;
115
117
  /** Public URL to advertise when `mode: 'proxy_url'`. */
116
118
  public_url?: string;
119
+ /**
120
+ * Permit an HTTP public URL for controlled local-development topologies.
121
+ */
122
+ allowHttp?: boolean;
123
+ /**
124
+ * TLS material for serving HTTPS directly. Omit when HTTPS terminates at a
125
+ * tunnel or reverse proxy in front of this listener.
126
+ */
127
+ tls?: {
128
+ cert: string | Buffer;
129
+ key: string | Buffer;
130
+ passphrase?: string;
131
+ };
117
132
  }
118
133
  export declare function createWebhookReceiver(options?: CreateWebhookReceiverOptions): Promise<WebhookReceiver>;
@@ -80,6 +80,8 @@ export interface WebhookReceiver {
80
80
  readonly base_url: string;
81
81
  /** Whether the receiver is in loopback-mock or proxy-url mode. */
82
82
  readonly mode: 'loopback_mock' | 'proxy_url';
83
+ /** Local address the listener is bound to. */
84
+ readonly bind_host?: string;
83
85
  /** All webhooks captured so far, in arrival order. */
84
86
  all(): CapturedWebhook[];
85
87
  /** Schema-valid proof-of-control challenges, kept separate from event deliveries. */
@@ -114,5 +116,18 @@ export interface CreateWebhookReceiverOptions {
114
116
  port?: number;
115
117
  /** Public URL to advertise when `mode: 'proxy_url'`. */
116
118
  public_url?: string;
119
+ /**
120
+ * Permit an HTTP public URL for controlled local-development topologies.
121
+ */
122
+ allowHttp?: boolean;
123
+ /**
124
+ * TLS material for serving HTTPS directly. Omit when HTTPS terminates at a
125
+ * tunnel or reverse proxy in front of this listener.
126
+ */
127
+ tls?: {
128
+ cert: string | Buffer;
129
+ key: string | Buffer;
130
+ passphrase?: string;
131
+ };
117
132
  }
118
133
  export declare function createWebhookReceiver(options?: CreateWebhookReceiverOptions): Promise<WebhookReceiver>;