@adcp/sdk 16.2.0 → 16.4.0

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 (161) hide show
  1. package/AGENTS.md +2 -0
  2. package/dist/lib/adapters/version/3.1/assignments.d.mts +12 -0
  3. package/dist/lib/adapters/version/3.1/assignments.d.ts +12 -0
  4. package/dist/lib/adapters/version/3.1/assignments.js +67 -0
  5. package/dist/lib/adapters/version/3.1/assignments.mjs +43 -0
  6. package/dist/lib/adapters/version/3.1/bidding.d.mts +3 -0
  7. package/dist/lib/adapters/version/3.1/bidding.d.ts +3 -0
  8. package/dist/lib/adapters/version/3.1/bidding.js +115 -0
  9. package/dist/lib/adapters/version/3.1/bidding.mjs +90 -0
  10. package/dist/lib/adapters/version/3.1/delivery.d.mts +7 -0
  11. package/dist/lib/adapters/version/3.1/delivery.d.ts +7 -0
  12. package/dist/lib/adapters/version/3.1/delivery.js +78 -0
  13. package/dist/lib/adapters/version/3.1/delivery.mjs +54 -0
  14. package/dist/lib/adapters/version/3.1/index.d.mts +3 -0
  15. package/dist/lib/adapters/version/3.1/index.d.ts +3 -0
  16. package/dist/lib/adapters/version/3.1/index.js +36 -0
  17. package/dist/lib/adapters/version/3.1/index.mjs +12 -0
  18. package/dist/lib/adapters/version/index.d.mts +59 -7
  19. package/dist/lib/adapters/version/index.d.ts +59 -7
  20. package/dist/lib/adapters/version/index.js +51 -2
  21. package/dist/lib/adapters/version/index.mjs +49 -1
  22. package/dist/lib/adapters/version/negotiate.d.mts +71 -0
  23. package/dist/lib/adapters/version/negotiate.d.ts +71 -0
  24. package/dist/lib/adapters/version/negotiate.js +101 -0
  25. package/dist/lib/adapters/version/negotiate.mjs +73 -0
  26. package/dist/lib/adapters/version/schema-projection.d.mts +33 -0
  27. package/dist/lib/adapters/version/schema-projection.d.ts +33 -0
  28. package/dist/lib/adapters/version/schema-projection.js +294 -0
  29. package/dist/lib/adapters/version/schema-projection.mjs +269 -0
  30. package/dist/lib/adapters/version/translate.d.mts +122 -0
  31. package/dist/lib/adapters/version/translate.d.ts +122 -0
  32. package/dist/lib/adapters/version/translate.js +169 -0
  33. package/dist/lib/adapters/version/translate.mjs +141 -0
  34. package/dist/lib/adapters/version/types.d.mts +51 -10
  35. package/dist/lib/adapters/version/types.d.ts +51 -10
  36. package/dist/lib/core/AgentClient.d.mts +1 -1
  37. package/dist/lib/core/AgentClient.d.ts +1 -1
  38. package/dist/lib/core/AgentClient.js +17 -1
  39. package/dist/lib/core/AgentClient.mjs +17 -1
  40. package/dist/lib/core/ConversationTypes.d.mts +7 -0
  41. package/dist/lib/core/ConversationTypes.d.ts +7 -0
  42. package/dist/lib/core/SingleAgentClient.d.mts +82 -0
  43. package/dist/lib/core/SingleAgentClient.d.ts +82 -0
  44. package/dist/lib/core/SingleAgentClient.js +336 -15
  45. package/dist/lib/core/SingleAgentClient.mjs +350 -17
  46. package/dist/lib/core/TaskExecutor.d.mts +1 -1
  47. package/dist/lib/core/TaskExecutor.d.ts +1 -1
  48. package/dist/lib/core/TaskExecutor.js +182 -31
  49. package/dist/lib/core/TaskExecutor.mjs +183 -32
  50. package/dist/lib/core/account-reduction.d.mts +36 -0
  51. package/dist/lib/core/account-reduction.d.ts +36 -0
  52. package/dist/lib/core/account-reduction.js +127 -0
  53. package/dist/lib/core/account-reduction.mjs +98 -0
  54. package/dist/lib/core/buyer-account-registry.d.mts +52 -0
  55. package/dist/lib/core/buyer-account-registry.d.ts +52 -0
  56. package/dist/lib/core/buyer-account-registry.js +142 -6
  57. package/dist/lib/core/buyer-account-registry.mjs +142 -6
  58. package/dist/lib/errors/index.d.mts +34 -0
  59. package/dist/lib/errors/index.d.ts +34 -0
  60. package/dist/lib/errors/index.js +11 -0
  61. package/dist/lib/errors/index.mjs +10 -0
  62. package/dist/lib/index.d.mts +1 -1
  63. package/dist/lib/index.d.ts +1 -1
  64. package/dist/lib/index.js +2 -0
  65. package/dist/lib/index.mjs +2 -0
  66. package/dist/lib/media-buy/action-contracts.js +4 -4
  67. package/dist/lib/media-buy/action-contracts.mjs +4 -4
  68. package/dist/lib/media-buy/action-types.d.mts +3 -2
  69. package/dist/lib/media-buy/action-types.d.ts +3 -2
  70. package/dist/lib/media-buy/compatibility.d.mts +1 -0
  71. package/dist/lib/media-buy/compatibility.d.ts +1 -0
  72. package/dist/lib/media-buy/compatibility.js +33 -10
  73. package/dist/lib/media-buy/compatibility.mjs +32 -14
  74. package/dist/lib/media-buy/update-fields.generated.d.mts +3 -2
  75. package/dist/lib/media-buy/update-fields.generated.d.ts +3 -2
  76. package/dist/lib/negotiation/buyer.js +11 -5
  77. package/dist/lib/negotiation/buyer.mjs +11 -5
  78. package/dist/lib/negotiation/types.d.mts +4 -2
  79. package/dist/lib/negotiation/types.d.ts +4 -2
  80. package/dist/lib/negotiation/verification.d.mts +15 -6
  81. package/dist/lib/negotiation/verification.d.ts +15 -6
  82. package/dist/lib/negotiation/verification.js +146 -27
  83. package/dist/lib/negotiation/verification.mjs +146 -28
  84. package/dist/lib/negotiation/version.d.mts +6 -0
  85. package/dist/lib/negotiation/version.d.ts +6 -0
  86. package/dist/lib/negotiation/version.js +60 -0
  87. package/dist/lib/negotiation/version.mjs +33 -0
  88. package/dist/lib/registry/types.generated.d.mts +1 -1
  89. package/dist/lib/registry/types.generated.d.ts +1 -1
  90. package/dist/lib/server/create-adcp-server.d.mts +21 -0
  91. package/dist/lib/server/create-adcp-server.d.ts +21 -0
  92. package/dist/lib/server/create-adcp-server.js +98 -16
  93. package/dist/lib/server/create-adcp-server.mjs +84 -2
  94. package/dist/lib/server/error-arm-tools.d.mts +10 -10
  95. package/dist/lib/server/error-arm-tools.d.ts +10 -10
  96. package/dist/lib/server/error-arm-tools.js +22 -58
  97. package/dist/lib/server/error-arm-tools.mjs +30 -65
  98. package/dist/lib/server/index.d.mts +1 -1
  99. package/dist/lib/server/index.d.ts +1 -1
  100. package/dist/lib/storage/interfaces.d.mts +2 -0
  101. package/dist/lib/storage/interfaces.d.ts +2 -0
  102. package/dist/lib/testing/compliance/comply.js +17 -0
  103. package/dist/lib/testing/compliance/comply.mjs +17 -0
  104. package/dist/lib/testing/index.d.mts +3 -0
  105. package/dist/lib/testing/index.d.ts +3 -0
  106. package/dist/lib/testing/index.js +44 -2
  107. package/dist/lib/testing/index.mjs +42 -0
  108. package/dist/lib/testing/storyboard/collection-selection.d.mts +259 -0
  109. package/dist/lib/testing/storyboard/collection-selection.d.ts +259 -0
  110. package/dist/lib/testing/storyboard/collection-selection.js +384 -0
  111. package/dist/lib/testing/storyboard/collection-selection.mjs +348 -0
  112. package/dist/lib/testing/storyboard/context.js +12 -6
  113. package/dist/lib/testing/storyboard/context.mjs +12 -6
  114. package/dist/lib/testing/storyboard/fixture-resolution.d.mts +39 -1
  115. package/dist/lib/testing/storyboard/fixture-resolution.d.ts +39 -1
  116. package/dist/lib/testing/storyboard/fixture-resolution.js +216 -12
  117. package/dist/lib/testing/storyboard/fixture-resolution.mjs +218 -12
  118. package/dist/lib/testing/storyboard/index.d.mts +9 -2
  119. package/dist/lib/testing/storyboard/index.d.ts +9 -2
  120. package/dist/lib/testing/storyboard/index.js +51 -2
  121. package/dist/lib/testing/storyboard/index.mjs +49 -1
  122. package/dist/lib/testing/storyboard/prepared-fixtures.d.mts +471 -0
  123. package/dist/lib/testing/storyboard/prepared-fixtures.d.ts +471 -0
  124. package/dist/lib/testing/storyboard/prepared-fixtures.js +434 -0
  125. package/dist/lib/testing/storyboard/prepared-fixtures.mjs +389 -0
  126. package/dist/lib/testing/storyboard/protected-context.d.mts +127 -0
  127. package/dist/lib/testing/storyboard/protected-context.d.ts +127 -0
  128. package/dist/lib/testing/storyboard/protected-context.js +301 -0
  129. package/dist/lib/testing/storyboard/protected-context.mjs +267 -0
  130. package/dist/lib/testing/storyboard/rejection-hints.js +13 -4
  131. package/dist/lib/testing/storyboard/rejection-hints.mjs +13 -4
  132. package/dist/lib/testing/storyboard/runner.d.mts +24 -0
  133. package/dist/lib/testing/storyboard/runner.d.ts +24 -0
  134. package/dist/lib/testing/storyboard/runner.js +1061 -105
  135. package/dist/lib/testing/storyboard/runner.mjs +1075 -106
  136. package/dist/lib/testing/storyboard/seeding.d.mts +15 -1
  137. package/dist/lib/testing/storyboard/seeding.d.ts +15 -1
  138. package/dist/lib/testing/storyboard/seeding.js +272 -12
  139. package/dist/lib/testing/storyboard/seeding.mjs +274 -13
  140. package/dist/lib/testing/storyboard/types.d.mts +207 -10
  141. package/dist/lib/testing/storyboard/types.d.ts +207 -10
  142. package/dist/lib/testing/storyboard/types.js +1 -1
  143. package/dist/lib/testing/storyboard/types.mjs +1 -1
  144. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  145. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  146. package/dist/lib/testing/storyboard/validations.js +95 -0
  147. package/dist/lib/testing/storyboard/validations.mjs +95 -0
  148. package/dist/lib/validation/bundled-schema-store.js +5 -0
  149. package/dist/lib/validation/bundled-schema-store.mjs +4 -0
  150. package/dist/lib/validation/schema-loader.js +19 -2
  151. package/dist/lib/validation/schema-loader.mjs +22 -3
  152. package/dist/lib/version.d.mts +3 -3
  153. package/dist/lib/version.d.ts +3 -3
  154. package/dist/lib/version.js +3 -3
  155. package/dist/lib/version.mjs +3 -3
  156. package/docs/TYPE-SUMMARY.md +2 -2
  157. package/docs/guides/COLLECTION-FIXTURE-FOUNDATION.md +850 -0
  158. package/docs/guides/PROPOSAL-TERMS-VERIFICATION.md +66 -0
  159. package/docs/llms.txt +4 -3
  160. package/docs/migration-14.x-rc-worksheet.md +4 -4
  161. package/package.json +1 -1
@@ -0,0 +1,98 @@
1
+ import { accountReferenceKey } from "./account-key.mjs";
2
+ function isObject(value) {
3
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4
+ }
5
+ const REDUCIBLE_FIELDS = ["operator_unit", "currency", "timezone"];
6
+ function naturalAccountKey(value) {
7
+ if (!isObject(value) || typeof value.account_id === "string") return void 0;
8
+ const brand = value.brand;
9
+ if (!isObject(brand) || typeof brand.domain !== "string" || typeof value.operator !== "string") return void 0;
10
+ return {
11
+ brand: {
12
+ domain: brand.domain,
13
+ ...typeof brand.brand_id === "string" && { brand_id: brand.brand_id },
14
+ ...Array.isArray(brand.countries) && { countries: brand.countries }
15
+ },
16
+ operator: value.operator,
17
+ ...isObject(value.operator_unit) && { operator_unit: value.operator_unit },
18
+ ...typeof value.currency === "string" && { currency: value.currency },
19
+ ...typeof value.timezone === "string" && { timezone: value.timezone },
20
+ ...typeof value.sandbox === "boolean" && { sandbox: value.sandbox }
21
+ };
22
+ }
23
+ function accountKeySites(params) {
24
+ const sites = [];
25
+ const visit = (value, path, depth) => {
26
+ if (depth > 8) return;
27
+ if (Array.isArray(value)) {
28
+ value.forEach((item, index) => visit(item, [...path, index], depth + 1));
29
+ return;
30
+ }
31
+ if (!isObject(value)) return;
32
+ for (const [field, child] of Object.entries(value)) {
33
+ if (field === "account") {
34
+ const key = naturalAccountKey(child);
35
+ if (key) sites.push({ path: [...path, field], key });
36
+ } else if (field === "accounts" && Array.isArray(child)) {
37
+ child.forEach((entry, index) => {
38
+ const key = naturalAccountKey(entry);
39
+ if (key) sites.push({ path: [...path, field, index], key });
40
+ });
41
+ }
42
+ visit(child, [...path, field], depth + 1);
43
+ }
44
+ };
45
+ visit(params, [], 0);
46
+ return sites;
47
+ }
48
+ function valueAt(root, path) {
49
+ let current = root;
50
+ for (const segment of path) {
51
+ if (current === null || typeof current !== "object") return void 0;
52
+ current = current[segment];
53
+ }
54
+ return current;
55
+ }
56
+ function withReducedKeyAt(root, path, reduced) {
57
+ if (path.length === 0) {
58
+ if (!isObject(root) || "account_id" in reduced) return root;
59
+ const next = { ...root };
60
+ for (const field of REDUCIBLE_FIELDS) {
61
+ if (reduced[field] === void 0) delete next[field];
62
+ }
63
+ if (isObject(next.brand) && reduced.brand.countries === void 0 && "countries" in next.brand) {
64
+ const { countries: _, ...brand } = next.brand;
65
+ next.brand = brand;
66
+ }
67
+ return next;
68
+ }
69
+ const [head, ...rest] = path;
70
+ if (Array.isArray(root) && typeof head === "number") {
71
+ const copy = [...root];
72
+ copy[head] = withReducedKeyAt(copy[head], rest, reduced);
73
+ return copy;
74
+ }
75
+ if (isObject(root) && typeof head === "string") {
76
+ return { ...root, [head]: withReducedKeyAt(root[head], rest, reduced) };
77
+ }
78
+ return root;
79
+ }
80
+ function reducedFields(full, reduced) {
81
+ if ("account_id" in full || "account_id" in reduced) return [];
82
+ const fields = REDUCIBLE_FIELDS.filter(
83
+ (field) => full[field] !== void 0 && reduced[field] === void 0
84
+ );
85
+ if (full.brand.countries !== void 0 && reduced.brand.countries === void 0) fields.push("brand.countries");
86
+ return fields;
87
+ }
88
+ function sameAccountKey(left, right) {
89
+ return accountReferenceKey(left) === accountReferenceKey(right);
90
+ }
91
+ export {
92
+ accountKeySites,
93
+ naturalAccountKey,
94
+ reducedFields,
95
+ sameAccountKey,
96
+ valueAt,
97
+ withReducedKeyAt
98
+ };
@@ -24,6 +24,17 @@ export interface ProvisionedAccount {
24
24
  idempotencyKey: string;
25
25
  startedAt: string;
26
26
  };
27
+ /**
28
+ * Set on a buyer key that reached the seller reduced to an older release's
29
+ * account key (`accountIdentityDowngrade: 'reduce'`): the key the seller
30
+ * knows the account by, and the release it was reduced for.
31
+ */
32
+ reduction?: {
33
+ account: AccountReference;
34
+ adcpVersion: string;
35
+ };
36
+ /** Set on a reduced seller key: the one buyer key allowed to use it. */
37
+ reducedFrom?: AccountReference;
27
38
  }
28
39
  /** Partitioned by seller and caller scope. Implementations must provide read-your-writes. */
29
40
  export interface BuyerAccountStorage {
@@ -61,6 +72,8 @@ export declare class BuyerAccountRegistry {
61
72
  private repair?;
62
73
  private readonly registryOptions;
63
74
  private entries;
75
+ /** Reduction bindings when no storage is configured (never evicted). */
76
+ private reductions;
64
77
  private pending;
65
78
  private writes;
66
79
  private repairs;
@@ -96,4 +109,43 @@ export declare class BuyerAccountRegistry {
96
109
  }, options?: TaskOptions): Promise<void>;
97
110
  private repairStatus;
98
111
  private runRepair;
112
+ private reductionKey;
113
+ private readReduction;
114
+ /** Insert-if-absent; returns false when another writer already holds the key. */
115
+ private insertReduction;
116
+ private replaceReduction;
117
+ /**
118
+ * Bind reduced account keys to the buyer keys they came from, before a
119
+ * request carrying them is sent to a seller on an older release. Each
120
+ * reduced key serves one buyer key only. A key sent unreduced claims itself
121
+ * (`account` and `reduced` equal), so a plain key and a reduced one can never
122
+ * land on the same seller account in either order.
123
+ *
124
+ * All-or-nothing: every claim is checked, against storage and against the
125
+ * other claims in the same request, before any is written. On a conflict
126
+ * nothing is written and the index and owner of the first conflicting claim
127
+ * are returned. A claim lost to a concurrent writer rolls back the claims
128
+ * this call already wrote.
129
+ */
130
+ claimReductions(claims: ReadonlyArray<{
131
+ account: AccountReference;
132
+ reduced: AccountReference;
133
+ adcpVersion: string;
134
+ }>): Promise<{
135
+ index: number;
136
+ owner: AccountReference;
137
+ } | undefined>;
138
+ /** The buyer key a reduced seller key was claimed for, if any. */
139
+ reductionOwner(reduced: AccountReference): Promise<AccountReference | undefined>;
140
+ /** The reduced key a buyer key reached an older seller with, if any. */
141
+ reductionFor(account: AccountReference): Promise<ProvisionedAccount['reduction']>;
142
+ /**
143
+ * Forget that `account` reached this seller under a reduced key. Call once
144
+ * the account has moved to its full key: after a `sync_accounts` settings
145
+ * update rekeys `operator_unit` in place, or after opening a new account
146
+ * under the full key when the reduced fields (`currency`, `timezone`,
147
+ * `brand.countries`) cannot change on an existing account. Until then the
148
+ * client keeps addressing the seller account by its reduced key.
149
+ */
150
+ clearReduction(account: AccountReference): Promise<void>;
99
151
  }
@@ -24,6 +24,17 @@ export interface ProvisionedAccount {
24
24
  idempotencyKey: string;
25
25
  startedAt: string;
26
26
  };
27
+ /**
28
+ * Set on a buyer key that reached the seller reduced to an older release's
29
+ * account key (`accountIdentityDowngrade: 'reduce'`): the key the seller
30
+ * knows the account by, and the release it was reduced for.
31
+ */
32
+ reduction?: {
33
+ account: AccountReference;
34
+ adcpVersion: string;
35
+ };
36
+ /** Set on a reduced seller key: the one buyer key allowed to use it. */
37
+ reducedFrom?: AccountReference;
27
38
  }
28
39
  /** Partitioned by seller and caller scope. Implementations must provide read-your-writes. */
29
40
  export interface BuyerAccountStorage {
@@ -61,6 +72,8 @@ export declare class BuyerAccountRegistry {
61
72
  private repair?;
62
73
  private readonly registryOptions;
63
74
  private entries;
75
+ /** Reduction bindings when no storage is configured (never evicted). */
76
+ private reductions;
64
77
  private pending;
65
78
  private writes;
66
79
  private repairs;
@@ -96,4 +109,43 @@ export declare class BuyerAccountRegistry {
96
109
  }, options?: TaskOptions): Promise<void>;
97
110
  private repairStatus;
98
111
  private runRepair;
112
+ private reductionKey;
113
+ private readReduction;
114
+ /** Insert-if-absent; returns false when another writer already holds the key. */
115
+ private insertReduction;
116
+ private replaceReduction;
117
+ /**
118
+ * Bind reduced account keys to the buyer keys they came from, before a
119
+ * request carrying them is sent to a seller on an older release. Each
120
+ * reduced key serves one buyer key only. A key sent unreduced claims itself
121
+ * (`account` and `reduced` equal), so a plain key and a reduced one can never
122
+ * land on the same seller account in either order.
123
+ *
124
+ * All-or-nothing: every claim is checked, against storage and against the
125
+ * other claims in the same request, before any is written. On a conflict
126
+ * nothing is written and the index and owner of the first conflicting claim
127
+ * are returned. A claim lost to a concurrent writer rolls back the claims
128
+ * this call already wrote.
129
+ */
130
+ claimReductions(claims: ReadonlyArray<{
131
+ account: AccountReference;
132
+ reduced: AccountReference;
133
+ adcpVersion: string;
134
+ }>): Promise<{
135
+ index: number;
136
+ owner: AccountReference;
137
+ } | undefined>;
138
+ /** The buyer key a reduced seller key was claimed for, if any. */
139
+ reductionOwner(reduced: AccountReference): Promise<AccountReference | undefined>;
140
+ /** The reduced key a buyer key reached an older seller with, if any. */
141
+ reductionFor(account: AccountReference): Promise<ProvisionedAccount['reduction']>;
142
+ /**
143
+ * Forget that `account` reached this seller under a reduced key. Call once
144
+ * the account has moved to its full key: after a `sync_accounts` settings
145
+ * update rekeys `operator_unit` in place, or after opening a new account
146
+ * under the full key when the reduced fields (`currency`, `timezone`,
147
+ * `brand.countries`) cannot change on an existing account. Until then the
148
+ * client keeps addressing the seller account by its reduced key.
149
+ */
150
+ clearReduction(account: AccountReference): Promise<void>;
99
151
  }
@@ -45,6 +45,10 @@ function isDefinitiveRejection(result) {
45
45
  result.adcpError && !result.adcpError.synthetic && !result.adcpError.code.startsWith("IDEMPOTENCY_") && ["terminal", "correctable"].includes(result.adcpError.recovery ?? "")
46
46
  );
47
47
  }
48
+ function rowMatchesReference(ref, row) {
49
+ if ("account_id" in ref) return row.account_id === ref.account_id;
50
+ return row.brand?.domain === ref.brand.domain && row.operator === ref.operator && (!ref.brand.brand_id || row.brand.brand_id === ref.brand.brand_id) && (0, import_account_resolution.sameCountrySet)(ref.brand.countries, row.brand.countries) && (!ref.operator_unit || row.operator_unit?.id === ref.operator_unit.id) && (!ref.currency || row.currency === ref.currency) && (!ref.timezone || row.timezone === ref.timezone) && (row.sandbox === void 0 || row.sandbox === true === (ref.sandbox === true));
51
+ }
48
52
  class BuyerAccountRegistry {
49
53
  constructor(scope, provision, storage, repair, registryOptions = {}) {
50
54
  this.scope = scope;
@@ -63,6 +67,8 @@ class BuyerAccountRegistry {
63
67
  repair;
64
68
  registryOptions;
65
69
  entries = /* @__PURE__ */ new Map();
70
+ /** Reduction bindings when no storage is configured (never evicted). */
71
+ reductions = /* @__PURE__ */ new Map();
66
72
  pending = /* @__PURE__ */ new Map();
67
73
  writes = Promise.resolve();
68
74
  repairs = /* @__PURE__ */ new Map();
@@ -444,12 +450,13 @@ class BuyerAccountRegistry {
444
450
  return this.enqueue(async () => {
445
451
  if (scope !== this.scope()) return;
446
452
  for (const ref of refs) {
447
- const candidates = rows.filter((value) => {
448
- if (!value || typeof value !== "object") return false;
449
- const row2 = value;
450
- if ("account_id" in ref) return row2.account_id === ref.account_id;
451
- return row2.brand?.domain === ref.brand.domain && row2.operator === ref.operator && (!ref.brand.brand_id || row2.brand.brand_id === ref.brand.brand_id) && (0, import_account_resolution.sameCountrySet)(ref.brand.countries, row2.brand.countries) && (!ref.operator_unit || row2.operator_unit?.id === ref.operator_unit.id) && (!ref.currency || row2.currency === ref.currency) && (!ref.timezone || row2.timezone === ref.timezone) && (row2.sandbox === void 0 || row2.sandbox === true === (ref.sandbox === true));
452
- });
453
+ const isRow = (value) => !!value && typeof value === "object";
454
+ let candidates = rows.filter((value) => isRow(value) && rowMatchesReference(ref, value));
455
+ if (candidates.length === 0 && !("account_id" in ref)) {
456
+ const reduction = (await this.reductionFor(ref))?.account;
457
+ if (scope !== this.scope()) return;
458
+ if (reduction) candidates = rows.filter((value) => isRow(value) && rowMatchesReference(reduction, value));
459
+ }
453
460
  if (candidates.length !== 1) continue;
454
461
  let row = candidates[0];
455
462
  if (row.action === "failed" || typeof row.status !== "string") continue;
@@ -614,6 +621,135 @@ class BuyerAccountRegistry {
614
621
  }
615
622
  }
616
623
  }
624
+ reductionKey(kind, account, scope = this.scope()) {
625
+ return `${scope}:${kind}:${(0, import_account_key.accountReferenceKey)(account)}`;
626
+ }
627
+ async readReduction(key) {
628
+ const entry = this.storage ? await this.storage.get(key) : this.reductions.get(key);
629
+ return entry && entry.status === "reduced" ? structuredClone(entry) : void 0;
630
+ }
631
+ /** Insert-if-absent; returns false when another writer already holds the key. */
632
+ async insertReduction(key, entry) {
633
+ const prior = this.storage ? await this.storage.get(key) : this.reductions.get(key);
634
+ if (prior && prior.status === "reduced") return false;
635
+ if (this.storage?.compareAndSet) {
636
+ return this.storage.compareAndSet(key, prior?.revision, { ...entry, revision: (prior?.revision ?? 0) + 1 });
637
+ }
638
+ if (this.storage) await this.storage.set(key, structuredClone(entry));
639
+ else this.reductions.set(key, structuredClone(entry));
640
+ return true;
641
+ }
642
+ async replaceReduction(key, entry) {
643
+ const prior = this.storage ? await this.storage.get(key) : this.reductions.get(key);
644
+ if (this.storage?.compareAndSet) {
645
+ const next = { ...entry, revision: (prior?.revision ?? 0) + 1 };
646
+ if (!await this.storage.compareAndSet(key, prior?.revision, next))
647
+ throw new import_errors.AccountSetupRequiredError("Account storage revision changed. Reload and reconcile before retrying.");
648
+ } else if (this.storage) await this.storage.set(key, structuredClone(entry));
649
+ else this.reductions.set(key, structuredClone(entry));
650
+ }
651
+ /**
652
+ * Bind reduced account keys to the buyer keys they came from, before a
653
+ * request carrying them is sent to a seller on an older release. Each
654
+ * reduced key serves one buyer key only. A key sent unreduced claims itself
655
+ * (`account` and `reduced` equal), so a plain key and a reduced one can never
656
+ * land on the same seller account in either order.
657
+ *
658
+ * All-or-nothing: every claim is checked, against storage and against the
659
+ * other claims in the same request, before any is written. On a conflict
660
+ * nothing is written and the index and owner of the first conflicting claim
661
+ * are returned. A claim lost to a concurrent writer rolls back the claims
662
+ * this call already wrote.
663
+ */
664
+ async claimReductions(claims) {
665
+ const scope = this.scope();
666
+ let conflict;
667
+ await this.enqueue(async () => {
668
+ const pending = /* @__PURE__ */ new Map();
669
+ const toInsert = [];
670
+ for (const [index, claim] of claims.entries()) {
671
+ const reducedKey = (0, import_account_key.accountReferenceKey)(claim.reduced);
672
+ const inRequest = pending.get(reducedKey);
673
+ if (inRequest && (0, import_account_key.accountReferenceKey)(inRequest) !== (0, import_account_key.accountReferenceKey)(claim.account)) {
674
+ conflict = { index, owner: inRequest };
675
+ return;
676
+ }
677
+ pending.set(reducedKey, claim.account);
678
+ const owner = (await this.readReduction(this.reductionKey("reduced", claim.reduced, scope)))?.reducedFrom;
679
+ if (owner && (0, import_account_key.accountReferenceKey)(owner) !== (0, import_account_key.accountReferenceKey)(claim.account)) {
680
+ conflict = { index, owner };
681
+ return;
682
+ }
683
+ if (!owner && !inRequest) toInsert.push(index);
684
+ }
685
+ const inserted = [];
686
+ for (const index of toInsert) {
687
+ const claim = claims[index];
688
+ const reverseKey = this.reductionKey("reduced", claim.reduced, scope);
689
+ const ok = await this.insertReduction(reverseKey, {
690
+ account: structuredClone(claim.reduced),
691
+ status: "reduced",
692
+ reducedFrom: structuredClone(claim.account)
693
+ });
694
+ const winner = ok ? void 0 : (await this.readReduction(reverseKey))?.reducedFrom;
695
+ if (!ok && winner && (0, import_account_key.accountReferenceKey)(winner) !== (0, import_account_key.accountReferenceKey)(claim.account)) {
696
+ for (const done of inserted) {
697
+ const doneClaim = claims[done];
698
+ await this.replaceReduction(this.reductionKey("reduced", doneClaim.reduced, scope), {
699
+ account: structuredClone(doneClaim.reduced),
700
+ status: "released"
701
+ });
702
+ }
703
+ conflict = { index, owner: winner };
704
+ return;
705
+ }
706
+ if (ok) inserted.push(index);
707
+ }
708
+ for (const claim of claims) {
709
+ if ((0, import_account_key.accountReferenceKey)(claim.account) === (0, import_account_key.accountReferenceKey)(claim.reduced)) continue;
710
+ const forwardKey = this.reductionKey("reduction", claim.account, scope);
711
+ const current = await this.readReduction(forwardKey);
712
+ const reduction = { account: claim.reduced, adcpVersion: claim.adcpVersion };
713
+ if (current?.reduction && (0, import_node_util.isDeepStrictEqual)(current.reduction, reduction)) continue;
714
+ await this.replaceReduction(forwardKey, {
715
+ account: structuredClone(claim.account),
716
+ status: "reduced",
717
+ reduction: structuredClone(reduction)
718
+ });
719
+ }
720
+ });
721
+ return conflict;
722
+ }
723
+ /** The buyer key a reduced seller key was claimed for, if any. */
724
+ async reductionOwner(reduced) {
725
+ return (await this.readReduction(this.reductionKey("reduced", reduced)))?.reducedFrom;
726
+ }
727
+ /** The reduced key a buyer key reached an older seller with, if any. */
728
+ async reductionFor(account) {
729
+ return (await this.readReduction(this.reductionKey("reduction", account)))?.reduction;
730
+ }
731
+ /**
732
+ * Forget that `account` reached this seller under a reduced key. Call once
733
+ * the account has moved to its full key: after a `sync_accounts` settings
734
+ * update rekeys `operator_unit` in place, or after opening a new account
735
+ * under the full key when the reduced fields (`currency`, `timezone`,
736
+ * `brand.countries`) cannot change on an existing account. Until then the
737
+ * client keeps addressing the seller account by its reduced key.
738
+ */
739
+ async clearReduction(account) {
740
+ const scope = this.scope();
741
+ await this.enqueue(async () => {
742
+ const forwardKey = this.reductionKey("reduction", account, scope);
743
+ const forward = await this.readReduction(forwardKey);
744
+ if (!forward?.reduction) return;
745
+ const reverseKey = this.reductionKey("reduced", forward.reduction.account, scope);
746
+ const reverse = await this.readReduction(reverseKey);
747
+ if (reverse?.reducedFrom && (0, import_account_key.accountReferenceKey)(reverse.reducedFrom) === (0, import_account_key.accountReferenceKey)(account)) {
748
+ await this.replaceReduction(reverseKey, { account: reverse.account, status: "released" });
749
+ }
750
+ await this.replaceReduction(forwardKey, { account: forward.account, status: "released" });
751
+ });
752
+ }
617
753
  }
618
754
  // Annotate the CommonJS export names for ESM import in node:
619
755
  0 && (module.exports = {
@@ -22,6 +22,10 @@ function isDefinitiveRejection(result) {
22
22
  result.adcpError && !result.adcpError.synthetic && !result.adcpError.code.startsWith("IDEMPOTENCY_") && ["terminal", "correctable"].includes(result.adcpError.recovery ?? "")
23
23
  );
24
24
  }
25
+ function rowMatchesReference(ref, row) {
26
+ if ("account_id" in ref) return row.account_id === ref.account_id;
27
+ return row.brand?.domain === ref.brand.domain && row.operator === ref.operator && (!ref.brand.brand_id || row.brand.brand_id === ref.brand.brand_id) && sameCountrySet(ref.brand.countries, row.brand.countries) && (!ref.operator_unit || row.operator_unit?.id === ref.operator_unit.id) && (!ref.currency || row.currency === ref.currency) && (!ref.timezone || row.timezone === ref.timezone) && (row.sandbox === void 0 || row.sandbox === true === (ref.sandbox === true));
28
+ }
25
29
  class BuyerAccountRegistry {
26
30
  constructor(scope, provision, storage, repair, registryOptions = {}) {
27
31
  this.scope = scope;
@@ -40,6 +44,8 @@ class BuyerAccountRegistry {
40
44
  repair;
41
45
  registryOptions;
42
46
  entries = /* @__PURE__ */ new Map();
47
+ /** Reduction bindings when no storage is configured (never evicted). */
48
+ reductions = /* @__PURE__ */ new Map();
43
49
  pending = /* @__PURE__ */ new Map();
44
50
  writes = Promise.resolve();
45
51
  repairs = /* @__PURE__ */ new Map();
@@ -421,12 +427,13 @@ class BuyerAccountRegistry {
421
427
  return this.enqueue(async () => {
422
428
  if (scope !== this.scope()) return;
423
429
  for (const ref of refs) {
424
- const candidates = rows.filter((value) => {
425
- if (!value || typeof value !== "object") return false;
426
- const row2 = value;
427
- if ("account_id" in ref) return row2.account_id === ref.account_id;
428
- return row2.brand?.domain === ref.brand.domain && row2.operator === ref.operator && (!ref.brand.brand_id || row2.brand.brand_id === ref.brand.brand_id) && sameCountrySet(ref.brand.countries, row2.brand.countries) && (!ref.operator_unit || row2.operator_unit?.id === ref.operator_unit.id) && (!ref.currency || row2.currency === ref.currency) && (!ref.timezone || row2.timezone === ref.timezone) && (row2.sandbox === void 0 || row2.sandbox === true === (ref.sandbox === true));
429
- });
430
+ const isRow = (value) => !!value && typeof value === "object";
431
+ let candidates = rows.filter((value) => isRow(value) && rowMatchesReference(ref, value));
432
+ if (candidates.length === 0 && !("account_id" in ref)) {
433
+ const reduction = (await this.reductionFor(ref))?.account;
434
+ if (scope !== this.scope()) return;
435
+ if (reduction) candidates = rows.filter((value) => isRow(value) && rowMatchesReference(reduction, value));
436
+ }
430
437
  if (candidates.length !== 1) continue;
431
438
  let row = candidates[0];
432
439
  if (row.action === "failed" || typeof row.status !== "string") continue;
@@ -591,6 +598,135 @@ class BuyerAccountRegistry {
591
598
  }
592
599
  }
593
600
  }
601
+ reductionKey(kind, account, scope = this.scope()) {
602
+ return `${scope}:${kind}:${accountReferenceKey(account)}`;
603
+ }
604
+ async readReduction(key) {
605
+ const entry = this.storage ? await this.storage.get(key) : this.reductions.get(key);
606
+ return entry && entry.status === "reduced" ? structuredClone(entry) : void 0;
607
+ }
608
+ /** Insert-if-absent; returns false when another writer already holds the key. */
609
+ async insertReduction(key, entry) {
610
+ const prior = this.storage ? await this.storage.get(key) : this.reductions.get(key);
611
+ if (prior && prior.status === "reduced") return false;
612
+ if (this.storage?.compareAndSet) {
613
+ return this.storage.compareAndSet(key, prior?.revision, { ...entry, revision: (prior?.revision ?? 0) + 1 });
614
+ }
615
+ if (this.storage) await this.storage.set(key, structuredClone(entry));
616
+ else this.reductions.set(key, structuredClone(entry));
617
+ return true;
618
+ }
619
+ async replaceReduction(key, entry) {
620
+ const prior = this.storage ? await this.storage.get(key) : this.reductions.get(key);
621
+ if (this.storage?.compareAndSet) {
622
+ const next = { ...entry, revision: (prior?.revision ?? 0) + 1 };
623
+ if (!await this.storage.compareAndSet(key, prior?.revision, next))
624
+ throw new AccountSetupRequiredError("Account storage revision changed. Reload and reconcile before retrying.");
625
+ } else if (this.storage) await this.storage.set(key, structuredClone(entry));
626
+ else this.reductions.set(key, structuredClone(entry));
627
+ }
628
+ /**
629
+ * Bind reduced account keys to the buyer keys they came from, before a
630
+ * request carrying them is sent to a seller on an older release. Each
631
+ * reduced key serves one buyer key only. A key sent unreduced claims itself
632
+ * (`account` and `reduced` equal), so a plain key and a reduced one can never
633
+ * land on the same seller account in either order.
634
+ *
635
+ * All-or-nothing: every claim is checked, against storage and against the
636
+ * other claims in the same request, before any is written. On a conflict
637
+ * nothing is written and the index and owner of the first conflicting claim
638
+ * are returned. A claim lost to a concurrent writer rolls back the claims
639
+ * this call already wrote.
640
+ */
641
+ async claimReductions(claims) {
642
+ const scope = this.scope();
643
+ let conflict;
644
+ await this.enqueue(async () => {
645
+ const pending = /* @__PURE__ */ new Map();
646
+ const toInsert = [];
647
+ for (const [index, claim] of claims.entries()) {
648
+ const reducedKey = accountReferenceKey(claim.reduced);
649
+ const inRequest = pending.get(reducedKey);
650
+ if (inRequest && accountReferenceKey(inRequest) !== accountReferenceKey(claim.account)) {
651
+ conflict = { index, owner: inRequest };
652
+ return;
653
+ }
654
+ pending.set(reducedKey, claim.account);
655
+ const owner = (await this.readReduction(this.reductionKey("reduced", claim.reduced, scope)))?.reducedFrom;
656
+ if (owner && accountReferenceKey(owner) !== accountReferenceKey(claim.account)) {
657
+ conflict = { index, owner };
658
+ return;
659
+ }
660
+ if (!owner && !inRequest) toInsert.push(index);
661
+ }
662
+ const inserted = [];
663
+ for (const index of toInsert) {
664
+ const claim = claims[index];
665
+ const reverseKey = this.reductionKey("reduced", claim.reduced, scope);
666
+ const ok = await this.insertReduction(reverseKey, {
667
+ account: structuredClone(claim.reduced),
668
+ status: "reduced",
669
+ reducedFrom: structuredClone(claim.account)
670
+ });
671
+ const winner = ok ? void 0 : (await this.readReduction(reverseKey))?.reducedFrom;
672
+ if (!ok && winner && accountReferenceKey(winner) !== accountReferenceKey(claim.account)) {
673
+ for (const done of inserted) {
674
+ const doneClaim = claims[done];
675
+ await this.replaceReduction(this.reductionKey("reduced", doneClaim.reduced, scope), {
676
+ account: structuredClone(doneClaim.reduced),
677
+ status: "released"
678
+ });
679
+ }
680
+ conflict = { index, owner: winner };
681
+ return;
682
+ }
683
+ if (ok) inserted.push(index);
684
+ }
685
+ for (const claim of claims) {
686
+ if (accountReferenceKey(claim.account) === accountReferenceKey(claim.reduced)) continue;
687
+ const forwardKey = this.reductionKey("reduction", claim.account, scope);
688
+ const current = await this.readReduction(forwardKey);
689
+ const reduction = { account: claim.reduced, adcpVersion: claim.adcpVersion };
690
+ if (current?.reduction && isDeepStrictEqual(current.reduction, reduction)) continue;
691
+ await this.replaceReduction(forwardKey, {
692
+ account: structuredClone(claim.account),
693
+ status: "reduced",
694
+ reduction: structuredClone(reduction)
695
+ });
696
+ }
697
+ });
698
+ return conflict;
699
+ }
700
+ /** The buyer key a reduced seller key was claimed for, if any. */
701
+ async reductionOwner(reduced) {
702
+ return (await this.readReduction(this.reductionKey("reduced", reduced)))?.reducedFrom;
703
+ }
704
+ /** The reduced key a buyer key reached an older seller with, if any. */
705
+ async reductionFor(account) {
706
+ return (await this.readReduction(this.reductionKey("reduction", account)))?.reduction;
707
+ }
708
+ /**
709
+ * Forget that `account` reached this seller under a reduced key. Call once
710
+ * the account has moved to its full key: after a `sync_accounts` settings
711
+ * update rekeys `operator_unit` in place, or after opening a new account
712
+ * under the full key when the reduced fields (`currency`, `timezone`,
713
+ * `brand.countries`) cannot change on an existing account. Until then the
714
+ * client keeps addressing the seller account by its reduced key.
715
+ */
716
+ async clearReduction(account) {
717
+ const scope = this.scope();
718
+ await this.enqueue(async () => {
719
+ const forwardKey = this.reductionKey("reduction", account, scope);
720
+ const forward = await this.readReduction(forwardKey);
721
+ if (!forward?.reduction) return;
722
+ const reverseKey = this.reductionKey("reduced", forward.reduction.account, scope);
723
+ const reverse = await this.readReduction(reverseKey);
724
+ if (reverse?.reducedFrom && accountReferenceKey(reverse.reducedFrom) === accountReferenceKey(account)) {
725
+ await this.replaceReduction(reverseKey, { account: reverse.account, status: "released" });
726
+ }
727
+ await this.replaceReduction(forwardKey, { account: forward.account, status: "released" });
728
+ });
729
+ }
594
730
  }
595
731
  export {
596
732
  BuyerAccountRegistry
@@ -537,3 +537,37 @@ export declare function extractErrorInfo(error: unknown): {
537
537
  details?: unknown;
538
538
  stack?: string;
539
539
  };
540
+ /**
541
+ * Prepared fixture state was refused by the host authorizer.
542
+ *
543
+ * An authorization failure is an ACCESS-CONTROL error, not a grading
544
+ * outcome, so the refused caller gets this and no `StoryboardResult` —
545
+ * not a partial one, not a redacted one. It carries the machine refusal
546
+ * code and nothing else: no fixture identities, no `seller_ids`, no
547
+ * captures, no binding values, no request/response records, and no
548
+ * results from steps that ran before the grant was revoked.
549
+ *
550
+ * The complete, unredacted record — including the synthesized
551
+ * `prepared_fixtures_authorization` check, `fixture_resolutions` with
552
+ * `seller_ids`, and exact request/response records — is handed to the
553
+ * host's `prepared_fixtures_audit_sink` before this is thrown. Where no
554
+ * sink is configured the record is simply not produced to anyone.
555
+ *
556
+ * Hosted entry points should surface this as a 403-style denial.
557
+ */
558
+ export declare class PreparedFixturesDeniedError extends ADCPError {
559
+ /**
560
+ * Machine refusal code: `authorizer_required` | `untrusted_state` |
561
+ * `contract_version_mismatch` | `authorizer_denied` |
562
+ * `binding_mismatch` | `unauthorized_metadata`.
563
+ */
564
+ readonly refusalCode: string;
565
+ readonly code = "PREPARED_FIXTURES_DENIED";
566
+ constructor(
567
+ /**
568
+ * Machine refusal code: `authorizer_required` | `untrusted_state` |
569
+ * `contract_version_mismatch` | `authorizer_denied` |
570
+ * `binding_mismatch` | `unauthorized_metadata`.
571
+ */
572
+ refusalCode: string);
573
+ }
@@ -537,3 +537,37 @@ export declare function extractErrorInfo(error: unknown): {
537
537
  details?: unknown;
538
538
  stack?: string;
539
539
  };
540
+ /**
541
+ * Prepared fixture state was refused by the host authorizer.
542
+ *
543
+ * An authorization failure is an ACCESS-CONTROL error, not a grading
544
+ * outcome, so the refused caller gets this and no `StoryboardResult` —
545
+ * not a partial one, not a redacted one. It carries the machine refusal
546
+ * code and nothing else: no fixture identities, no `seller_ids`, no
547
+ * captures, no binding values, no request/response records, and no
548
+ * results from steps that ran before the grant was revoked.
549
+ *
550
+ * The complete, unredacted record — including the synthesized
551
+ * `prepared_fixtures_authorization` check, `fixture_resolutions` with
552
+ * `seller_ids`, and exact request/response records — is handed to the
553
+ * host's `prepared_fixtures_audit_sink` before this is thrown. Where no
554
+ * sink is configured the record is simply not produced to anyone.
555
+ *
556
+ * Hosted entry points should surface this as a 403-style denial.
557
+ */
558
+ export declare class PreparedFixturesDeniedError extends ADCPError {
559
+ /**
560
+ * Machine refusal code: `authorizer_required` | `untrusted_state` |
561
+ * `contract_version_mismatch` | `authorizer_denied` |
562
+ * `binding_mismatch` | `unauthorized_metadata`.
563
+ */
564
+ readonly refusalCode: string;
565
+ readonly code = "PREPARED_FIXTURES_DENIED";
566
+ constructor(
567
+ /**
568
+ * Machine refusal code: `authorizer_required` | `untrusted_state` |
569
+ * `contract_version_mismatch` | `authorizer_denied` |
570
+ * `binding_mismatch` | `unauthorized_metadata`.
571
+ */
572
+ refusalCode: string);
573
+ }