@adcp/sdk 16.0.0 → 16.1.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 (57) hide show
  1. package/README.md +52 -0
  2. package/bin/adcp-identity.js +3 -2
  3. package/dist/lib/client/core.d.mts +1 -1
  4. package/dist/lib/client/core.d.ts +1 -1
  5. package/dist/lib/core/AgentClient.d.mts +8 -1
  6. package/dist/lib/core/AgentClient.d.ts +8 -1
  7. package/dist/lib/core/AgentClient.js +10 -0
  8. package/dist/lib/core/AgentClient.mjs +10 -0
  9. package/dist/lib/core/AsyncHandler.d.mts +2 -0
  10. package/dist/lib/core/AsyncHandler.d.ts +2 -0
  11. package/dist/lib/core/AsyncHandler.js +1 -1
  12. package/dist/lib/core/AsyncHandler.mjs +1 -1
  13. package/dist/lib/core/ConversationTypes.d.mts +32 -0
  14. package/dist/lib/core/ConversationTypes.d.ts +32 -0
  15. package/dist/lib/core/SingleAgentClient.d.mts +11 -1
  16. package/dist/lib/core/SingleAgentClient.d.ts +11 -1
  17. package/dist/lib/core/SingleAgentClient.js +25 -6
  18. package/dist/lib/core/SingleAgentClient.mjs +26 -7
  19. package/dist/lib/core/TaskExecutor.d.mts +3 -1
  20. package/dist/lib/core/TaskExecutor.d.ts +3 -1
  21. package/dist/lib/core/TaskExecutor.js +127 -34
  22. package/dist/lib/core/TaskExecutor.mjs +127 -34
  23. package/dist/lib/core/task-deadline.d.mts +2 -0
  24. package/dist/lib/core/task-deadline.d.ts +2 -0
  25. package/dist/lib/core/task-deadline.js +20 -2
  26. package/dist/lib/core/task-deadline.mjs +19 -2
  27. package/dist/lib/identity-3.3/schemas.draft.js +1 -1
  28. package/dist/lib/identity-3.3/schemas.draft.mjs +1 -1
  29. package/dist/lib/identity-3.3/trust.d.mts +8 -0
  30. package/dist/lib/identity-3.3/trust.d.ts +8 -0
  31. package/dist/lib/identity-3.3/trust.js +41 -12
  32. package/dist/lib/identity-3.3/trust.mjs +40 -12
  33. package/dist/lib/index.d.mts +1 -1
  34. package/dist/lib/index.d.ts +1 -1
  35. package/dist/lib/server/account-sync-contract.js +4 -1
  36. package/dist/lib/server/account-sync-contract.mjs +4 -1
  37. package/dist/lib/server/decisioning/runtime/from-platform.js +22 -17
  38. package/dist/lib/server/decisioning/runtime/from-platform.mjs +22 -17
  39. package/dist/lib/signing/web-bot-auth.js +20 -5
  40. package/dist/lib/signing/web-bot-auth.mjs +21 -6
  41. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  42. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  43. package/dist/lib/v2/projection/catalog.d.mts +6 -0
  44. package/dist/lib/v2/projection/catalog.d.ts +6 -0
  45. package/dist/lib/v2/projection/catalog.js +5 -0
  46. package/dist/lib/v2/projection/catalog.mjs +4 -0
  47. package/dist/lib/v2/projection/v1-to-v2.js +3 -2
  48. package/dist/lib/v2/projection/v1-to-v2.mjs +4 -3
  49. package/dist/lib/version.d.mts +3 -3
  50. package/dist/lib/version.d.ts +3 -3
  51. package/dist/lib/version.js +3 -3
  52. package/dist/lib/version.mjs +3 -3
  53. package/docs/TYPE-SUMMARY.md +12 -2
  54. package/docs/llms.txt +3 -3
  55. package/docs/migration-14.x-rc-worksheet.md +4 -4
  56. package/docs/migration-agent-identity-3.3.md +21 -13
  57. package/package.json +1 -1
@@ -614,6 +614,10 @@ class TaskExecutor {
614
614
  * @internal
615
615
  */
616
616
  async observeExternalTaskStatus(taskId, status, result, identity = {}) {
617
+ const task = this.activeTasks.get(taskId);
618
+ if (task && (identity.serverTaskId !== void 0 && task.serverTaskId !== void 0 && identity.serverTaskId !== task.serverTaskId || identity.taskType !== void 0 && identity.taskType !== task.taskName)) {
619
+ throw new ConfigurationError("The pushed task identity does not match the tracked operation.");
620
+ }
617
621
  if (!["completed", "failed", "rejected", "canceled", "governance-denied", "aborted"].includes(status)) {
618
622
  return { settled: false, result };
619
623
  }
@@ -642,7 +646,9 @@ class TaskExecutor {
642
646
  this.pendingExternalTaskObservations.set(taskId, pending);
643
647
  return { settled: false, queued: true };
644
648
  }
645
- this.updateTaskStatus(taskId, status, result);
649
+ if (task && task.serverTaskId === void 0 && identity.serverTaskId !== void 0)
650
+ task.serverTaskId = identity.serverTaskId;
651
+ if (task && !TERMINAL_TASK_STATUSES.has(task.status)) this.updateTaskStatus(taskId, status, result);
646
652
  return { settled: false, result };
647
653
  }
648
654
  /** Whether this process can safely settle or reject a durable callback for the operation. */
@@ -770,6 +776,9 @@ class TaskExecutor {
770
776
  this.requireDeferredSettlementOperationRouting(this.config.deferredStorage, true);
771
777
  }
772
778
  const taskId = getTaskOperationId(options) ?? randomUUID();
779
+ if (this.activeTasks.has(taskId) || this.compactedTaskIds.has(taskId) || this.deferredTerminalPublicationTaskIds.has(taskId) || this.closedExternalTaskSettlementTaskIds.has(taskId) || this.externalTaskSettlementHandlers.has(taskId)) {
780
+ throw new ConfigurationError("operationId is already in use by this client; track the existing task instead.");
781
+ }
773
782
  const startTime = Date.now();
774
783
  const workingTimeout = this.config.workingTimeout || 12e4;
775
784
  const wireAdcpVersion = options.wireAdcpVersion ?? this.config.wireAdcpVersion;
@@ -1045,12 +1054,14 @@ class TaskExecutor {
1045
1054
  );
1046
1055
  throwIfAborted(postDispatchOptions.signal);
1047
1056
  const respStatus = this.responseParser.getStatus(response);
1057
+ taskState.serverTaskId = this.responseParser.getTaskId(response);
1048
1058
  await this.config.onActivity?.({
1049
1059
  type: "protocol_response",
1050
1060
  operation_id: taskId,
1051
1061
  agent_id: agent.id,
1052
1062
  context_id: options.contextId,
1053
1063
  task_id: taskId,
1064
+ server_task_id: taskState.serverTaskId,
1054
1065
  task_type: taskName,
1055
1066
  status: respStatus,
1056
1067
  payload: response,
@@ -1225,6 +1236,9 @@ class TaskExecutor {
1225
1236
  }
1226
1237
  async handleAsyncResponse(agent, taskId, taskName, params, response, messages, inputHandler, options = {}, debugLogs = [], startTime = Date.now(), deferTerminalTaskStatus = false, persistPausedContinuation = true, serverVersion = "v3", deferredClientContext, requireDeferredSettlementResumeAuthorization = false) {
1227
1238
  const status = this.responseParser.getStatus(response);
1239
+ const serverTaskId = this.responseParser.getTaskId(response);
1240
+ const task = this.activeTasks.get(taskId);
1241
+ if (task && serverTaskId) task.serverTaskId = serverTaskId;
1228
1242
  switch (status) {
1229
1243
  case ADCP_STATUS.COMPLETED:
1230
1244
  const completedData = this.extractResponseData(response, debugLogs, taskName, serverVersion);
@@ -1580,8 +1594,70 @@ class TaskExecutor {
1580
1594
  debug_logs: debugLogs
1581
1595
  };
1582
1596
  }
1597
+ /** Rebuild the polling continuation for seller work persisted by the caller. */
1598
+ trackTask(agent, request) {
1599
+ if (typeof request.taskId !== "string" || !request.taskId.trim()) {
1600
+ throw new TypeError("taskId must be a non-empty seller task id.");
1601
+ }
1602
+ if (typeof request.taskType !== "string" || !request.taskType.trim()) {
1603
+ throw new TypeError("taskType must be a non-empty tool name.");
1604
+ }
1605
+ const operationId = getTaskOperationId(request) ?? randomUUID();
1606
+ const existing = this.activeTasks.get(operationId);
1607
+ if (existing && (existing.serverTaskId !== request.taskId || existing.taskName !== request.taskType || existing.agent.id !== agent.id || existing.agent.protocol !== agent.protocol || existing.status === "pending")) {
1608
+ throw new ConfigurationError(
1609
+ "operationId is already tracked by this client for a different or dispatching task."
1610
+ );
1611
+ }
1612
+ if (this.deferredTerminalPublicationTaskIds.has(operationId) || this.closedExternalTaskSettlementTaskIds.has(operationId) || this.externalTaskSettlementHandlers.has(operationId)) {
1613
+ throw new ConfigurationError("This operation requires its original continuation or durable settlement recovery.");
1614
+ }
1615
+ const startTime = existing?.startTime ?? Date.now();
1616
+ if (!existing)
1617
+ this.activeTasks.set(operationId, {
1618
+ taskId: operationId,
1619
+ serverTaskId: request.taskId,
1620
+ taskName: request.taskType,
1621
+ status: "submitted",
1622
+ messages: [],
1623
+ startTime,
1624
+ attempt: 0,
1625
+ maxAttempts: 0,
1626
+ options: {},
1627
+ agent: { id: agent.id, name: agent.name, protocol: agent.protocol },
1628
+ serverVersion: "v3"
1629
+ });
1630
+ if (!existing) this.compactIntermediateTaskState(operationId, "submitted");
1631
+ const continuation = this.createSubmittedContinuation(
1632
+ agent,
1633
+ operationId,
1634
+ request.taskType,
1635
+ request.taskId,
1636
+ void 0,
1637
+ snapshotTransportOptions(request.transport ?? this.config.transport),
1638
+ {
1639
+ ...this.buildMetadata({
1640
+ taskId: operationId,
1641
+ taskName: request.taskType,
1642
+ agent,
1643
+ startTime,
1644
+ status: "submitted"
1645
+ }),
1646
+ serverTaskId: request.taskId,
1647
+ a2aTaskId: request.cancelOnAbort === true ? request.a2aTaskId : void 0
1648
+ },
1649
+ false,
1650
+ "v3",
1651
+ this.config.wireAdcpVersion,
1652
+ this.config.versionEnvelope,
1653
+ request.signal
1654
+ );
1655
+ return { ...continuation, operationId };
1656
+ }
1583
1657
  /** Build polling closures around one authoritative seller work handle. */
1584
- createSubmittedContinuation(agent, taskId, taskName, serverTaskId, webhookUrl, pollingTransport, metadata, deferTerminalTaskStatus, serverVersion, wireAdcpVersion, versionEnvelope) {
1658
+ createSubmittedContinuation(agent, taskId, taskName, serverTaskId, webhookUrl, pollingTransport, metadata, deferTerminalTaskStatus, serverVersion, wireAdcpVersion, versionEnvelope, pollingSignal) {
1659
+ const registeredTask = this.activeTasks.get(taskId);
1660
+ const canPublish = (status) => registeredTask !== void 0 && this.activeTasks.get(taskId) === registeredTask && !TERMINAL_TASK_STATUSES.has(registeredTask.status) && registeredTask.status !== status;
1585
1661
  return {
1586
1662
  taskId: serverTaskId,
1587
1663
  webhookUrl,
@@ -1591,25 +1667,27 @@ class TaskExecutor {
1591
1667
  agent,
1592
1668
  serverTaskId,
1593
1669
  transportSnapshot,
1594
- void 0,
1670
+ pollingSignal,
1595
1671
  taskName,
1596
1672
  serverVersion,
1597
1673
  wireAdcpVersion,
1598
- versionEnvelope
1674
+ versionEnvelope,
1675
+ taskId
1599
1676
  )).task;
1600
- if (!deferTerminalTaskStatus && ["completed", "failed", "rejected", "canceled"].includes(task.status)) {
1677
+ if (!deferTerminalTaskStatus && ["completed", "failed", "rejected", "canceled"].includes(task.status) && canPublish(task.status)) {
1601
1678
  this.updateTaskStatus(taskId, task.status, task.result, task.error);
1602
1679
  }
1603
- return task;
1680
+ return { ...task, operationId: taskId, serverTaskId };
1604
1681
  },
1605
1682
  waitForCompletion: async (pollInterval = 6e4, signal, requireExactTaskIdentity = false) => {
1606
1683
  void requireExactTaskIdentity;
1684
+ const completionSignal = pollingSignal && signal ? AbortSignal.any([pollingSignal, signal]) : signal ?? pollingSignal;
1607
1685
  const completed = await this.pollTaskCompletion(
1608
1686
  agent,
1609
1687
  serverTaskId,
1610
1688
  pollInterval,
1611
1689
  pollingTransport,
1612
- signal,
1690
+ completionSignal,
1613
1691
  metadata.a2aTaskId,
1614
1692
  taskName,
1615
1693
  taskId,
@@ -1617,7 +1695,9 @@ class TaskExecutor {
1617
1695
  wireAdcpVersion,
1618
1696
  versionEnvelope
1619
1697
  );
1620
- if (!deferTerminalTaskStatus || !TERMINAL_TASK_STATUSES.has(completed.status)) {
1698
+ if ((!deferTerminalTaskStatus || !TERMINAL_TASK_STATUSES.has(completed.status)) && // Cancellation, identity mismatches, missing tasks, and malformed
1699
+ // responses establish observation failure, not seller task failure.
1700
+ (!TERMINAL_TASK_STATUSES.has(completed.status) || isAuthoritativePolledTerminal(completed)) && canPublish(completed.status)) {
1621
1701
  this.updateTaskStatus(taskId, completed.status, completed.data, completed.error);
1622
1702
  }
1623
1703
  return completed;
@@ -1630,7 +1710,9 @@ class TaskExecutor {
1630
1710
  * local task publication only after those final wrappers return.
1631
1711
  */
1632
1712
  attachSettledContinuationTaskStatus(taskId, result) {
1713
+ const registeredTask = this.activeTasks.get(taskId);
1633
1714
  const publish = (status, data, error, identity) => {
1715
+ if (registeredTask === void 0 || this.activeTasks.get(taskId) !== registeredTask) return;
1634
1716
  this.publishSettledTaskStatus(taskId, status, data, error, false, identity);
1635
1717
  };
1636
1718
  if (result.submitted) {
@@ -2261,7 +2343,7 @@ class TaskExecutor {
2261
2343
  return [];
2262
2344
  }
2263
2345
  }
2264
- async getTaskStatusWithRawResponse(agent, taskId, transport, signal, expectedTaskType, serverVersion, wireAdcpVersion, versionEnvelope) {
2346
+ async getTaskStatusWithRawResponse(agent, taskId, transport, signal, expectedTaskType, serverVersion, wireAdcpVersion, versionEnvelope, operationId) {
2265
2347
  const toolName = agent.protocol === "mcp" ? "tasks_get" : "tasks/get";
2266
2348
  const response = await ProtocolClient.callTool(
2267
2349
  agent,
@@ -2280,7 +2362,8 @@ class TaskExecutor {
2280
2362
  signal,
2281
2363
  onTransportActivity: this.config.onTransportActivity,
2282
2364
  transportActivityContext: {
2283
- taskId
2365
+ taskId,
2366
+ ...operationId !== void 0 && { operationId }
2284
2367
  }
2285
2368
  }
2286
2369
  );
@@ -2312,12 +2395,13 @@ class TaskExecutor {
2312
2395
  wireOptions?.versionEnvelope
2313
2396
  )).task;
2314
2397
  }
2315
- async pollTaskCompletion(agent, taskId, pollInterval = 6e4, transport, signal, a2aCancellationTaskId, expectedTaskType, metadataTaskId = taskId, serverVersion, wireAdcpVersion, versionEnvelope) {
2398
+ async pollTaskCompletion(agent, taskId, pollInterval = 6e4, transport, signal, a2aCancellationTaskId, expectedTaskType, metadataTaskId, serverVersion, wireAdcpVersion, versionEnvelope) {
2316
2399
  const transportSnapshot = snapshotTransportOptions(transport ?? this.config.transport);
2317
2400
  if (!Number.isFinite(pollInterval) || pollInterval < 0 || pollInterval > MAX_TIMER_DELAY_MS) {
2318
2401
  throw new RangeError(`pollInterval must be a finite non-negative number <= ${MAX_TIMER_DELAY_MS}`);
2319
2402
  }
2320
2403
  const pollStartTime = Date.now();
2404
+ const correlationId = metadataTaskId ?? taskId;
2321
2405
  const withServerTaskId = (metadata) => ({
2322
2406
  ...metadata,
2323
2407
  serverTaskId: taskId
@@ -2345,8 +2429,8 @@ class TaskExecutor {
2345
2429
  error: `tasks/get polling was cancelled before a terminal state was observed: ${reason}`,
2346
2430
  metadata: withServerTaskId(
2347
2431
  this.buildMetadata({
2348
- taskId: metadataTaskId,
2349
- taskName: "unknown",
2432
+ taskId: correlationId,
2433
+ taskName: expectedTaskType ?? "unknown",
2350
2434
  agent,
2351
2435
  startTime: pollStartTime,
2352
2436
  status: "failed"
@@ -2365,11 +2449,15 @@ class TaskExecutor {
2365
2449
  expectedTaskType,
2366
2450
  serverVersion,
2367
2451
  wireAdcpVersion,
2368
- versionEnvelope
2452
+ versionEnvelope,
2453
+ metadataTaskId
2369
2454
  );
2370
2455
  status = pollResult.task;
2371
2456
  rawResponse = pollResult.rawResponse;
2372
2457
  } catch (err) {
2458
+ if (signal?.aborted && agent.protocol === "a2a" && a2aCancellationTaskId && isAbortOrTimeoutError(err)) {
2459
+ continue;
2460
+ }
2373
2461
  if (err instanceof TaskBindingMismatchError) {
2374
2462
  return attachMatch({
2375
2463
  success: false,
@@ -2377,8 +2465,8 @@ class TaskExecutor {
2377
2465
  error: err.message,
2378
2466
  metadata: withServerTaskId(
2379
2467
  this.buildMetadata({
2380
- taskId: metadataTaskId,
2381
- taskName: "unknown",
2468
+ taskId: correlationId,
2469
+ taskName: expectedTaskType ?? "unknown",
2382
2470
  agent,
2383
2471
  startTime: pollStartTime,
2384
2472
  status: "failed",
@@ -2395,8 +2483,8 @@ class TaskExecutor {
2395
2483
  error: `Task ${taskId} is no longer queryable \u2014 it may have been completed and evicted by the seller before this poll arrived. Consider using push notifications (reporting_webhook) instead of polling, or configuring a longer task retention TTL on the seller.`,
2396
2484
  metadata: withServerTaskId(
2397
2485
  this.buildMetadata({
2398
- taskId: metadataTaskId,
2399
- taskName: "unknown",
2486
+ taskId: correlationId,
2487
+ taskName: expectedTaskType ?? "unknown",
2400
2488
  agent,
2401
2489
  startTime: pollStartTime,
2402
2490
  status: "failed"
@@ -2411,13 +2499,15 @@ class TaskExecutor {
2411
2499
  success: false,
2412
2500
  status: "failed",
2413
2501
  error: "The seller returned a task outside the requested durable task identity.",
2414
- metadata: this.buildMetadata({
2415
- taskId,
2416
- taskName: "unknown",
2417
- agent,
2418
- startTime: pollStartTime,
2419
- status: "failed"
2420
- })
2502
+ metadata: withServerTaskId(
2503
+ this.buildMetadata({
2504
+ taskId: correlationId,
2505
+ taskName: expectedTaskType ?? "unknown",
2506
+ agent,
2507
+ startTime: pollStartTime,
2508
+ status: "failed"
2509
+ })
2510
+ )
2421
2511
  });
2422
2512
  }
2423
2513
  if (status.status === ADCP_STATUS.COMPLETED) {
@@ -2430,7 +2520,7 @@ class TaskExecutor {
2430
2520
  data: status.result,
2431
2521
  metadata: withServerTaskId(
2432
2522
  this.buildMetadata({
2433
- taskId: metadataTaskId,
2523
+ taskId: correlationId,
2434
2524
  taskName: status.taskType,
2435
2525
  agent,
2436
2526
  responseTimeMs: Date.now() - status.createdAt,
@@ -2449,11 +2539,11 @@ class TaskExecutor {
2449
2539
  data: status.result,
2450
2540
  error: this.extractOperationError(status.result),
2451
2541
  adcpError: asyncResultErr,
2452
- errorInstance: this.buildErrorInstance(metadataTaskId, asyncResultErr),
2542
+ errorInstance: this.buildErrorInstance(correlationId, asyncResultErr),
2453
2543
  correlationId: extractCorrelationId(status.result),
2454
2544
  metadata: withServerTaskId(
2455
2545
  this.buildMetadata({
2456
- taskId: metadataTaskId,
2546
+ taskId: correlationId,
2457
2547
  taskName: status.taskType,
2458
2548
  agent,
2459
2549
  responseTimeMs: Date.now() - status.createdAt,
@@ -2473,11 +2563,11 @@ class TaskExecutor {
2473
2563
  data: status.result,
2474
2564
  error: status.error || status.message || `Task ${status.status}`,
2475
2565
  adcpError: asyncFailedErr,
2476
- errorInstance: this.buildErrorInstance(metadataTaskId, asyncFailedErr),
2566
+ errorInstance: this.buildErrorInstance(correlationId, asyncFailedErr),
2477
2567
  correlationId: extractCorrelationId(status.result),
2478
2568
  metadata: withServerTaskId(
2479
2569
  this.buildMetadata({
2480
- taskId: metadataTaskId,
2570
+ taskId: correlationId,
2481
2571
  taskName: status.taskType,
2482
2572
  agent,
2483
2573
  responseTimeMs: Date.now() - status.createdAt,
@@ -2495,7 +2585,7 @@ class TaskExecutor {
2495
2585
  data: status.result,
2496
2586
  metadata: withServerTaskId(
2497
2587
  this.buildMetadata({
2498
- taskId: metadataTaskId,
2588
+ taskId: correlationId,
2499
2589
  taskName: status.taskType,
2500
2590
  agent,
2501
2591
  responseTimeMs: Date.now() - status.createdAt,
@@ -2512,8 +2602,8 @@ class TaskExecutor {
2512
2602
  error: `tasks/get returned an unrecognizable response (parsed status: "unknown", taskId: "${status.taskId}"). The seller may not implement tasks/get as an AdCP skill, or its response did not match any supported envelope shape (flat AdCP, MCP structuredContent, or A2A DataPart artifact).`,
2513
2603
  metadata: withServerTaskId(
2514
2604
  this.buildMetadata({
2515
- taskId: metadataTaskId,
2516
- taskName: "unknown",
2605
+ taskId: correlationId,
2606
+ taskName: expectedTaskType ?? "unknown",
2517
2607
  agent,
2518
2608
  startTime: pollStartTime,
2519
2609
  status: "failed",
@@ -4497,6 +4587,8 @@ class TaskExecutor {
4497
4587
  task.status = status;
4498
4588
  const taskInfo = {
4499
4589
  taskId: task.taskId,
4590
+ operationId: task.taskId,
4591
+ serverTaskId: task.serverTaskId,
4500
4592
  status,
4501
4593
  taskType: task.taskName,
4502
4594
  createdAt: task.startTime,
@@ -4511,6 +4603,7 @@ class TaskExecutor {
4511
4603
  agent_id: task.agent.id,
4512
4604
  context_id: void 0,
4513
4605
  task_id: task.taskId,
4606
+ server_task_id: task.serverTaskId,
4514
4607
  task_type: task.taskName,
4515
4608
  status,
4516
4609
  payload: result ?? (error ? { error } : void 0),
@@ -4525,7 +4618,7 @@ class TaskExecutor {
4525
4618
  task.options = {};
4526
4619
  delete task.pendingInput;
4527
4620
  setTimeout(() => {
4528
- this.cleanupTerminalTaskInspectionState(taskId);
4621
+ if (this.activeTasks.get(taskId) === task) this.cleanupTerminalTaskInspectionState(taskId);
4529
4622
  }, 3e4).unref();
4530
4623
  }
4531
4624
  }
@@ -6,6 +6,8 @@ type DeadlineTaskOptions<TOptions extends TaskOptions = TaskOptions> = TOptions
6
6
  [TASK_OPERATION_ID]: string;
7
7
  [TASK_DEADLINE_ERROR]?: TaskTimeoutError;
8
8
  };
9
+ /** Give a child dispatch its own correlation id while retaining the parent deadline and cancellation. */
10
+ export declare function childTaskOptions(options?: TaskOptions): TaskOptions;
9
11
  export declare function getTaskOperationId(options: TaskOptions | undefined): string | undefined;
10
12
  export declare function attachTaskDeadlineIdempotencyKey(options: TaskOptions, idempotencyKey: string): void;
11
13
  export declare function attachTaskDeadlineGovernanceRecovery(options: TaskOptions, recovery: GovernanceOutcomeRecovery): void;
@@ -6,6 +6,8 @@ type DeadlineTaskOptions<TOptions extends TaskOptions = TaskOptions> = TOptions
6
6
  [TASK_OPERATION_ID]: string;
7
7
  [TASK_DEADLINE_ERROR]?: TaskTimeoutError;
8
8
  };
9
+ /** Give a child dispatch its own correlation id while retaining the parent deadline and cancellation. */
10
+ export declare function childTaskOptions(options?: TaskOptions): TaskOptions;
9
11
  export declare function getTaskOperationId(options: TaskOptions | undefined): string | undefined;
10
12
  export declare function attachTaskDeadlineIdempotencyKey(options: TaskOptions, idempotencyKey: string): void;
11
13
  export declare function attachTaskDeadlineGovernanceRecovery(options: TaskOptions, recovery: GovernanceOutcomeRecovery): void;
@@ -20,6 +20,7 @@ var task_deadline_exports = {};
20
20
  __export(task_deadline_exports, {
21
21
  attachTaskDeadlineGovernanceRecovery: () => attachTaskDeadlineGovernanceRecovery,
22
22
  attachTaskDeadlineIdempotencyKey: () => attachTaskDeadlineIdempotencyKey,
23
+ childTaskOptions: () => childTaskOptions,
23
24
  getTaskOperationId: () => getTaskOperationId,
24
25
  withTaskDeadline: () => withTaskDeadline
25
26
  });
@@ -29,8 +30,24 @@ var import_errors = require('../errors/index.js');
29
30
  var import_abort = require('../protocols/abort.js');
30
31
  const TASK_OPERATION_ID = /* @__PURE__ */ Symbol("adcp.taskOperationId");
31
32
  const TASK_DEADLINE_ERROR = /* @__PURE__ */ Symbol("adcp.taskDeadlineError");
33
+ function childTaskOptions(options) {
34
+ const child = { ...options };
35
+ delete child.operationId;
36
+ delete child[TASK_OPERATION_ID];
37
+ return child;
38
+ }
32
39
  function getTaskOperationId(options) {
33
- return options?.[TASK_OPERATION_ID];
40
+ const operationId = options?.operationId;
41
+ if (operationId !== void 0 && (typeof operationId !== "string" || !/^(?!\.+$)[A-Za-z0-9_.:-]{1,255}$/.test(operationId) || operationId.startsWith("delivery_route:"))) {
42
+ throw new TypeError(
43
+ "operationId must contain 1\u2013255 letters, digits, underscores, periods, colons, or hyphens; dot-only IDs and the delivery_route: prefix are reserved."
44
+ );
45
+ }
46
+ const internalId = options?.[TASK_OPERATION_ID];
47
+ if (internalId !== void 0 && operationId !== void 0 && internalId !== operationId) {
48
+ throw new TypeError("operationId does not match the operation already assigned to this call.");
49
+ }
50
+ return internalId ?? operationId;
34
51
  }
35
52
  function attachTaskDeadlineIdempotencyKey(options, idempotencyKey) {
36
53
  const error = options[TASK_DEADLINE_ERROR];
@@ -44,7 +61,7 @@ function attachTaskDeadlineGovernanceRecovery(options, recovery) {
44
61
  }
45
62
  async function withTaskDeadline(options, run) {
46
63
  const existing = options;
47
- const taskId = existing?.[TASK_OPERATION_ID] ?? (0, import_node_crypto.randomUUID)();
64
+ const taskId = getTaskOperationId(existing) ?? (0, import_node_crypto.randomUUID)();
48
65
  const timeout = options?.timeout;
49
66
  const baseOptions = { ...options, timeout: void 0, [TASK_OPERATION_ID]: taskId };
50
67
  (0, import_abort.throwIfAborted)(options?.signal);
@@ -68,6 +85,7 @@ async function withTaskDeadline(options, run) {
68
85
  0 && (module.exports = {
69
86
  attachTaskDeadlineGovernanceRecovery,
70
87
  attachTaskDeadlineIdempotencyKey,
88
+ childTaskOptions,
71
89
  getTaskOperationId,
72
90
  withTaskDeadline
73
91
  });
@@ -3,8 +3,24 @@ import { TaskTimeoutError } from "../errors/index.mjs";
3
3
  import { MAX_TIMER_DELAY_MS, throwIfAborted, withAbortSignal } from "../protocols/abort.mjs";
4
4
  const TASK_OPERATION_ID = /* @__PURE__ */ Symbol("adcp.taskOperationId");
5
5
  const TASK_DEADLINE_ERROR = /* @__PURE__ */ Symbol("adcp.taskDeadlineError");
6
+ function childTaskOptions(options) {
7
+ const child = { ...options };
8
+ delete child.operationId;
9
+ delete child[TASK_OPERATION_ID];
10
+ return child;
11
+ }
6
12
  function getTaskOperationId(options) {
7
- return options?.[TASK_OPERATION_ID];
13
+ const operationId = options?.operationId;
14
+ if (operationId !== void 0 && (typeof operationId !== "string" || !/^(?!\.+$)[A-Za-z0-9_.:-]{1,255}$/.test(operationId) || operationId.startsWith("delivery_route:"))) {
15
+ throw new TypeError(
16
+ "operationId must contain 1\u2013255 letters, digits, underscores, periods, colons, or hyphens; dot-only IDs and the delivery_route: prefix are reserved."
17
+ );
18
+ }
19
+ const internalId = options?.[TASK_OPERATION_ID];
20
+ if (internalId !== void 0 && operationId !== void 0 && internalId !== operationId) {
21
+ throw new TypeError("operationId does not match the operation already assigned to this call.");
22
+ }
23
+ return internalId ?? operationId;
8
24
  }
9
25
  function attachTaskDeadlineIdempotencyKey(options, idempotencyKey) {
10
26
  const error = options[TASK_DEADLINE_ERROR];
@@ -18,7 +34,7 @@ function attachTaskDeadlineGovernanceRecovery(options, recovery) {
18
34
  }
19
35
  async function withTaskDeadline(options, run) {
20
36
  const existing = options;
21
- const taskId = existing?.[TASK_OPERATION_ID] ?? randomUUID();
37
+ const taskId = getTaskOperationId(existing) ?? randomUUID();
22
38
  const timeout = options?.timeout;
23
39
  const baseOptions = { ...options, timeout: void 0, [TASK_OPERATION_ID]: taskId };
24
40
  throwIfAborted(options?.signal);
@@ -41,6 +57,7 @@ async function withTaskDeadline(options, run) {
41
57
  export {
42
58
  attachTaskDeadlineGovernanceRecovery,
43
59
  attachTaskDeadlineIdempotencyKey,
60
+ childTaskOptions,
44
61
  getTaskOperationId,
45
62
  withTaskDeadline
46
63
  };
@@ -242,7 +242,7 @@ var schemas_draft_default = [
242
242
  properties: {
243
243
  grantee: {
244
244
  $ref: "#/definitions/domain",
245
- description: "Domain of the organization receiving the grant. Its agents resolve to this domain through its own trust.json."
245
+ description: "Registrable domain (eTLD+1) of the organization receiving the grant, under the pinned Public Suffix List snapshot, ICANN and private sections (spec R3a step 1). Its agents resolve to this domain through its own trust.json. A host below a registrable domain (t1.vendor.example) is not one, and neither is a public suffix itself (github.io); alice.github.io is one, because github.io is in the private section. A grant whose grantee is not a registrable domain covers no claim, and its scopes still count for exclusive_grants, as L2 \xA7Grants in trust records describes (docs/building/by-layer/L2/accounts-and-agents.mdx). A pattern can't evaluate the Public Suffix List, so verifiers apply the rule. To limit a grant to some of the grantee's agents, list them in agents[]."
246
246
  },
247
247
  agents: {
248
248
  type: "array",
@@ -219,7 +219,7 @@ var schemas_draft_default = [
219
219
  properties: {
220
220
  grantee: {
221
221
  $ref: "#/definitions/domain",
222
- description: "Domain of the organization receiving the grant. Its agents resolve to this domain through its own trust.json."
222
+ description: "Registrable domain (eTLD+1) of the organization receiving the grant, under the pinned Public Suffix List snapshot, ICANN and private sections (spec R3a step 1). Its agents resolve to this domain through its own trust.json. A host below a registrable domain (t1.vendor.example) is not one, and neither is a public suffix itself (github.io); alice.github.io is one, because github.io is in the private section. A grant whose grantee is not a registrable domain covers no claim, and its scopes still count for exclusive_grants, as L2 \xA7Grants in trust records describes (docs/building/by-layer/L2/accounts-and-agents.mdx). A pattern can't evaluate the Public Suffix List, so verifiers apply the rule. To limit a grant to some of the grantee's agents, list them in agents[]."
223
223
  },
224
224
  agents: {
225
225
  type: "array",
@@ -34,6 +34,14 @@ export declare class TrustMembershipError extends IdentityAuthorizationError {
34
34
  }
35
35
  /** Schema plus semantic checks; publication never includes identity keys. */
36
36
  export declare function validateTrustRecord(value: unknown): asserts value is TrustRecord;
37
+ /**
38
+ * Publication rules beyond the schema: every grant's `grantee` is a
39
+ * registrable domain and every `agents[].url` parses as an agent origin. A
40
+ * relying party ignores a grant that breaks either, so publishing one grants
41
+ * nothing while still making `grants[]` exhaustive for its scopes.
42
+ */
43
+ export declare function assertPublishableTrustRecord(record: TrustRecord): void;
44
+ /** Validate ({@link assertPublishableTrustRecord}) and copy a trust record for publication. */
37
45
  export declare function createTrustRecord(record: TrustRecord): TrustRecord;
38
46
  export type TrustFetchResult = {
39
47
  status: 'present';
@@ -34,6 +34,14 @@ export declare class TrustMembershipError extends IdentityAuthorizationError {
34
34
  }
35
35
  /** Schema plus semantic checks; publication never includes identity keys. */
36
36
  export declare function validateTrustRecord(value: unknown): asserts value is TrustRecord;
37
+ /**
38
+ * Publication rules beyond the schema: every grant's `grantee` is a
39
+ * registrable domain and every `agents[].url` parses as an agent origin. A
40
+ * relying party ignores a grant that breaks either, so publishing one grants
41
+ * nothing while still making `grants[]` exhaustive for its scopes.
42
+ */
43
+ export declare function assertPublishableTrustRecord(record: TrustRecord): void;
44
+ /** Validate ({@link assertPublishableTrustRecord}) and copy a trust record for publication. */
37
45
  export declare function createTrustRecord(record: TrustRecord): TrustRecord;
38
46
  export type TrustFetchResult = {
39
47
  status: 'present';
@@ -32,6 +32,7 @@ __export(trust_exports, {
32
32
  TrustEvaluator: () => TrustEvaluator,
33
33
  TrustMembershipError: () => TrustMembershipError,
34
34
  TrustRecordUnavailableError: () => TrustRecordUnavailableError,
35
+ assertPublishableTrustRecord: () => assertPublishableTrustRecord,
35
36
  authorizeDemandGrant: () => authorizeDemandGrant,
36
37
  createTrustRecord: () => createTrustRecord,
37
38
  validateIdentity33Schema: () => validateIdentity33Schema,
@@ -110,8 +111,34 @@ function validateTrustRecord(value) {
110
111
  origins.add(origin);
111
112
  }
112
113
  }
113
- function createTrustRecord(record) {
114
+ function registrableGrantee(grantee) {
115
+ try {
116
+ return (0, import_etld.eTldPlusOne)(grantee) === grantee ? grantee : void 0;
117
+ } catch {
118
+ return void 0;
119
+ }
120
+ }
121
+ function grantAgentOrigin(url) {
122
+ try {
123
+ return (0, import_canonicalize.canonicalAgentOrigin)(url);
124
+ } catch {
125
+ return void 0;
126
+ }
127
+ }
128
+ function assertPublishableTrustRecord(record) {
114
129
  validateTrustRecord(record);
130
+ for (const grant of record.grants ?? []) {
131
+ if (registrableGrantee(grant.grantee) === void 0)
132
+ throw new TypeError(
133
+ `Grant grantee "${grant.grantee}" is not a registrable domain; name the organization's registrable domain and narrow with agents[]`
134
+ );
135
+ for (const agent of grant.agents ?? [])
136
+ if (grantAgentOrigin(agent.url) === void 0)
137
+ throw new TypeError(`Grant agents[] URL "${agent.url}" is not a valid agent origin`);
138
+ }
139
+ }
140
+ function createTrustRecord(record) {
141
+ assertPublishableTrustRecord(record);
115
142
  return structuredClone(record);
116
143
  }
117
144
  class TrustEvaluator {
@@ -308,13 +335,15 @@ function authorityGroup(operator) {
308
335
  const provider = (0, import_tldts.parse)(operator).domain;
309
336
  return `private:${provider ?? operator}`;
310
337
  }
338
+ function grantsScope(grant, scope) {
339
+ return grant.scopes.some(
340
+ (granted) => granted === "*" || granted === scope || granted === "adcp:all" && scope.startsWith("adcp:")
341
+ );
342
+ }
311
343
  function grantMatches(grant, target, claim, now) {
312
- if ((0, import_etld.eTldPlusOne)(grant.grantee) !== target) return false;
313
- if (!grant.scopes.some(
314
- (scope) => scope === "*" || scope === claim.scope || scope === "adcp:all" && claim.scope.startsWith("adcp:")
315
- ))
316
- return false;
317
- if (grant.agents && !grant.agents.some((agent) => (0, import_canonicalize.canonicalAgentOrigin)(agent.url) === (0, import_canonicalize.canonicalAgentOrigin)(claim.signerOrigin)))
344
+ if (registrableGrantee(grant.grantee) !== target) return false;
345
+ if (!grantsScope(grant, claim.scope)) return false;
346
+ if (grant.agents && !grant.agents.some((agent) => grantAgentOrigin(agent.url) === (0, import_canonicalize.canonicalAgentOrigin)(claim.signerOrigin)))
318
347
  return false;
319
348
  const brands = grant.profiles?.adcp?.brands;
320
349
  if (brands && !brands.includes("*") && (!claim.brand.brand_id || !brands.includes(claim.brand.brand_id)))
@@ -361,11 +390,10 @@ async function authorizeDemandGrant(claim, options) {
361
390
  if (second) return result("trust", [brand, account, operator], [first[0], second]);
362
391
  }
363
392
  }
364
- const covered = (record.record.grants ?? []).some(
365
- (g) => ((0, import_etld.eTldPlusOne)(g.grantee) === account || selfOperated && (0, import_etld.eTldPlusOne)(g.grantee) === operator) && g.scopes.some(
366
- (scope) => scope === "*" || scope === claim.scope || scope === "adcp:all" && claim.scope.startsWith("adcp:")
367
- )
368
- );
393
+ const covered = (record.record.grants ?? []).some((g) => {
394
+ const grantee = registrableGrantee(g.grantee);
395
+ return grantee !== void 0 && (grantee === account || selfOperated && grantee === operator) && grantsScope(g, claim.scope);
396
+ });
369
397
  if (!covered && !record.record.exclusive_grants && await options.sellerAuthorization?.(claim))
370
398
  return result("seller", [brand, account, operator]);
371
399
  throw new IdentityAuthorizationError("grant_absent");
@@ -376,6 +404,7 @@ async function authorizeDemandGrant(claim, options) {
376
404
  TrustEvaluator,
377
405
  TrustMembershipError,
378
406
  TrustRecordUnavailableError,
407
+ assertPublishableTrustRecord,
379
408
  authorizeDemandGrant,
380
409
  createTrustRecord,
381
410
  validateIdentity33Schema,