@adcp/sdk 16.0.0-beta.7 → 16.0.0-beta.8

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 (75) hide show
  1. package/README.md +17 -0
  2. package/dist/lib/conformance/index.d.mts +2 -0
  3. package/dist/lib/conformance/index.d.ts +2 -0
  4. package/dist/lib/conformance/index.js +3 -0
  5. package/dist/lib/conformance/index.mjs +2 -0
  6. package/dist/lib/conformance/property-lists.d.mts +14 -0
  7. package/dist/lib/conformance/property-lists.d.ts +14 -0
  8. package/dist/lib/conformance/property-lists.js +45 -0
  9. package/dist/lib/conformance/property-lists.mjs +21 -0
  10. package/dist/lib/conformance/runConformance.js +20 -1
  11. package/dist/lib/conformance/runConformance.mjs +20 -1
  12. package/dist/lib/conformance/types.d.mts +3 -0
  13. package/dist/lib/conformance/types.d.ts +3 -0
  14. package/dist/lib/core/SingleAgentClient.d.mts +13 -3
  15. package/dist/lib/core/SingleAgentClient.d.ts +13 -3
  16. package/dist/lib/core/SingleAgentClient.js +109 -21
  17. package/dist/lib/core/SingleAgentClient.mjs +112 -22
  18. package/dist/lib/identifiers.d.mts +53 -0
  19. package/dist/lib/identifiers.d.ts +53 -0
  20. package/dist/lib/identifiers.js +212 -0
  21. package/dist/lib/identifiers.mjs +163 -0
  22. package/dist/lib/index.d.mts +3 -1
  23. package/dist/lib/index.d.ts +3 -1
  24. package/dist/lib/index.js +2 -0
  25. package/dist/lib/index.mjs +1 -0
  26. package/dist/lib/media-buy/index.d.mts +1 -1
  27. package/dist/lib/media-buy/index.d.ts +1 -1
  28. package/dist/lib/media-buy/property-policy.d.mts +15 -1
  29. package/dist/lib/media-buy/property-policy.d.ts +15 -1
  30. package/dist/lib/media-buy/property-policy.js +135 -3
  31. package/dist/lib/media-buy/property-policy.mjs +134 -3
  32. package/dist/lib/registry/index.d.mts +12 -0
  33. package/dist/lib/registry/index.d.ts +12 -0
  34. package/dist/lib/registry/index.js +159 -5
  35. package/dist/lib/registry/index.mjs +156 -4
  36. package/dist/lib/registry/types.d.mts +23 -0
  37. package/dist/lib/registry/types.d.ts +23 -0
  38. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  39. package/dist/lib/server/decisioning/specialisms/lists.d.mts +4 -4
  40. package/dist/lib/server/decisioning/specialisms/lists.d.ts +4 -4
  41. package/dist/lib/server/index.d.mts +3 -0
  42. package/dist/lib/server/index.d.ts +3 -0
  43. package/dist/lib/server/index.js +8 -1
  44. package/dist/lib/server/index.mjs +4 -0
  45. package/dist/lib/server/property-lists.d.mts +36 -0
  46. package/dist/lib/server/property-lists.d.ts +36 -0
  47. package/dist/lib/server/property-lists.js +154 -0
  48. package/dist/lib/server/property-lists.mjs +129 -0
  49. package/dist/lib/server/targeting-helpers.d.mts +2 -0
  50. package/dist/lib/server/targeting-helpers.d.ts +2 -0
  51. package/dist/lib/server/targeting-helpers.js +28 -1
  52. package/dist/lib/server/targeting-helpers.mjs +18 -1
  53. package/dist/lib/signing/parser.js +2 -2
  54. package/dist/lib/signing/parser.mjs +2 -2
  55. package/dist/lib/signing/server.d.mts +1 -1
  56. package/dist/lib/signing/server.d.ts +1 -1
  57. package/dist/lib/signing/server.mjs +5 -1
  58. package/dist/lib/signing/webhook-verifier.d.mts +11 -0
  59. package/dist/lib/signing/webhook-verifier.d.ts +11 -0
  60. package/dist/lib/signing/webhook-verifier.js +11 -3
  61. package/dist/lib/signing/webhook-verifier.mjs +11 -3
  62. package/dist/lib/supply-path/verify.js +12 -2
  63. package/dist/lib/supply-path/verify.mjs +2 -2
  64. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  65. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  66. package/dist/lib/version.d.mts +3 -3
  67. package/dist/lib/version.d.ts +3 -3
  68. package/dist/lib/version.js +3 -3
  69. package/dist/lib/version.mjs +3 -3
  70. package/docs/TYPE-SUMMARY.md +2 -2
  71. package/docs/guides/PROPERTY-LISTS.md +264 -0
  72. package/docs/guides/SIGNING-GUIDE.md +25 -0
  73. package/docs/llms.txt +2 -2
  74. package/docs/migration-14.x-rc-worksheet.md +4 -4
  75. package/package.json +1 -1
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var property_lists_exports = {};
20
+ __export(property_lists_exports, {
21
+ registerPropertyListTool: () => registerPropertyListTool,
22
+ sendPropertyListChanged: () => sendPropertyListChanged
23
+ });
24
+ module.exports = __toCommonJS(property_lists_exports);
25
+ var import_zod = require("zod");
26
+ var import_schemas = require('../types/schemas.generated.js');
27
+ var import_wire_shape = require('./ctx-metadata/wire-shape.js');
28
+ function registerPropertyListTool(server, options) {
29
+ return server.registerTool(
30
+ "get_property_list",
31
+ {
32
+ description: "Retrieve an authorized property list and a page of resolved identifiers.",
33
+ inputSchema: import_schemas.GetPropertyListRequestSchema,
34
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }
35
+ },
36
+ async (request, extra) => {
37
+ const denied = () => toolError("PERMISSION_DENIED", "Property list access denied.", request.context);
38
+ const headers = extra.requestInfo?.headers ?? {};
39
+ for (const name of ["authorization", "x-adcp-auth"]) {
40
+ const values = Object.entries(headers).filter(([key]) => key.toLowerCase() === name);
41
+ if (values.length > 1 || values.length === 1 && typeof values[0][1] !== "string") return denied();
42
+ }
43
+ const readHeader = (name) => {
44
+ const values = Object.entries(headers).filter(([key]) => key.toLowerCase() === name);
45
+ if (values.length !== 1 || typeof values[0][1] !== "string") return void 0;
46
+ return values[0][1];
47
+ };
48
+ const authorization = readHeader("authorization");
49
+ const bearer = authorization?.match(/^Bearer ([^\s]+)$/i)?.[1];
50
+ const adcpToken = readHeader("x-adcp-auth");
51
+ const authenticatedToken = extra.authInfo?.token;
52
+ const tokens = [bearer, adcpToken, authenticatedToken].filter((token) => token !== void 0);
53
+ if (!tokens.length || tokens.some((token) => !token || token !== tokens[0]) || authorization !== void 0 && !bearer)
54
+ return denied();
55
+ let principal;
56
+ try {
57
+ principal = await options.verifyToken({ listId: request.list_id, token: tokens[0] });
58
+ } catch {
59
+ return denied();
60
+ }
61
+ if (principal === null || principal === false || principal === void 0) return denied();
62
+ try {
63
+ const page = await options.loadPage(request, principal);
64
+ if (page.list.list_id !== request.list_id) throw new Error("Invalid list identity");
65
+ if (request.resolve !== false && !Array.isArray(page.identifiers))
66
+ throw new Error("Missing resolved identifiers");
67
+ const limit = request.pagination?.max_results ?? 1e3;
68
+ if (request.resolve !== false && (page.identifiers?.length ?? 0) > limit) throw new Error("Invalid page size");
69
+ if (page.pagination?.has_more && !page.pagination.cursor) throw new Error("Missing continuation cursor");
70
+ if (!page.pagination?.has_more && page.pagination?.cursor) throw new Error("Unexpected continuation cursor");
71
+ const payload = import_schemas.GetPropertyListResponseSchema.parse({
72
+ list: Object.fromEntries(
73
+ [
74
+ "list_id",
75
+ "name",
76
+ "description",
77
+ "account",
78
+ "base_properties",
79
+ "filters",
80
+ "brand",
81
+ "webhook_url",
82
+ "cache_duration_hours",
83
+ "created_at",
84
+ "updated_at",
85
+ "property_count",
86
+ "pricing_options"
87
+ ].filter((key) => Object.hasOwn(page.list, key)).map((key) => [key, page.list[key]])
88
+ ),
89
+ ...page.identifiers !== void 0 ? { identifiers: page.identifiers.map((identifier) => ({ type: identifier.type, value: identifier.value })) } : {},
90
+ ...page.pagination !== void 0 ? {
91
+ pagination: {
92
+ has_more: page.pagination.has_more,
93
+ cursor: page.pagination.cursor,
94
+ total_count: page.pagination.total_count
95
+ }
96
+ } : {},
97
+ ...page.resolved_at !== void 0 ? { resolved_at: page.resolved_at } : {},
98
+ ...page.cache_valid_until !== void 0 ? { cache_valid_until: page.cache_valid_until } : {},
99
+ ...page.coverage_gaps !== void 0 ? { coverage_gaps: page.coverage_gaps } : {},
100
+ status: "completed",
101
+ ...request.resolve === false ? { identifiers: void 0, pagination: void 0 } : {},
102
+ ...request.context ? { context: request.context } : {}
103
+ });
104
+ (0, import_wire_shape.stripCtxMetadata)({ property_list: payload.list });
105
+ return { content: [{ type: "text", text: JSON.stringify(payload) }], structuredContent: payload };
106
+ } catch {
107
+ return toolError("INTERNAL_ERROR", "Property list could not be loaded.", request.context);
108
+ }
109
+ }
110
+ );
111
+ }
112
+ function toolError(code, message, context) {
113
+ const payload = { status: "failed", errors: [{ code, message }], ...context !== void 0 ? { context } : {} };
114
+ return {
115
+ isError: true,
116
+ content: [{ type: "text", text: JSON.stringify(payload) }],
117
+ structuredContent: payload
118
+ };
119
+ }
120
+ async function sendPropertyListChanged(emitter, options) {
121
+ const { notification, ...delivery } = options;
122
+ const dateTime = import_zod.z.string().datetime({ offset: true });
123
+ if (!notification.list_id || !dateTime.safeParse(notification.resolved_at).success || notification.cache_valid_until !== void 0 && !dateTime.safeParse(notification.cache_valid_until).success) {
124
+ throw new TypeError("Property-list notifications require a list_id and valid timestamps");
125
+ }
126
+ const summary = notification.change_summary === void 0 ? void 0 : Object.fromEntries(
127
+ ["properties_added", "properties_removed", "total_properties"].filter(
128
+ (key) => Object.hasOwn(notification.change_summary, key) && notification.change_summary[key] !== void 0
129
+ ).map((key) => [key, notification.change_summary[key]])
130
+ );
131
+ for (const count of Object.values(summary ?? {})) {
132
+ if (typeof count !== "number" || !Number.isSafeInteger(count) || count < 0)
133
+ throw new TypeError("Property-list change counts must be nonnegative integers");
134
+ }
135
+ return emitter.emit({
136
+ ...delivery,
137
+ authentication: null,
138
+ signingProfile: "adcp/webhook-signing/v1",
139
+ payload: {
140
+ event: "property_list_changed",
141
+ list_id: notification.list_id,
142
+ resolved_at: notification.resolved_at,
143
+ ...notification.list_name !== void 0 ? { list_name: notification.list_name } : {},
144
+ ...notification.cache_valid_until !== void 0 ? { cache_valid_until: notification.cache_valid_until } : {},
145
+ ...summary !== void 0 ? { change_summary: summary } : {},
146
+ signature: "rfc9421"
147
+ }
148
+ });
149
+ }
150
+ // Annotate the CommonJS export names for ESM import in node:
151
+ 0 && (module.exports = {
152
+ registerPropertyListTool,
153
+ sendPropertyListChanged
154
+ });
@@ -0,0 +1,129 @@
1
+ import { z } from "zod";
2
+ import { GetPropertyListRequestSchema, GetPropertyListResponseSchema } from "../types/schemas.generated.mjs";
3
+ import { stripCtxMetadata } from "./ctx-metadata/wire-shape.mjs";
4
+ function registerPropertyListTool(server, options) {
5
+ return server.registerTool(
6
+ "get_property_list",
7
+ {
8
+ description: "Retrieve an authorized property list and a page of resolved identifiers.",
9
+ inputSchema: GetPropertyListRequestSchema,
10
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }
11
+ },
12
+ async (request, extra) => {
13
+ const denied = () => toolError("PERMISSION_DENIED", "Property list access denied.", request.context);
14
+ const headers = extra.requestInfo?.headers ?? {};
15
+ for (const name of ["authorization", "x-adcp-auth"]) {
16
+ const values = Object.entries(headers).filter(([key]) => key.toLowerCase() === name);
17
+ if (values.length > 1 || values.length === 1 && typeof values[0][1] !== "string") return denied();
18
+ }
19
+ const readHeader = (name) => {
20
+ const values = Object.entries(headers).filter(([key]) => key.toLowerCase() === name);
21
+ if (values.length !== 1 || typeof values[0][1] !== "string") return void 0;
22
+ return values[0][1];
23
+ };
24
+ const authorization = readHeader("authorization");
25
+ const bearer = authorization?.match(/^Bearer ([^\s]+)$/i)?.[1];
26
+ const adcpToken = readHeader("x-adcp-auth");
27
+ const authenticatedToken = extra.authInfo?.token;
28
+ const tokens = [bearer, adcpToken, authenticatedToken].filter((token) => token !== void 0);
29
+ if (!tokens.length || tokens.some((token) => !token || token !== tokens[0]) || authorization !== void 0 && !bearer)
30
+ return denied();
31
+ let principal;
32
+ try {
33
+ principal = await options.verifyToken({ listId: request.list_id, token: tokens[0] });
34
+ } catch {
35
+ return denied();
36
+ }
37
+ if (principal === null || principal === false || principal === void 0) return denied();
38
+ try {
39
+ const page = await options.loadPage(request, principal);
40
+ if (page.list.list_id !== request.list_id) throw new Error("Invalid list identity");
41
+ if (request.resolve !== false && !Array.isArray(page.identifiers))
42
+ throw new Error("Missing resolved identifiers");
43
+ const limit = request.pagination?.max_results ?? 1e3;
44
+ if (request.resolve !== false && (page.identifiers?.length ?? 0) > limit) throw new Error("Invalid page size");
45
+ if (page.pagination?.has_more && !page.pagination.cursor) throw new Error("Missing continuation cursor");
46
+ if (!page.pagination?.has_more && page.pagination?.cursor) throw new Error("Unexpected continuation cursor");
47
+ const payload = GetPropertyListResponseSchema.parse({
48
+ list: Object.fromEntries(
49
+ [
50
+ "list_id",
51
+ "name",
52
+ "description",
53
+ "account",
54
+ "base_properties",
55
+ "filters",
56
+ "brand",
57
+ "webhook_url",
58
+ "cache_duration_hours",
59
+ "created_at",
60
+ "updated_at",
61
+ "property_count",
62
+ "pricing_options"
63
+ ].filter((key) => Object.hasOwn(page.list, key)).map((key) => [key, page.list[key]])
64
+ ),
65
+ ...page.identifiers !== void 0 ? { identifiers: page.identifiers.map((identifier) => ({ type: identifier.type, value: identifier.value })) } : {},
66
+ ...page.pagination !== void 0 ? {
67
+ pagination: {
68
+ has_more: page.pagination.has_more,
69
+ cursor: page.pagination.cursor,
70
+ total_count: page.pagination.total_count
71
+ }
72
+ } : {},
73
+ ...page.resolved_at !== void 0 ? { resolved_at: page.resolved_at } : {},
74
+ ...page.cache_valid_until !== void 0 ? { cache_valid_until: page.cache_valid_until } : {},
75
+ ...page.coverage_gaps !== void 0 ? { coverage_gaps: page.coverage_gaps } : {},
76
+ status: "completed",
77
+ ...request.resolve === false ? { identifiers: void 0, pagination: void 0 } : {},
78
+ ...request.context ? { context: request.context } : {}
79
+ });
80
+ stripCtxMetadata({ property_list: payload.list });
81
+ return { content: [{ type: "text", text: JSON.stringify(payload) }], structuredContent: payload };
82
+ } catch {
83
+ return toolError("INTERNAL_ERROR", "Property list could not be loaded.", request.context);
84
+ }
85
+ }
86
+ );
87
+ }
88
+ function toolError(code, message, context) {
89
+ const payload = { status: "failed", errors: [{ code, message }], ...context !== void 0 ? { context } : {} };
90
+ return {
91
+ isError: true,
92
+ content: [{ type: "text", text: JSON.stringify(payload) }],
93
+ structuredContent: payload
94
+ };
95
+ }
96
+ async function sendPropertyListChanged(emitter, options) {
97
+ const { notification, ...delivery } = options;
98
+ const dateTime = z.string().datetime({ offset: true });
99
+ if (!notification.list_id || !dateTime.safeParse(notification.resolved_at).success || notification.cache_valid_until !== void 0 && !dateTime.safeParse(notification.cache_valid_until).success) {
100
+ throw new TypeError("Property-list notifications require a list_id and valid timestamps");
101
+ }
102
+ const summary = notification.change_summary === void 0 ? void 0 : Object.fromEntries(
103
+ ["properties_added", "properties_removed", "total_properties"].filter(
104
+ (key) => Object.hasOwn(notification.change_summary, key) && notification.change_summary[key] !== void 0
105
+ ).map((key) => [key, notification.change_summary[key]])
106
+ );
107
+ for (const count of Object.values(summary ?? {})) {
108
+ if (typeof count !== "number" || !Number.isSafeInteger(count) || count < 0)
109
+ throw new TypeError("Property-list change counts must be nonnegative integers");
110
+ }
111
+ return emitter.emit({
112
+ ...delivery,
113
+ authentication: null,
114
+ signingProfile: "adcp/webhook-signing/v1",
115
+ payload: {
116
+ event: "property_list_changed",
117
+ list_id: notification.list_id,
118
+ resolved_at: notification.resolved_at,
119
+ ...notification.list_name !== void 0 ? { list_name: notification.list_name } : {},
120
+ ...notification.cache_valid_until !== void 0 ? { cache_valid_until: notification.cache_valid_until } : {},
121
+ ...summary !== void 0 ? { change_summary: summary } : {},
122
+ signature: "rfc9421"
123
+ }
124
+ });
125
+ }
126
+ export {
127
+ registerPropertyListTool,
128
+ sendPropertyListChanged
129
+ };
@@ -56,6 +56,8 @@ export declare const defaultResolvedListCache: ResolvedListCache;
56
56
  export declare function clearDefaultResolvedListCache(): void;
57
57
  export type ResolveListCallTool = (agentUrl: string, toolName: 'get_property_list' | 'get_collection_list', args: Record<string, unknown>, authToken?: string) => Promise<unknown>;
58
58
  export interface ResolveListOptions {
59
+ /** Require schema-valid property-list pages with identifiers when resolving. Used for certification. */
60
+ strictResponseValidation?: boolean;
59
61
  /** Maximum page size when calling get_property_list / get_collection_list. Defaults to 1000. */
60
62
  pageSize?: number;
61
63
  /** Maximum number of pages to walk before treating pagination as unsafe. Defaults to 100. */
@@ -56,6 +56,8 @@ export declare const defaultResolvedListCache: ResolvedListCache;
56
56
  export declare function clearDefaultResolvedListCache(): void;
57
57
  export type ResolveListCallTool = (agentUrl: string, toolName: 'get_property_list' | 'get_collection_list', args: Record<string, unknown>, authToken?: string) => Promise<unknown>;
58
58
  export interface ResolveListOptions {
59
+ /** Require schema-valid property-list pages with identifiers when resolving. Used for certification. */
60
+ strictResponseValidation?: boolean;
59
61
  /** Maximum page size when calling get_property_list / get_collection_list. Defaults to 1000. */
60
62
  pageSize?: number;
61
63
  /** Maximum number of pages to walk before treating pagination as unsafe. Defaults to 100. */
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
  var targeting_helpers_exports = {};
20
30
  __export(targeting_helpers_exports, {
@@ -75,7 +85,7 @@ async function resolvePropertyList(ref, options = {}) {
75
85
  const cache = cacheForOptions(options);
76
86
  const nowMs = nowMsForOptions(options);
77
87
  const cacheKey = resolvedListCacheKey("property", structurallyNormalizedRef, options);
78
- const cached = cacheKey ? cache?.get(cacheKey, nowMs) : void 0;
88
+ const cached = cacheKey && !options.strictResponseValidation ? cache?.get(cacheKey, nowMs) : void 0;
79
89
  if (cached) return cached;
80
90
  const normalizedRef = options.callTool ? structurallyNormalizedRef : await normalizePropertyListReference(structurallyNormalizedRef, { resolveDns: false });
81
91
  const result = await fetchResolvedPropertyList(normalizedRef, options);
@@ -89,6 +99,7 @@ async function fetchResolvedPropertyList(ref, options) {
89
99
  let cacheValidUntil;
90
100
  let missingCacheValidUntil = false;
91
101
  let cursor;
102
+ const seenCursors = /* @__PURE__ */ new Set();
92
103
  for (let pageIndex = 0; pageIndex < maxPagesForOptions(options); pageIndex += 1) {
93
104
  const args = buildResolveListArgs(ref.list_id, pageSizeForOptions(options), cursor);
94
105
  const raw = await callTool(ref.agent_url, "get_property_list", args, ref.auth_token);
@@ -96,6 +107,20 @@ async function fetchResolvedPropertyList(ref, options) {
96
107
  throw new Error("property_list_invalid_response");
97
108
  }
98
109
  const response = raw;
110
+ if (response.status !== void 0 && response.status !== "completed" || raw.errors !== void 0 && (!Array.isArray(raw.errors) || raw.errors.some((error) => !error || typeof error !== "object" || error.severity !== "warning")) || raw.adcp_error !== void 0 || !Array.isArray(response.identifiers)) {
111
+ throw new Error("property_list_invalid_response");
112
+ }
113
+ if (options.strictResponseValidation) {
114
+ const { GetPropertyListResponseSchema } = await import("../types/schemas.generated.js");
115
+ if (!GetPropertyListResponseSchema.safeParse(raw).success || response.status !== "completed" || response.identifiers.length > pageSizeForOptions(options)) {
116
+ throw new Error("property_list_invalid_response");
117
+ }
118
+ }
119
+ if (!response.list || response.list.list_id !== ref.list_id || response.identifiers !== void 0 && (!Array.isArray(response.identifiers) || response.identifiers.some(
120
+ (identifier) => !identifier || typeof identifier !== "object" || typeof identifier.type !== "string" || typeof identifier.value !== "string"
121
+ ))) {
122
+ throw new Error("property_list_invalid_response");
123
+ }
99
124
  listId = response.list.list_id;
100
125
  identifiers.push(...response.identifiers ?? []);
101
126
  if (typeof response.cache_valid_until === "string") {
@@ -118,6 +143,8 @@ async function fetchResolvedPropertyList(ref, options) {
118
143
  if (!pagination.cursor) {
119
144
  throw new Error("property_list_invalid_pagination");
120
145
  }
146
+ if (seenCursors.has(pagination.cursor)) throw new Error("property_list_invalid_pagination");
147
+ seenCursors.add(pagination.cursor);
121
148
  cursor = pagination.cursor;
122
149
  }
123
150
  throw new Error("property_list_pagination_limit_exceeded");
@@ -44,7 +44,7 @@ async function resolvePropertyList(ref, options = {}) {
44
44
  const cache = cacheForOptions(options);
45
45
  const nowMs = nowMsForOptions(options);
46
46
  const cacheKey = resolvedListCacheKey("property", structurallyNormalizedRef, options);
47
- const cached = cacheKey ? cache?.get(cacheKey, nowMs) : void 0;
47
+ const cached = cacheKey && !options.strictResponseValidation ? cache?.get(cacheKey, nowMs) : void 0;
48
48
  if (cached) return cached;
49
49
  const normalizedRef = options.callTool ? structurallyNormalizedRef : await normalizePropertyListReference(structurallyNormalizedRef, { resolveDns: false });
50
50
  const result = await fetchResolvedPropertyList(normalizedRef, options);
@@ -58,6 +58,7 @@ async function fetchResolvedPropertyList(ref, options) {
58
58
  let cacheValidUntil;
59
59
  let missingCacheValidUntil = false;
60
60
  let cursor;
61
+ const seenCursors = /* @__PURE__ */ new Set();
61
62
  for (let pageIndex = 0; pageIndex < maxPagesForOptions(options); pageIndex += 1) {
62
63
  const args = buildResolveListArgs(ref.list_id, pageSizeForOptions(options), cursor);
63
64
  const raw = await callTool(ref.agent_url, "get_property_list", args, ref.auth_token);
@@ -65,6 +66,20 @@ async function fetchResolvedPropertyList(ref, options) {
65
66
  throw new Error("property_list_invalid_response");
66
67
  }
67
68
  const response = raw;
69
+ if (response.status !== void 0 && response.status !== "completed" || raw.errors !== void 0 && (!Array.isArray(raw.errors) || raw.errors.some((error) => !error || typeof error !== "object" || error.severity !== "warning")) || raw.adcp_error !== void 0 || !Array.isArray(response.identifiers)) {
70
+ throw new Error("property_list_invalid_response");
71
+ }
72
+ if (options.strictResponseValidation) {
73
+ const { GetPropertyListResponseSchema } = await import("../types/schemas.generated.mjs");
74
+ if (!GetPropertyListResponseSchema.safeParse(raw).success || response.status !== "completed" || response.identifiers.length > pageSizeForOptions(options)) {
75
+ throw new Error("property_list_invalid_response");
76
+ }
77
+ }
78
+ if (!response.list || response.list.list_id !== ref.list_id || response.identifiers !== void 0 && (!Array.isArray(response.identifiers) || response.identifiers.some(
79
+ (identifier) => !identifier || typeof identifier !== "object" || typeof identifier.type !== "string" || typeof identifier.value !== "string"
80
+ ))) {
81
+ throw new Error("property_list_invalid_response");
82
+ }
68
83
  listId = response.list.list_id;
69
84
  identifiers.push(...response.identifiers ?? []);
70
85
  if (typeof response.cache_valid_until === "string") {
@@ -87,6 +102,8 @@ async function fetchResolvedPropertyList(ref, options) {
87
102
  if (!pagination.cursor) {
88
103
  throw new Error("property_list_invalid_pagination");
89
104
  }
105
+ if (seenCursors.has(pagination.cursor)) throw new Error("property_list_invalid_pagination");
106
+ seenCursors.add(pagination.cursor);
90
107
  cursor = pagination.cursor;
91
108
  }
92
109
  throw new Error("property_list_pagination_limit_exceeded");
@@ -86,10 +86,10 @@ function parseSignature(headerValue, expectedLabel, encoding = "legacy-base64url
86
86
  if (!rawBytes) malformed(`Signature header does not contain a byte sequence for label "${expectedLabel}"`);
87
87
  if (encoding === "rfc8941-base64") {
88
88
  if (!(0, import_content_digest.isPaddedStandardBase64)(rawBytes)) {
89
- malformed("Signature value must use padded standard Base64 for AdCP 3.2+");
89
+ malformed("Signature value must use padded standard Base64 for the selected signing profile");
90
90
  }
91
91
  } else if (!/^[A-Za-z0-9_-]+$/.test(rawBytes)) {
92
- malformed("Signature value must use unpadded Base64URL for AdCP 3.0/3.1");
92
+ malformed("Signature value must use unpadded Base64URL for the selected signing profile");
93
93
  }
94
94
  let dict;
95
95
  try {
@@ -60,10 +60,10 @@ function parseSignature(headerValue, expectedLabel, encoding = "legacy-base64url
60
60
  if (!rawBytes) malformed(`Signature header does not contain a byte sequence for label "${expectedLabel}"`);
61
61
  if (encoding === "rfc8941-base64") {
62
62
  if (!isPaddedStandardBase64(rawBytes)) {
63
- malformed("Signature value must use padded standard Base64 for AdCP 3.2+");
63
+ malformed("Signature value must use padded standard Base64 for the selected signing profile");
64
64
  }
65
65
  } else if (!/^[A-Za-z0-9_-]+$/.test(rawBytes)) {
66
- malformed("Signature value must use unpadded Base64URL for AdCP 3.0/3.1");
66
+ malformed("Signature value must use unpadded Base64URL for the selected signing profile");
67
67
  }
68
68
  let dict;
69
69
  try {
@@ -10,7 +10,7 @@
10
10
  */
11
11
  export type { SsrfDnsLookup } from '../net/index.mjs';
12
12
  export { buildResponseSignatureBase, buildSignatureBase, canonicalAuthority, canonicalMethod, canonicalTargetUri, formatSignatureParams, getHeaderValue, type RequestLike, type RequestCanonicalizationProfile, type ResponseLike, type SignatureParams, } from './canonicalize.mjs';
13
- export { computeContentDigest, contentDigestMatches, parseContentDigest } from './content-digest.mjs';
13
+ export { computeContentDigest, contentDigestMatches, parseContentDigest, type SfBinaryEncoding, } from './content-digest.mjs';
14
14
  export { requestContextFromExpress, requestContextFromFetch, requestContextFromLambda, type ExpressRequestLike, type FetchRequestLike, type LambdaRequestEvent, type RequestContextFromExpressOptions, type RequestContextFromLambdaOptions, } from './request-context.mjs';
15
15
  export { jwkToPublicKey, verifySignature, verifySignatureAsync } from './crypto.mjs';
16
16
  export { RequestSignatureError, RequestSigningErrorCodeMetadata, type RequestSignatureErrorCode, ResponseSignatureError, type ResponseSignatureErrorCode, WebhookSignatureError, type WebhookSignatureErrorCode, } from './errors.mjs';
@@ -10,7 +10,7 @@
10
10
  */
11
11
  export type { SsrfDnsLookup } from '../net';
12
12
  export { buildResponseSignatureBase, buildSignatureBase, canonicalAuthority, canonicalMethod, canonicalTargetUri, formatSignatureParams, getHeaderValue, type RequestLike, type RequestCanonicalizationProfile, type ResponseLike, type SignatureParams, } from './canonicalize';
13
- export { computeContentDigest, contentDigestMatches, parseContentDigest } from './content-digest';
13
+ export { computeContentDigest, contentDigestMatches, parseContentDigest, type SfBinaryEncoding, } from './content-digest';
14
14
  export { requestContextFromExpress, requestContextFromFetch, requestContextFromLambda, type ExpressRequestLike, type FetchRequestLike, type LambdaRequestEvent, type RequestContextFromExpressOptions, type RequestContextFromLambdaOptions, } from './request-context';
15
15
  export { jwkToPublicKey, verifySignature, verifySignatureAsync } from './crypto';
16
16
  export { RequestSignatureError, RequestSigningErrorCodeMetadata, type RequestSignatureErrorCode, ResponseSignatureError, type ResponseSignatureErrorCode, WebhookSignatureError, type WebhookSignatureErrorCode, } from './errors';
@@ -7,7 +7,11 @@ import {
7
7
  formatSignatureParams,
8
8
  getHeaderValue
9
9
  } from "./canonicalize.mjs";
10
- import { computeContentDigest, contentDigestMatches, parseContentDigest } from "./content-digest.mjs";
10
+ import {
11
+ computeContentDigest,
12
+ contentDigestMatches,
13
+ parseContentDigest
14
+ } from "./content-digest.mjs";
11
15
  import {
12
16
  requestContextFromExpress,
13
17
  requestContextFromFetch,
@@ -22,6 +22,7 @@
22
22
  * so failures can point at a specific spec clause. Numbers are 1-based to
23
23
  * match the security doc.
24
24
  */
25
+ import { type SfBinaryEncoding } from './content-digest.mjs';
25
26
  import type { JwksResolver } from './jwks.mjs';
26
27
  import { type PublisherSigningKeyPin } from './publisher-pins.mjs';
27
28
  import { type ReplayStore } from './replay.mjs';
@@ -43,6 +44,16 @@ export interface WebhookRequestLike {
43
44
  export declare const WEBHOOK_MANDATORY_COMPONENTS: ReadonlyArray<string>;
44
45
  export interface VerifyWebhookOptions {
45
46
  jwks: JwksResolver;
47
+ /**
48
+ * Trusted receiver configuration for the Signature byte sequence.
49
+ * Defaults to `legacy-base64url`, as the AdCP webhook-v1 profile requires
50
+ * throughout 3.x. Set `rfc8941-base64` only for an explicitly configured
51
+ * peer emitting standard padded Base64 (for example, a generic RFC 9421
52
+ * request signer with the webhook tag). There is no encoding fallback.
53
+ * URL canonicalization remains AdCP 3.2 in either mode; Signature encoding
54
+ * does not identify a webhook's AdCP version.
55
+ */
56
+ signatureEncoding?: SfBinaryEncoding;
46
57
  /** Applicable publishers from the verifier's own media-buy record. */
47
58
  publisherPins?: readonly PublisherSigningKeyPin[];
48
59
  /** Log the underlying request_signature_* discovery cause locally. */
@@ -22,6 +22,7 @@
22
22
  * so failures can point at a specific spec clause. Numbers are 1-based to
23
23
  * match the security doc.
24
24
  */
25
+ import { type SfBinaryEncoding } from './content-digest';
25
26
  import type { JwksResolver } from './jwks';
26
27
  import { type PublisherSigningKeyPin } from './publisher-pins';
27
28
  import { type ReplayStore } from './replay';
@@ -43,6 +44,16 @@ export interface WebhookRequestLike {
43
44
  export declare const WEBHOOK_MANDATORY_COMPONENTS: ReadonlyArray<string>;
44
45
  export interface VerifyWebhookOptions {
45
46
  jwks: JwksResolver;
47
+ /**
48
+ * Trusted receiver configuration for the Signature byte sequence.
49
+ * Defaults to `legacy-base64url`, as the AdCP webhook-v1 profile requires
50
+ * throughout 3.x. Set `rfc8941-base64` only for an explicitly configured
51
+ * peer emitting standard padded Base64 (for example, a generic RFC 9421
52
+ * request signer with the webhook tag). There is no encoding fallback.
53
+ * URL canonicalization remains AdCP 3.2 in either mode; Signature encoding
54
+ * does not identify a webhook's AdCP version.
55
+ */
56
+ signatureEncoding?: SfBinaryEncoding;
46
57
  /** Applicable publishers from the verifier's own media-buy record. */
47
58
  publisherPins?: readonly PublisherSigningKeyPin[];
48
59
  /** Log the underlying request_signature_* discovery cause locally. */
@@ -50,6 +50,7 @@ const WEBHOOK_MANDATORY_COMPONENTS = [
50
50
  "content-digest"
51
51
  ];
52
52
  async function verifyWebhookSignature(request, options) {
53
+ const signatureEncoding = resolveSignatureEncoding(options.signatureEncoding);
53
54
  const currentTime = options.now ?? (() => Date.now() / 1e3);
54
55
  const now = currentTime();
55
56
  const requiredTag = options.requiredTag ?? WEBHOOK_SIGNING_TAG;
@@ -66,12 +67,12 @@ async function verifyWebhookSignature(request, options) {
66
67
  let parsedSig;
67
68
  try {
68
69
  parsedInput = (0, import_parser.parseSignatureInput)(sigInputHeader);
69
- parsedSig = (0, import_parser.parseSignature)(sigHeader, parsedInput.label);
70
+ parsedSig = (0, import_parser.parseSignature)(sigHeader, parsedInput.label, signatureEncoding);
70
71
  } catch (err) {
71
72
  throw new import_errors.WebhookSignatureError(
72
73
  "webhook_signature_header_malformed",
73
74
  1,
74
- err instanceof Error ? err.message : String(err)
75
+ `${err instanceof Error ? err.message : String(err)} (webhook signatureEncoding="${signatureEncoding}").`
75
76
  );
76
77
  }
77
78
  requireParams(parsedInput);
@@ -239,6 +240,12 @@ async function verifyWebhookSignature(request, options) {
239
240
  verified_at: Math.floor(now)
240
241
  };
241
242
  }
243
+ function resolveSignatureEncoding(encoding = "legacy-base64url") {
244
+ if (encoding !== "legacy-base64url" && encoding !== "rfc8941-base64") {
245
+ throw new TypeError('signatureEncoding must be "legacy-base64url" or "rfc8941-base64".');
246
+ }
247
+ return encoding;
248
+ }
242
249
  function assertDelegatedOperatorAuthorizationActive(validUntil, now) {
243
250
  if (validUntil !== void 0 && (!Number.isFinite(validUntil) || now >= validUntil)) {
244
251
  throw new import_errors.WebhookSignatureError(
@@ -345,9 +352,10 @@ function isWebhookLoopbackHost(hostname) {
345
352
  return LOOPBACK_IPV4.test(normalized);
346
353
  }
347
354
  function createWebhookVerifier(options) {
355
+ const signatureEncoding = resolveSignatureEncoding(options.signatureEncoding);
348
356
  const replayStore = options.replayStore ?? new import_replay.InMemoryReplayStore();
349
357
  const revocationStore = options.revocationStore ?? new import_revocation.InMemoryRevocationStore();
350
- return (request) => verifyWebhookSignature(request, { ...options, replayStore, revocationStore });
358
+ return (request) => verifyWebhookSignature(request, { ...options, signatureEncoding, replayStore, revocationStore });
351
359
  }
352
360
  // Annotate the CommonJS export names for ESM import in node:
353
361
  0 && (module.exports = {
@@ -23,6 +23,7 @@ const WEBHOOK_MANDATORY_COMPONENTS = [
23
23
  "content-digest"
24
24
  ];
25
25
  async function verifyWebhookSignature(request, options) {
26
+ const signatureEncoding = resolveSignatureEncoding(options.signatureEncoding);
26
27
  const currentTime = options.now ?? (() => Date.now() / 1e3);
27
28
  const now = currentTime();
28
29
  const requiredTag = options.requiredTag ?? WEBHOOK_SIGNING_TAG;
@@ -39,12 +40,12 @@ async function verifyWebhookSignature(request, options) {
39
40
  let parsedSig;
40
41
  try {
41
42
  parsedInput = parseSignatureInput(sigInputHeader);
42
- parsedSig = parseSignature(sigHeader, parsedInput.label);
43
+ parsedSig = parseSignature(sigHeader, parsedInput.label, signatureEncoding);
43
44
  } catch (err) {
44
45
  throw new WebhookSignatureError(
45
46
  "webhook_signature_header_malformed",
46
47
  1,
47
- err instanceof Error ? err.message : String(err)
48
+ `${err instanceof Error ? err.message : String(err)} (webhook signatureEncoding="${signatureEncoding}").`
48
49
  );
49
50
  }
50
51
  requireParams(parsedInput);
@@ -212,6 +213,12 @@ async function verifyWebhookSignature(request, options) {
212
213
  verified_at: Math.floor(now)
213
214
  };
214
215
  }
216
+ function resolveSignatureEncoding(encoding = "legacy-base64url") {
217
+ if (encoding !== "legacy-base64url" && encoding !== "rfc8941-base64") {
218
+ throw new TypeError('signatureEncoding must be "legacy-base64url" or "rfc8941-base64".');
219
+ }
220
+ return encoding;
221
+ }
215
222
  function assertDelegatedOperatorAuthorizationActive(validUntil, now) {
216
223
  if (validUntil !== void 0 && (!Number.isFinite(validUntil) || now >= validUntil)) {
217
224
  throw new WebhookSignatureError(
@@ -318,9 +325,10 @@ function isWebhookLoopbackHost(hostname) {
318
325
  return LOOPBACK_IPV4.test(normalized);
319
326
  }
320
327
  function createWebhookVerifier(options) {
328
+ const signatureEncoding = resolveSignatureEncoding(options.signatureEncoding);
321
329
  const replayStore = options.replayStore ?? new InMemoryReplayStore();
322
330
  const revocationStore = options.revocationStore ?? new InMemoryRevocationStore();
323
- return (request) => verifyWebhookSignature(request, { ...options, replayStore, revocationStore });
331
+ return (request) => verifyWebhookSignature(request, { ...options, signatureEncoding, replayStore, revocationStore });
324
332
  }
325
333
  export {
326
334
  WEBHOOK_MANDATORY_COMPONENTS,