@adcp/sdk 16.0.0-beta.1 → 16.0.0-beta.2

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 (97) hide show
  1. package/dist/lib/core/SingleAgentClient.d.mts +10 -1
  2. package/dist/lib/core/SingleAgentClient.d.ts +10 -1
  3. package/dist/lib/core/SingleAgentClient.js +29 -8
  4. package/dist/lib/core/SingleAgentClient.mjs +29 -8
  5. package/dist/lib/identity-3.3/generated-compat.d.mts +34 -0
  6. package/dist/lib/identity-3.3/generated-compat.d.ts +34 -0
  7. package/dist/lib/identity-3.3/generated-compat.js +16 -0
  8. package/dist/lib/identity-3.3/generated-compat.mjs +0 -0
  9. package/dist/lib/identity-3.3/index.d.mts +2 -2
  10. package/dist/lib/identity-3.3/index.d.ts +2 -2
  11. package/dist/lib/identity-3.3/index.js +4 -0
  12. package/dist/lib/identity-3.3/index.mjs +5 -1
  13. package/dist/lib/identity-3.3/lifecycle.d.mts +17 -5
  14. package/dist/lib/identity-3.3/lifecycle.d.ts +17 -5
  15. package/dist/lib/identity-3.3/lifecycle.js +32 -5
  16. package/dist/lib/identity-3.3/lifecycle.mjs +38 -6
  17. package/dist/lib/identity-3.3/revocation.d.mts +39 -3
  18. package/dist/lib/identity-3.3/revocation.d.ts +39 -3
  19. package/dist/lib/identity-3.3/revocation.js +88 -18
  20. package/dist/lib/identity-3.3/revocation.mjs +89 -19
  21. package/dist/lib/identity-3.3/server.d.mts +12 -0
  22. package/dist/lib/identity-3.3/server.d.ts +12 -0
  23. package/dist/lib/identity-3.3/server.js +103 -43
  24. package/dist/lib/identity-3.3/server.mjs +111 -45
  25. package/dist/lib/identity-3.3/trust.d.mts +62 -1
  26. package/dist/lib/identity-3.3/trust.d.ts +62 -1
  27. package/dist/lib/identity-3.3/trust.js +126 -21
  28. package/dist/lib/identity-3.3/trust.mjs +124 -21
  29. package/dist/lib/identity-3.3/types.d.mts +39 -11
  30. package/dist/lib/identity-3.3/types.d.ts +39 -11
  31. package/dist/lib/index.d.mts +1 -1
  32. package/dist/lib/index.d.ts +1 -1
  33. package/dist/lib/index.js +2 -0
  34. package/dist/lib/index.mjs +2 -0
  35. package/dist/lib/protocols/index.d.mts +1 -1
  36. package/dist/lib/protocols/index.d.ts +1 -1
  37. package/dist/lib/protocols/index.js +2 -2
  38. package/dist/lib/protocols/index.mjs +2 -2
  39. package/dist/lib/reporting/bigquery/index.js +8 -0
  40. package/dist/lib/reporting/bigquery/index.mjs +8 -0
  41. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  42. package/dist/lib/server/auth-signature.d.mts +6 -1
  43. package/dist/lib/server/auth-signature.d.ts +6 -1
  44. package/dist/lib/server/auth-signature.js +24 -13
  45. package/dist/lib/server/auth-signature.mjs +29 -13
  46. package/dist/lib/server/auth.d.mts +2 -2
  47. package/dist/lib/server/auth.d.ts +2 -2
  48. package/dist/lib/server/create-adcp-server.js +31 -10
  49. package/dist/lib/server/create-adcp-server.mjs +37 -11
  50. package/dist/lib/signing/crypto.js +8 -0
  51. package/dist/lib/signing/crypto.mjs +9 -1
  52. package/dist/lib/signing/fetch-async.js +1 -1
  53. package/dist/lib/signing/fetch-async.mjs +1 -1
  54. package/dist/lib/signing/fetch.js +1 -1
  55. package/dist/lib/signing/fetch.mjs +1 -1
  56. package/dist/lib/signing/middleware.js +9 -0
  57. package/dist/lib/signing/middleware.mjs +14 -0
  58. package/dist/lib/signing/parser.d.mts +8 -5
  59. package/dist/lib/signing/parser.d.ts +8 -5
  60. package/dist/lib/signing/parser.js +77 -61
  61. package/dist/lib/signing/parser.mjs +77 -61
  62. package/dist/lib/signing/revocation.js +3 -0
  63. package/dist/lib/signing/revocation.mjs +3 -0
  64. package/dist/lib/signing/verifier.js +1 -1
  65. package/dist/lib/signing/verifier.mjs +1 -1
  66. package/dist/lib/signing/web-bot-auth.d.mts +84 -10
  67. package/dist/lib/signing/web-bot-auth.d.ts +84 -10
  68. package/dist/lib/signing/web-bot-auth.js +259 -82
  69. package/dist/lib/signing/web-bot-auth.mjs +259 -87
  70. package/dist/lib/signing/webhook-verifier.js +1 -1
  71. package/dist/lib/signing/webhook-verifier.mjs +1 -1
  72. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  73. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  74. package/dist/lib/v2/projection/catalog-snapshot.js +3 -1
  75. package/dist/lib/v2/projection/catalog-snapshot.mjs +3 -1
  76. package/dist/lib/v2/projection/creative-delivery.d.mts +31 -9
  77. package/dist/lib/v2/projection/creative-delivery.d.ts +31 -9
  78. package/dist/lib/v2/projection/index.d.mts +1 -1
  79. package/dist/lib/v2/projection/index.d.ts +1 -1
  80. package/dist/lib/v2/projection/index.js +2 -0
  81. package/dist/lib/v2/projection/index.mjs +2 -0
  82. package/dist/lib/v2/projection/legacy-routes.d.mts +10 -0
  83. package/dist/lib/v2/projection/legacy-routes.d.ts +10 -0
  84. package/dist/lib/v2/projection/legacy-routes.js +7 -0
  85. package/dist/lib/v2/projection/legacy-routes.mjs +6 -0
  86. package/dist/lib/version.d.mts +3 -3
  87. package/dist/lib/version.d.ts +3 -3
  88. package/dist/lib/version.js +3 -3
  89. package/dist/lib/version.mjs +3 -3
  90. package/docs/TYPE-SUMMARY.md +2 -2
  91. package/docs/guides/REPORTING-LEDGER.md +15 -5
  92. package/docs/guides/REPORTING-OPERATIONS.md +17 -11
  93. package/docs/llms.txt +2 -2
  94. package/docs/migration-12-to-13.md +8 -0
  95. package/docs/migration-14.x-rc-worksheet.md +4 -4
  96. package/docs/migration-agent-identity-3.3.md +150 -11
  97. package/package.json +3 -1
@@ -30,6 +30,8 @@ var trust_exports = {};
30
30
  __export(trust_exports, {
31
31
  IdentityAuthorizationError: () => IdentityAuthorizationError,
32
32
  TrustEvaluator: () => TrustEvaluator,
33
+ TrustMembershipError: () => TrustMembershipError,
34
+ TrustRecordUnavailableError: () => TrustRecordUnavailableError,
33
35
  authorizeDemandGrant: () => authorizeDemandGrant,
34
36
  createTrustRecord: () => createTrustRecord,
35
37
  validateIdentity33Schema: () => validateIdentity33Schema,
@@ -42,6 +44,7 @@ var import_net = require('../net/index.js');
42
44
  var import_canonicalize = require('../signing/agent-resolver/canonicalize.js');
43
45
  var import_etld = require('../signing/agent-resolver/etld.js');
44
46
  var import_strict_json = require('../signing/agent-resolver/strict-json.js');
47
+ var import_tldts = require("tldts");
45
48
  var import_schemas = __toESM(require('./schemas.draft.js'));
46
49
  const ajv = new import_ajv.default({ strict: false, allErrors: false });
47
50
  (0, import_ajv_formats.default)(ajv);
@@ -58,6 +61,39 @@ class IdentityAuthorizationError extends Error {
58
61
  this.details = reason === "grant_absent" ? { scope: "agent", reason } : void 0;
59
62
  }
60
63
  }
64
+ class TrustRecordUnavailableError extends IdentityAuthorizationError {
65
+ transient = true;
66
+ /**
67
+ * Set by `match()`: the last record read for this operator listed the origin
68
+ * with the role and is still within the seller's outage bound
69
+ * (`maxOutageContinuationSeconds` past its freshness). Only then may non-spend
70
+ * calls continue under a binding.
71
+ */
72
+ lastKnownListed = false;
73
+ constructor() {
74
+ super();
75
+ this.name = "TrustRecordUnavailableError";
76
+ this.message = "Agent trust record is temporarily unavailable";
77
+ }
78
+ }
79
+ class TrustMembershipError extends IdentityAuthorizationError {
80
+ constructor(membership = "unlisted") {
81
+ super();
82
+ this.membership = membership;
83
+ this.name = "TrustMembershipError";
84
+ }
85
+ membership;
86
+ }
87
+ function unavailable(cause) {
88
+ const error = new TrustRecordUnavailableError();
89
+ if (cause !== void 0) Object.defineProperty(error, "cause", { value: cause });
90
+ throw error;
91
+ }
92
+ class TrustHostCapacityError extends Error {
93
+ }
94
+ const FOREIGN_HOST_BUDGET = "\0foreign";
95
+ const OWN_HOST_REQUESTS_PER_MINUTE = 10;
96
+ const FOREIGN_HOST_REQUESTS_PER_MINUTE = 30;
61
97
  function failure(cause) {
62
98
  const error = new IdentityAuthorizationError();
63
99
  if (cause !== void 0) Object.defineProperty(error, "cause", { value: cause });
@@ -82,6 +118,9 @@ class TrustEvaluator {
82
118
  constructor(options = {}) {
83
119
  this.options = options;
84
120
  this.pollingIntervalSeconds = options.pollingIntervalSeconds ?? 300;
121
+ this.maxOutageContinuationSeconds = options.maxOutageContinuationSeconds ?? 4 * this.pollingIntervalSeconds;
122
+ if (!Number.isFinite(this.maxOutageContinuationSeconds) || this.maxOutageContinuationSeconds < 0)
123
+ throw new TypeError("maxOutageContinuationSeconds must be a finite, non-negative number");
85
124
  if (!Number.isFinite(this.pollingIntervalSeconds) || this.pollingIntervalSeconds < 60 || this.pollingIntervalSeconds > 1800)
86
125
  throw new TypeError("Trust polling interval must be 60\u20131800 seconds");
87
126
  }
@@ -90,10 +129,14 @@ class TrustEvaluator {
90
129
  negative = /* @__PURE__ */ new Map();
91
130
  pending = /* @__PURE__ */ new Map();
92
131
  urlRequests = /* @__PURE__ */ new Map();
132
+ /** Keyed by (operator, host): one operator's authoritative_location hops never spend another's budget. */
93
133
  hostRates = /* @__PURE__ */ new Map();
94
134
  authoritativeOperators = /* @__PURE__ */ new Set();
95
- authorityHistoryOverflowed = false;
135
+ /** Remembered authoritative operators per budget group (see authorityGroup). */
136
+ authorityGroups = /* @__PURE__ */ new Map();
137
+ privateAuthorityOperators = 0;
96
138
  pollingIntervalSeconds;
139
+ maxOutageContinuationSeconds;
97
140
  async get(domainOrOrigin, force = false) {
98
141
  let operator;
99
142
  try {
@@ -104,10 +147,10 @@ class TrustEvaluator {
104
147
  const now = this.now();
105
148
  const cached = this.cache.get(operator);
106
149
  if (!force && cached && cached.freshUntil > now) return cached;
107
- if ((this.negative.get(operator) ?? 0) > now) failure();
150
+ if ((this.negative.get(operator) ?? 0) > now) unavailable();
108
151
  let pending = this.pending.get(operator);
109
152
  if (!pending) {
110
- if (this.pending.size >= (this.options.maxConcurrentFetches ?? 16)) failure();
153
+ if (this.pending.size >= (this.options.maxConcurrentFetches ?? 16)) unavailable();
111
154
  pending = this.fetch(operator, now);
112
155
  this.pending.set(operator, pending);
113
156
  }
@@ -119,11 +162,20 @@ class TrustEvaluator {
119
162
  }
120
163
  async match(origin, role, countries) {
121
164
  const canonical = (0, import_canonicalize.canonicalAgentOrigin)(origin);
122
- const result = await this.get(canonical);
123
- if (result.status !== "present") failure();
124
- const entries = (result.record.agents ?? []).filter((agent2) => (0, import_canonicalize.canonicalAgentOrigin)(agent2.url) === canonical);
125
- if (entries.length !== 1 || !entries[0].roles.includes(role)) failure();
126
- const agent = entries[0];
165
+ let result;
166
+ try {
167
+ result = await this.get(canonical);
168
+ } catch (error) {
169
+ if (!(error instanceof TrustRecordUnavailableError)) throw error;
170
+ const last = this.cache.get((0, import_etld.eTldPlusOne)(canonical));
171
+ if (last && !listedAgent(last, canonical, role))
172
+ throw new TrustMembershipError(last.status === "present" ? "unlisted" : "absent");
173
+ error.lastKnownListed = !!last && this.now() <= last.freshUntil + this.maxOutageContinuationSeconds;
174
+ throw error;
175
+ }
176
+ const agent = listedAgent(result, canonical, role);
177
+ if (result.status !== "present") throw new TrustMembershipError("absent");
178
+ if (!agent) throw new TrustMembershipError();
127
179
  if (agent.countries && countries?.length && countries.some((country) => !agent.countries.includes(country)))
128
180
  failure();
129
181
  return { operator: result.operator, agent, record: result.record, freshUntil: result.freshUntil };
@@ -131,20 +183,55 @@ class TrustEvaluator {
131
183
  invalidate(domain) {
132
184
  this.cache.delete((0, import_etld.eTldPlusOne)(domain));
133
185
  }
186
+ /**
187
+ * Record that `operator` served a trust record. With durable history the
188
+ * in-memory set is only a cache and may evict; without it, nothing is
189
+ * evicted and an operator over its group budget or the total is refused.
190
+ */
191
+ rememberAuthority(operator, durable) {
192
+ if (this.authoritativeOperators.has(operator)) return true;
193
+ const max = this.options.maxEntries ?? 1e3;
194
+ if (durable) {
195
+ if (this.authoritativeOperators.size >= max)
196
+ this.authoritativeOperators.delete(this.authoritativeOperators.values().next().value);
197
+ this.authoritativeOperators.add(operator);
198
+ return true;
199
+ }
200
+ const group = authorityGroup(operator);
201
+ const hosted = group !== operator;
202
+ if (this.authoritativeOperators.size >= max || hosted && (this.privateAuthorityOperators >= (this.options.maxPrivateAuthoritativeOperators ?? Math.floor(max / 2)) || (this.authorityGroups.get(group) ?? 0) >= (this.options.maxAuthoritativeOperatorsPerPrivateSuffix ?? 32)))
203
+ return false;
204
+ this.authoritativeOperators.add(operator);
205
+ if (hosted) {
206
+ this.privateAuthorityOperators++;
207
+ this.authorityGroups.set(group, (this.authorityGroups.get(group) ?? 0) + 1);
208
+ }
209
+ return true;
210
+ }
211
+ /**
212
+ * Process-local view only. With durable `authorityHistory` this set is a
213
+ * cache and may have evicted an operator that durable history still records.
214
+ */
134
215
  hasAuthoritativeRecord(domain) {
135
216
  return this.authoritativeOperators.has((0, import_etld.eTldPlusOne)(domain));
136
217
  }
137
218
  now() {
138
219
  return (this.options.now ?? (() => Date.now() / 1e3))();
139
220
  }
140
- async request(url) {
221
+ async request(url, operator) {
141
222
  const parsed = new URL(url);
142
223
  if (parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.hash) failure();
143
- const host = parsed.hostname;
224
+ let own = false;
225
+ try {
226
+ own = (0, import_etld.eTldPlusOne)(parsed.hostname) === operator;
227
+ } catch {
228
+ }
229
+ const host = JSON.stringify([own ? operator : FOREIGN_HOST_BUDGET, parsed.hostname]);
230
+ const limit = own ? OWN_HOST_REQUESTS_PER_MINUTE : FOREIGN_HOST_REQUESTS_PER_MINUTE;
144
231
  const pending = this.urlRequests.get(url);
145
232
  if (pending) return pending;
146
233
  const now = this.now(), rate = this.hostRates.get(host);
147
- if (rate && now - rate.started < 60 && rate.count >= 10) failure();
234
+ if (rate && now - rate.started < 60 && rate.count >= limit) throw new TrustHostCapacityError();
148
235
  if (!rate && this.hostRates.size >= (this.options.maxEntries ?? 1e3))
149
236
  this.hostRates.delete(this.hostRates.keys().next().value);
150
237
  this.hostRates.set(
@@ -166,24 +253,24 @@ class TrustEvaluator {
166
253
  }
167
254
  async fetch(operator, now) {
168
255
  try {
169
- let response = await this.request(`https://${operator}/.well-known/trust.json`);
256
+ let response = await this.request(`https://${operator}/.well-known/trust.json`, operator);
170
257
  let result;
171
258
  if (response.status === 404 || response.status === 410) {
172
- if (this.authorityHistoryOverflowed || this.authoritativeOperators.has(operator) || await this.options.authorityHistory?.has(operator))
259
+ if (this.authoritativeOperators.has(operator) || await this.options.authorityHistory?.has(operator))
173
260
  failure();
174
261
  result = { status: "absent", operator, checkedAt: now, freshUntil: now + 60 };
175
262
  } else {
176
263
  if (response.status !== 200) failure();
177
- if (!this.authoritativeOperators.has(operator) && this.authoritativeOperators.size >= (this.options.maxEntries ?? 1e3)) {
178
- if (!this.options.authorityHistory) this.authorityHistoryOverflowed = true;
179
- this.authoritativeOperators.delete(this.authoritativeOperators.values().next().value);
264
+ if (this.options.authorityHistory) {
265
+ await this.options.authorityHistory.mark(operator);
266
+ this.rememberAuthority(operator, true);
267
+ } else if (!this.rememberAuthority(operator, false)) {
268
+ failure();
180
269
  }
181
- await this.options.authorityHistory?.mark(operator);
182
- this.authoritativeOperators.add(operator);
183
270
  let record = (0, import_strict_json.parseStrictJson)(new TextDecoder("utf-8", { fatal: true }).decode(response.body), 32);
184
271
  validateTrustRecord(record);
185
272
  if (record.authoritative_location) {
186
- response = await this.request(record.authoritative_location);
273
+ response = await this.request(record.authoritative_location, operator);
187
274
  if (response.status !== 200) failure();
188
275
  record = (0, import_strict_json.parseStrictJson)(new TextDecoder("utf-8", { fatal: true }).decode(response.body), 32);
189
276
  validateTrustRecord(record);
@@ -203,13 +290,24 @@ class TrustEvaluator {
203
290
  this.negative.delete(operator);
204
291
  return result;
205
292
  } catch (cause) {
293
+ if (cause instanceof TrustHostCapacityError) return unavailable(cause);
206
294
  if (this.negative.size >= (this.options.maxEntries ?? 1e3))
207
295
  this.negative.delete(this.negative.keys().next().value);
208
296
  this.negative.set(operator, now + 60);
209
- return failure(cause);
297
+ return unavailable(cause);
210
298
  }
211
299
  }
212
300
  }
301
+ function listedAgent(result, origin, role) {
302
+ if (result.status !== "present") return void 0;
303
+ const entries = (result.record.agents ?? []).filter((agent) => (0, import_canonicalize.canonicalAgentOrigin)(agent.url) === origin);
304
+ return entries.length === 1 && entries[0].roles.includes(role) ? entries[0] : void 0;
305
+ }
306
+ function authorityGroup(operator) {
307
+ if (!(0, import_tldts.parse)(operator, { allowPrivateDomains: true }).isPrivate) return operator;
308
+ const provider = (0, import_tldts.parse)(operator).domain;
309
+ return `private:${provider ?? operator}`;
310
+ }
213
311
  function grantMatches(grant, target, claim, now) {
214
312
  if ((0, import_etld.eTldPlusOne)(grant.grantee) !== target) return false;
215
313
  if (!grant.scopes.some(
@@ -251,6 +349,11 @@ async function authorizeDemandGrant(claim, options) {
251
349
  }
252
350
  const first = (record.record.grants ?? []).filter((g) => grantMatches(g, account, grantClaim, now));
253
351
  if (account === operator && first.length) return result("trust", [brand, operator], [first[0]]);
352
+ const selfOperated = account === brand || account === authoritativeBrand;
353
+ if (selfOperated && account !== operator) {
354
+ const direct = (record.record.grants ?? []).find((g) => grantMatches(g, operator, grantClaim, now));
355
+ if (direct) return result("trust", [brand, operator], [direct]);
356
+ }
254
357
  if (account !== operator && account !== brand && first.length) {
255
358
  const intermediary = await options.evaluator.get(account);
256
359
  if (intermediary.status === "present") {
@@ -259,7 +362,7 @@ async function authorizeDemandGrant(claim, options) {
259
362
  }
260
363
  }
261
364
  const covered = (record.record.grants ?? []).some(
262
- (g) => (0, import_etld.eTldPlusOne)(g.grantee) === account && g.scopes.some(
365
+ (g) => ((0, import_etld.eTldPlusOne)(g.grantee) === account || selfOperated && (0, import_etld.eTldPlusOne)(g.grantee) === operator) && g.scopes.some(
263
366
  (scope) => scope === "*" || scope === claim.scope || scope === "adcp:all" && claim.scope.startsWith("adcp:")
264
367
  )
265
368
  );
@@ -271,6 +374,8 @@ async function authorizeDemandGrant(claim, options) {
271
374
  0 && (module.exports = {
272
375
  IdentityAuthorizationError,
273
376
  TrustEvaluator,
377
+ TrustMembershipError,
378
+ TrustRecordUnavailableError,
274
379
  authorizeDemandGrant,
275
380
  createTrustRecord,
276
381
  validateIdentity33Schema,
@@ -4,6 +4,7 @@ import { ssrfSafeFetch } from "../net/index.mjs";
4
4
  import { canonicalAgentOrigin } from "../signing/agent-resolver/canonicalize.mjs";
5
5
  import { eTldPlusOne } from "../signing/agent-resolver/etld.mjs";
6
6
  import { parseStrictJson } from "../signing/agent-resolver/strict-json.mjs";
7
+ import { parse as parseDomain } from "tldts";
7
8
  import schemas from "./schemas.draft.mjs";
8
9
  const ajv = new Ajv({ strict: false, allErrors: false });
9
10
  addFormats(ajv);
@@ -20,6 +21,39 @@ class IdentityAuthorizationError extends Error {
20
21
  this.details = reason === "grant_absent" ? { scope: "agent", reason } : void 0;
21
22
  }
22
23
  }
24
+ class TrustRecordUnavailableError extends IdentityAuthorizationError {
25
+ transient = true;
26
+ /**
27
+ * Set by `match()`: the last record read for this operator listed the origin
28
+ * with the role and is still within the seller's outage bound
29
+ * (`maxOutageContinuationSeconds` past its freshness). Only then may non-spend
30
+ * calls continue under a binding.
31
+ */
32
+ lastKnownListed = false;
33
+ constructor() {
34
+ super();
35
+ this.name = "TrustRecordUnavailableError";
36
+ this.message = "Agent trust record is temporarily unavailable";
37
+ }
38
+ }
39
+ class TrustMembershipError extends IdentityAuthorizationError {
40
+ constructor(membership = "unlisted") {
41
+ super();
42
+ this.membership = membership;
43
+ this.name = "TrustMembershipError";
44
+ }
45
+ membership;
46
+ }
47
+ function unavailable(cause) {
48
+ const error = new TrustRecordUnavailableError();
49
+ if (cause !== void 0) Object.defineProperty(error, "cause", { value: cause });
50
+ throw error;
51
+ }
52
+ class TrustHostCapacityError extends Error {
53
+ }
54
+ const FOREIGN_HOST_BUDGET = "\0foreign";
55
+ const OWN_HOST_REQUESTS_PER_MINUTE = 10;
56
+ const FOREIGN_HOST_REQUESTS_PER_MINUTE = 30;
23
57
  function failure(cause) {
24
58
  const error = new IdentityAuthorizationError();
25
59
  if (cause !== void 0) Object.defineProperty(error, "cause", { value: cause });
@@ -44,6 +78,9 @@ class TrustEvaluator {
44
78
  constructor(options = {}) {
45
79
  this.options = options;
46
80
  this.pollingIntervalSeconds = options.pollingIntervalSeconds ?? 300;
81
+ this.maxOutageContinuationSeconds = options.maxOutageContinuationSeconds ?? 4 * this.pollingIntervalSeconds;
82
+ if (!Number.isFinite(this.maxOutageContinuationSeconds) || this.maxOutageContinuationSeconds < 0)
83
+ throw new TypeError("maxOutageContinuationSeconds must be a finite, non-negative number");
47
84
  if (!Number.isFinite(this.pollingIntervalSeconds) || this.pollingIntervalSeconds < 60 || this.pollingIntervalSeconds > 1800)
48
85
  throw new TypeError("Trust polling interval must be 60\u20131800 seconds");
49
86
  }
@@ -52,10 +89,14 @@ class TrustEvaluator {
52
89
  negative = /* @__PURE__ */ new Map();
53
90
  pending = /* @__PURE__ */ new Map();
54
91
  urlRequests = /* @__PURE__ */ new Map();
92
+ /** Keyed by (operator, host): one operator's authoritative_location hops never spend another's budget. */
55
93
  hostRates = /* @__PURE__ */ new Map();
56
94
  authoritativeOperators = /* @__PURE__ */ new Set();
57
- authorityHistoryOverflowed = false;
95
+ /** Remembered authoritative operators per budget group (see authorityGroup). */
96
+ authorityGroups = /* @__PURE__ */ new Map();
97
+ privateAuthorityOperators = 0;
58
98
  pollingIntervalSeconds;
99
+ maxOutageContinuationSeconds;
59
100
  async get(domainOrOrigin, force = false) {
60
101
  let operator;
61
102
  try {
@@ -66,10 +107,10 @@ class TrustEvaluator {
66
107
  const now = this.now();
67
108
  const cached = this.cache.get(operator);
68
109
  if (!force && cached && cached.freshUntil > now) return cached;
69
- if ((this.negative.get(operator) ?? 0) > now) failure();
110
+ if ((this.negative.get(operator) ?? 0) > now) unavailable();
70
111
  let pending = this.pending.get(operator);
71
112
  if (!pending) {
72
- if (this.pending.size >= (this.options.maxConcurrentFetches ?? 16)) failure();
113
+ if (this.pending.size >= (this.options.maxConcurrentFetches ?? 16)) unavailable();
73
114
  pending = this.fetch(operator, now);
74
115
  this.pending.set(operator, pending);
75
116
  }
@@ -81,11 +122,20 @@ class TrustEvaluator {
81
122
  }
82
123
  async match(origin, role, countries) {
83
124
  const canonical = canonicalAgentOrigin(origin);
84
- const result = await this.get(canonical);
85
- if (result.status !== "present") failure();
86
- const entries = (result.record.agents ?? []).filter((agent2) => canonicalAgentOrigin(agent2.url) === canonical);
87
- if (entries.length !== 1 || !entries[0].roles.includes(role)) failure();
88
- const agent = entries[0];
125
+ let result;
126
+ try {
127
+ result = await this.get(canonical);
128
+ } catch (error) {
129
+ if (!(error instanceof TrustRecordUnavailableError)) throw error;
130
+ const last = this.cache.get(eTldPlusOne(canonical));
131
+ if (last && !listedAgent(last, canonical, role))
132
+ throw new TrustMembershipError(last.status === "present" ? "unlisted" : "absent");
133
+ error.lastKnownListed = !!last && this.now() <= last.freshUntil + this.maxOutageContinuationSeconds;
134
+ throw error;
135
+ }
136
+ const agent = listedAgent(result, canonical, role);
137
+ if (result.status !== "present") throw new TrustMembershipError("absent");
138
+ if (!agent) throw new TrustMembershipError();
89
139
  if (agent.countries && countries?.length && countries.some((country) => !agent.countries.includes(country)))
90
140
  failure();
91
141
  return { operator: result.operator, agent, record: result.record, freshUntil: result.freshUntil };
@@ -93,20 +143,55 @@ class TrustEvaluator {
93
143
  invalidate(domain) {
94
144
  this.cache.delete(eTldPlusOne(domain));
95
145
  }
146
+ /**
147
+ * Record that `operator` served a trust record. With durable history the
148
+ * in-memory set is only a cache and may evict; without it, nothing is
149
+ * evicted and an operator over its group budget or the total is refused.
150
+ */
151
+ rememberAuthority(operator, durable) {
152
+ if (this.authoritativeOperators.has(operator)) return true;
153
+ const max = this.options.maxEntries ?? 1e3;
154
+ if (durable) {
155
+ if (this.authoritativeOperators.size >= max)
156
+ this.authoritativeOperators.delete(this.authoritativeOperators.values().next().value);
157
+ this.authoritativeOperators.add(operator);
158
+ return true;
159
+ }
160
+ const group = authorityGroup(operator);
161
+ const hosted = group !== operator;
162
+ if (this.authoritativeOperators.size >= max || hosted && (this.privateAuthorityOperators >= (this.options.maxPrivateAuthoritativeOperators ?? Math.floor(max / 2)) || (this.authorityGroups.get(group) ?? 0) >= (this.options.maxAuthoritativeOperatorsPerPrivateSuffix ?? 32)))
163
+ return false;
164
+ this.authoritativeOperators.add(operator);
165
+ if (hosted) {
166
+ this.privateAuthorityOperators++;
167
+ this.authorityGroups.set(group, (this.authorityGroups.get(group) ?? 0) + 1);
168
+ }
169
+ return true;
170
+ }
171
+ /**
172
+ * Process-local view only. With durable `authorityHistory` this set is a
173
+ * cache and may have evicted an operator that durable history still records.
174
+ */
96
175
  hasAuthoritativeRecord(domain) {
97
176
  return this.authoritativeOperators.has(eTldPlusOne(domain));
98
177
  }
99
178
  now() {
100
179
  return (this.options.now ?? (() => Date.now() / 1e3))();
101
180
  }
102
- async request(url) {
181
+ async request(url, operator) {
103
182
  const parsed = new URL(url);
104
183
  if (parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.hash) failure();
105
- const host = parsed.hostname;
184
+ let own = false;
185
+ try {
186
+ own = eTldPlusOne(parsed.hostname) === operator;
187
+ } catch {
188
+ }
189
+ const host = JSON.stringify([own ? operator : FOREIGN_HOST_BUDGET, parsed.hostname]);
190
+ const limit = own ? OWN_HOST_REQUESTS_PER_MINUTE : FOREIGN_HOST_REQUESTS_PER_MINUTE;
106
191
  const pending = this.urlRequests.get(url);
107
192
  if (pending) return pending;
108
193
  const now = this.now(), rate = this.hostRates.get(host);
109
- if (rate && now - rate.started < 60 && rate.count >= 10) failure();
194
+ if (rate && now - rate.started < 60 && rate.count >= limit) throw new TrustHostCapacityError();
110
195
  if (!rate && this.hostRates.size >= (this.options.maxEntries ?? 1e3))
111
196
  this.hostRates.delete(this.hostRates.keys().next().value);
112
197
  this.hostRates.set(
@@ -128,24 +213,24 @@ class TrustEvaluator {
128
213
  }
129
214
  async fetch(operator, now) {
130
215
  try {
131
- let response = await this.request(`https://${operator}/.well-known/trust.json`);
216
+ let response = await this.request(`https://${operator}/.well-known/trust.json`, operator);
132
217
  let result;
133
218
  if (response.status === 404 || response.status === 410) {
134
- if (this.authorityHistoryOverflowed || this.authoritativeOperators.has(operator) || await this.options.authorityHistory?.has(operator))
219
+ if (this.authoritativeOperators.has(operator) || await this.options.authorityHistory?.has(operator))
135
220
  failure();
136
221
  result = { status: "absent", operator, checkedAt: now, freshUntil: now + 60 };
137
222
  } else {
138
223
  if (response.status !== 200) failure();
139
- if (!this.authoritativeOperators.has(operator) && this.authoritativeOperators.size >= (this.options.maxEntries ?? 1e3)) {
140
- if (!this.options.authorityHistory) this.authorityHistoryOverflowed = true;
141
- this.authoritativeOperators.delete(this.authoritativeOperators.values().next().value);
224
+ if (this.options.authorityHistory) {
225
+ await this.options.authorityHistory.mark(operator);
226
+ this.rememberAuthority(operator, true);
227
+ } else if (!this.rememberAuthority(operator, false)) {
228
+ failure();
142
229
  }
143
- await this.options.authorityHistory?.mark(operator);
144
- this.authoritativeOperators.add(operator);
145
230
  let record = parseStrictJson(new TextDecoder("utf-8", { fatal: true }).decode(response.body), 32);
146
231
  validateTrustRecord(record);
147
232
  if (record.authoritative_location) {
148
- response = await this.request(record.authoritative_location);
233
+ response = await this.request(record.authoritative_location, operator);
149
234
  if (response.status !== 200) failure();
150
235
  record = parseStrictJson(new TextDecoder("utf-8", { fatal: true }).decode(response.body), 32);
151
236
  validateTrustRecord(record);
@@ -165,13 +250,24 @@ class TrustEvaluator {
165
250
  this.negative.delete(operator);
166
251
  return result;
167
252
  } catch (cause) {
253
+ if (cause instanceof TrustHostCapacityError) return unavailable(cause);
168
254
  if (this.negative.size >= (this.options.maxEntries ?? 1e3))
169
255
  this.negative.delete(this.negative.keys().next().value);
170
256
  this.negative.set(operator, now + 60);
171
- return failure(cause);
257
+ return unavailable(cause);
172
258
  }
173
259
  }
174
260
  }
261
+ function listedAgent(result, origin, role) {
262
+ if (result.status !== "present") return void 0;
263
+ const entries = (result.record.agents ?? []).filter((agent) => canonicalAgentOrigin(agent.url) === origin);
264
+ return entries.length === 1 && entries[0].roles.includes(role) ? entries[0] : void 0;
265
+ }
266
+ function authorityGroup(operator) {
267
+ if (!parseDomain(operator, { allowPrivateDomains: true }).isPrivate) return operator;
268
+ const provider = parseDomain(operator).domain;
269
+ return `private:${provider ?? operator}`;
270
+ }
175
271
  function grantMatches(grant, target, claim, now) {
176
272
  if (eTldPlusOne(grant.grantee) !== target) return false;
177
273
  if (!grant.scopes.some(
@@ -213,6 +309,11 @@ async function authorizeDemandGrant(claim, options) {
213
309
  }
214
310
  const first = (record.record.grants ?? []).filter((g) => grantMatches(g, account, grantClaim, now));
215
311
  if (account === operator && first.length) return result("trust", [brand, operator], [first[0]]);
312
+ const selfOperated = account === brand || account === authoritativeBrand;
313
+ if (selfOperated && account !== operator) {
314
+ const direct = (record.record.grants ?? []).find((g) => grantMatches(g, operator, grantClaim, now));
315
+ if (direct) return result("trust", [brand, operator], [direct]);
316
+ }
216
317
  if (account !== operator && account !== brand && first.length) {
217
318
  const intermediary = await options.evaluator.get(account);
218
319
  if (intermediary.status === "present") {
@@ -221,7 +322,7 @@ async function authorizeDemandGrant(claim, options) {
221
322
  }
222
323
  }
223
324
  const covered = (record.record.grants ?? []).some(
224
- (g) => eTldPlusOne(g.grantee) === account && g.scopes.some(
325
+ (g) => (eTldPlusOne(g.grantee) === account || selfOperated && eTldPlusOne(g.grantee) === operator) && g.scopes.some(
225
326
  (scope) => scope === "*" || scope === claim.scope || scope === "adcp:all" && claim.scope.startsWith("adcp:")
226
327
  )
227
328
  );
@@ -232,6 +333,8 @@ async function authorizeDemandGrant(claim, options) {
232
333
  export {
233
334
  IdentityAuthorizationError,
234
335
  TrustEvaluator,
336
+ TrustMembershipError,
337
+ TrustRecordUnavailableError,
235
338
  authorizeDemandGrant,
236
339
  createTrustRecord,
237
340
  validateIdentity33Schema,
@@ -1,9 +1,9 @@
1
1
  /** Experimental drafts; the released SDK wire/schema pin remains 3.2.4. */
2
- export type AgentOnboardingMode = 'open' | 'application' | 'invite_only';
2
+ import type { GeneratedAccountCapabilities, ResolveAgentOnboarding } from './generated-compat.mjs';
3
3
  export type BillingParty = 'operator' | 'agent' | 'advertiser';
4
- export type AgentOnboardingRequirementKind = 'agent_identity' | 'grant' | 'billing_entity' | 'contract' | 'credit_application' | 'aao_verified' | 'storyboard' | 'sandbox' | 'experimental_feature';
5
- export interface AgentOnboardingRequirement {
6
- kind: AgentOnboardingRequirementKind | (string & {});
4
+ type DraftAgentOnboardingRequirementKind = 'agent_identity' | 'grant' | 'billing_entity' | 'contract' | 'credit_application' | 'aao_verified' | 'storyboard' | 'sandbox' | 'experimental_feature';
5
+ interface DraftAgentOnboardingRequirement {
6
+ kind: DraftAgentOnboardingRequirementKind;
7
7
  role?: string;
8
8
  verification_modes?: string[];
9
9
  specialisms?: string[];
@@ -11,23 +11,50 @@ export interface AgentOnboardingRequirement {
11
11
  id?: string;
12
12
  ext?: Record<string, unknown>;
13
13
  }
14
- export interface AgentOnboardingTier {
15
- mode: AgentOnboardingMode;
14
+ interface DraftAgentOnboardingTier {
15
+ mode: 'open' | 'application' | 'invite_only';
16
16
  url?: string;
17
- requirements?: AgentOnboardingRequirement[];
17
+ requirements?: DraftAgentOnboardingRequirement[];
18
18
  ext?: Record<string, unknown>;
19
19
  }
20
- export interface AgentOnboarding {
21
- sandbox?: AgentOnboardingTier;
22
- billing?: Partial<Record<BillingParty, AgentOnboardingTier>>;
20
+ interface DraftAgentOnboarding {
21
+ sandbox?: DraftAgentOnboardingTier;
22
+ billing?: Partial<Record<BillingParty, DraftAgentOnboardingTier>>;
23
23
  url?: string;
24
24
  documentation_url?: string;
25
25
  message?: string;
26
26
  ext?: Record<string, unknown>;
27
27
  }
28
+ /** `get_adcp_capabilities.account.agent_onboarding`: how a seller onboards buyer agents. */
29
+ export type AgentOnboarding = ResolveAgentOnboarding<GeneratedAccountCapabilities, DraftAgentOnboarding>;
30
+ type AgentOnboardingBilling = NonNullable<AgentOnboarding['billing']>;
31
+ /** The onboarding mode for sandbox access or for one billing party. */
32
+ export type AgentOnboardingTier = NonNullable<AgentOnboarding['sandbox']> | NonNullable<AgentOnboardingBilling[keyof AgentOnboardingBilling]>;
33
+ export type AgentOnboardingMode = AgentOnboardingTier['mode'];
34
+ /** One requirement an onboarding mode imposes, discriminated by `kind`. */
35
+ export type AgentOnboardingRequirement = NonNullable<AgentOnboardingTier['requirements']>[number];
36
+ export type AgentOnboardingRequirementKind = AgentOnboardingRequirement['kind'];
37
+ /**
38
+ * A requirement as an evaluator sees it. `kind` accepts any string because a
39
+ * counterparty on a newer revision may publish kinds this SDK does not know,
40
+ * and readers MUST treat those as unsatisfiable rather than reject the
41
+ * posture. Structural on purpose: every {@link AgentOnboardingRequirement} is
42
+ * assignable to it, whichever shape the pinned schemas give that type.
43
+ * `verified_specialisms` is the revised #8170 name for `specialisms`.
44
+ */
45
+ export interface ObservedAgentOnboardingRequirement {
46
+ kind: AgentOnboardingRequirementKind | (string & {});
47
+ role?: string;
48
+ verification_modes?: readonly string[];
49
+ specialisms?: readonly string[];
50
+ verified_specialisms?: readonly string[];
51
+ adcp_version?: string;
52
+ id?: string;
53
+ ext?: Record<string, unknown>;
54
+ }
28
55
  export interface BuyerOnboardingPosture {
29
56
  mode: 'considers_unsolicited' | 'application' | 'invite_only';
30
- requirements?: AgentOnboardingRequirement[];
57
+ requirements?: ObservedAgentOnboardingRequirement[];
31
58
  url?: string;
32
59
  documentation_url?: string;
33
60
  message?: string;
@@ -125,3 +152,4 @@ export type Identity33GetPrincipalResponse = Omit<import('../types/tools.generat
125
152
  kind: 'failed';
126
153
  }>;
127
154
  };
155
+ export {};