@adcp/sdk 14.0.0-beta.12 → 14.0.0-beta.13

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 (175) hide show
  1. package/dist/lib/index.d.mts +3 -3
  2. package/dist/lib/index.d.ts +3 -3
  3. package/dist/lib/index.d.ts.map +1 -1
  4. package/dist/lib/index.js +2 -0
  5. package/dist/lib/index.js.map +1 -1
  6. package/dist/lib/index.mjs +3 -1
  7. package/dist/lib/index.mjs.map +1 -1
  8. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  9. package/dist/lib/server/create-adcp-server.d.mts +2 -2
  10. package/dist/lib/server/create-adcp-server.d.ts +2 -2
  11. package/dist/lib/server/create-adcp-server.d.ts.map +1 -1
  12. package/dist/lib/server/create-adcp-server.js +6 -1
  13. package/dist/lib/server/create-adcp-server.js.map +1 -1
  14. package/dist/lib/server/create-adcp-server.mjs +6 -1
  15. package/dist/lib/server/create-adcp-server.mjs.map +1 -1
  16. package/dist/lib/server/decisioning/index.d.mts +3 -3
  17. package/dist/lib/server/decisioning/index.d.ts +3 -3
  18. package/dist/lib/server/decisioning/index.d.ts.map +1 -1
  19. package/dist/lib/server/decisioning/index.js.map +1 -1
  20. package/dist/lib/server/decisioning/index.mjs.map +1 -1
  21. package/dist/lib/server/decisioning/runtime/from-platform.d.mts +24 -17
  22. package/dist/lib/server/decisioning/runtime/from-platform.d.ts +24 -17
  23. package/dist/lib/server/decisioning/runtime/from-platform.d.ts.map +1 -1
  24. package/dist/lib/server/decisioning/runtime/from-platform.js +71 -42
  25. package/dist/lib/server/decisioning/runtime/from-platform.js.map +1 -1
  26. package/dist/lib/server/decisioning/runtime/from-platform.mjs +75 -43
  27. package/dist/lib/server/decisioning/runtime/from-platform.mjs.map +1 -1
  28. package/dist/lib/server/decisioning/runtime/postgres-task-registry.d.mts +15 -21
  29. package/dist/lib/server/decisioning/runtime/postgres-task-registry.d.ts +15 -21
  30. package/dist/lib/server/decisioning/runtime/postgres-task-registry.d.ts.map +1 -1
  31. package/dist/lib/server/decisioning/runtime/postgres-task-registry.js +100 -30
  32. package/dist/lib/server/decisioning/runtime/postgres-task-registry.js.map +1 -1
  33. package/dist/lib/server/decisioning/runtime/postgres-task-registry.mjs +100 -30
  34. package/dist/lib/server/decisioning/runtime/postgres-task-registry.mjs.map +1 -1
  35. package/dist/lib/server/decisioning/runtime/task-registry.d.mts +30 -16
  36. package/dist/lib/server/decisioning/runtime/task-registry.d.ts +30 -16
  37. package/dist/lib/server/decisioning/runtime/task-registry.d.ts.map +1 -1
  38. package/dist/lib/server/decisioning/runtime/task-registry.js +40 -20
  39. package/dist/lib/server/decisioning/runtime/task-registry.js.map +1 -1
  40. package/dist/lib/server/decisioning/runtime/task-registry.mjs +38 -19
  41. package/dist/lib/server/decisioning/runtime/task-registry.mjs.map +1 -1
  42. package/dist/lib/server/decisioning/runtime/to-context.d.mts +2 -2
  43. package/dist/lib/server/decisioning/runtime/to-context.d.ts +2 -2
  44. package/dist/lib/server/decisioning/runtime/to-context.d.ts.map +1 -1
  45. package/dist/lib/server/decisioning/runtime/to-context.js +2 -2
  46. package/dist/lib/server/decisioning/runtime/to-context.js.map +1 -1
  47. package/dist/lib/server/decisioning/runtime/to-context.mjs +2 -2
  48. package/dist/lib/server/decisioning/runtime/to-context.mjs.map +1 -1
  49. package/dist/lib/server/decisioning/specialisms/creative-ad-server.d.mts +3 -2
  50. package/dist/lib/server/decisioning/specialisms/creative-ad-server.d.ts +3 -2
  51. package/dist/lib/server/decisioning/specialisms/creative-ad-server.d.ts.map +1 -1
  52. package/dist/lib/server/decisioning/specialisms/creative-ad-server.js.map +1 -1
  53. package/dist/lib/server/decisioning/specialisms/creative.d.mts +10 -10
  54. package/dist/lib/server/decisioning/specialisms/creative.d.ts +10 -10
  55. package/dist/lib/server/decisioning/specialisms/creative.d.ts.map +1 -1
  56. package/dist/lib/server/decisioning/specialisms/creative.js.map +1 -1
  57. package/dist/lib/server/index.d.mts +1 -1
  58. package/dist/lib/server/index.d.ts +1 -1
  59. package/dist/lib/server/index.d.ts.map +1 -1
  60. package/dist/lib/server/index.js.map +1 -1
  61. package/dist/lib/server/index.mjs.map +1 -1
  62. package/dist/lib/testing/storyboard/default-invariants.d.ts.map +1 -1
  63. package/dist/lib/testing/storyboard/default-invariants.js +11 -2
  64. package/dist/lib/testing/storyboard/default-invariants.js.map +1 -1
  65. package/dist/lib/testing/storyboard/default-invariants.mjs +11 -2
  66. package/dist/lib/testing/storyboard/default-invariants.mjs.map +1 -1
  67. package/dist/lib/testing/storyboard/index.d.mts +1 -1
  68. package/dist/lib/testing/storyboard/index.d.ts +1 -1
  69. package/dist/lib/testing/storyboard/index.d.ts.map +1 -1
  70. package/dist/lib/testing/storyboard/index.js.map +1 -1
  71. package/dist/lib/testing/storyboard/index.mjs.map +1 -1
  72. package/dist/lib/testing/storyboard/loader.d.ts.map +1 -1
  73. package/dist/lib/testing/storyboard/loader.js +14 -0
  74. package/dist/lib/testing/storyboard/loader.js.map +1 -1
  75. package/dist/lib/testing/storyboard/loader.mjs +14 -0
  76. package/dist/lib/testing/storyboard/loader.mjs.map +1 -1
  77. package/dist/lib/testing/storyboard/phase-condition.d.mts +41 -0
  78. package/dist/lib/testing/storyboard/phase-condition.d.ts +42 -0
  79. package/dist/lib/testing/storyboard/phase-condition.d.ts.map +1 -0
  80. package/dist/lib/testing/storyboard/phase-condition.js +215 -0
  81. package/dist/lib/testing/storyboard/phase-condition.js.map +1 -0
  82. package/dist/lib/testing/storyboard/phase-condition.mjs +189 -0
  83. package/dist/lib/testing/storyboard/phase-condition.mjs.map +1 -0
  84. package/dist/lib/testing/storyboard/request-builder.d.ts.map +1 -1
  85. package/dist/lib/testing/storyboard/request-builder.js +15 -2
  86. package/dist/lib/testing/storyboard/request-builder.js.map +1 -1
  87. package/dist/lib/testing/storyboard/request-builder.mjs +15 -2
  88. package/dist/lib/testing/storyboard/request-builder.mjs.map +1 -1
  89. package/dist/lib/testing/storyboard/runner.d.ts.map +1 -1
  90. package/dist/lib/testing/storyboard/runner.js +57 -23
  91. package/dist/lib/testing/storyboard/runner.js.map +1 -1
  92. package/dist/lib/testing/storyboard/runner.mjs +57 -23
  93. package/dist/lib/testing/storyboard/runner.mjs.map +1 -1
  94. package/dist/lib/testing/storyboard/task-map.d.ts.map +1 -1
  95. package/dist/lib/testing/storyboard/task-map.js +6 -2
  96. package/dist/lib/testing/storyboard/task-map.js.map +1 -1
  97. package/dist/lib/testing/storyboard/task-map.mjs +6 -2
  98. package/dist/lib/testing/storyboard/task-map.mjs.map +1 -1
  99. package/dist/lib/testing/storyboard/types.d.mts +5 -4
  100. package/dist/lib/testing/storyboard/types.d.ts +5 -4
  101. package/dist/lib/testing/storyboard/types.d.ts.map +1 -1
  102. package/dist/lib/testing/storyboard/types.js.map +1 -1
  103. package/dist/lib/testing/storyboard/types.mjs.map +1 -1
  104. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  105. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  106. package/dist/lib/testing/storyboard/webhook-assertions.d.ts.map +1 -1
  107. package/dist/lib/testing/storyboard/webhook-assertions.js +60 -4
  108. package/dist/lib/testing/storyboard/webhook-assertions.js.map +1 -1
  109. package/dist/lib/testing/storyboard/webhook-assertions.mjs +60 -4
  110. package/dist/lib/testing/storyboard/webhook-assertions.mjs.map +1 -1
  111. package/dist/lib/testing/storyboard/webhook-receiver.d.mts +15 -0
  112. package/dist/lib/testing/storyboard/webhook-receiver.d.ts +15 -0
  113. package/dist/lib/testing/storyboard/webhook-receiver.d.ts.map +1 -1
  114. package/dist/lib/testing/storyboard/webhook-receiver.js +57 -6
  115. package/dist/lib/testing/storyboard/webhook-receiver.js.map +1 -1
  116. package/dist/lib/testing/storyboard/webhook-receiver.mjs +57 -6
  117. package/dist/lib/testing/storyboard/webhook-receiver.mjs.map +1 -1
  118. package/dist/lib/testing/types.d.mts +2 -0
  119. package/dist/lib/testing/types.d.ts +2 -0
  120. package/dist/lib/testing/types.d.ts.map +1 -1
  121. package/dist/lib/testing/types.js.map +1 -1
  122. package/dist/lib/types/core.generated.d.mts +5 -2
  123. package/dist/lib/types/core.generated.d.ts +5 -2
  124. package/dist/lib/types/core.generated.d.ts.map +1 -1
  125. package/dist/lib/types/core.generated.js.map +1 -1
  126. package/dist/lib/types/index.d.mts +1 -1
  127. package/dist/lib/types/index.d.ts +1 -1
  128. package/dist/lib/types/index.d.ts.map +1 -1
  129. package/dist/lib/types/index.js.map +1 -1
  130. package/dist/lib/types/index.mjs.map +1 -1
  131. package/dist/lib/types/list-transformers.d.ts +9 -5
  132. package/dist/lib/types/schemas.generated.d.ts +21 -298
  133. package/dist/lib/types/schemas.generated.d.ts.map +1 -1
  134. package/dist/lib/types/schemas.generated.js +5 -2
  135. package/dist/lib/types/schemas.generated.js.map +1 -1
  136. package/dist/lib/types/schemas.generated.mjs +4 -2
  137. package/dist/lib/types/schemas.generated.mjs.map +1 -1
  138. package/dist/lib/types/server-payload-aliases.d.mts +2 -1
  139. package/dist/lib/types/server-payload-aliases.d.ts +2 -1
  140. package/dist/lib/types/server-payload-aliases.d.ts.map +1 -1
  141. package/dist/lib/types/server-payload-aliases.js.map +1 -1
  142. package/dist/lib/types/tools.generated.d.mts +5 -2
  143. package/dist/lib/types/tools.generated.d.ts +5 -2
  144. package/dist/lib/types/tools.generated.d.ts.map +1 -1
  145. package/dist/lib/types/tools.generated.js.map +1 -1
  146. package/dist/lib/utils/build-creative-return-builders.d.mts +5 -1
  147. package/dist/lib/utils/build-creative-return-builders.d.ts +5 -1
  148. package/dist/lib/utils/build-creative-return-builders.d.ts.map +1 -1
  149. package/dist/lib/utils/build-creative-return-builders.js +9 -3
  150. package/dist/lib/utils/build-creative-return-builders.js.map +1 -1
  151. package/dist/lib/utils/build-creative-return-builders.mjs +7 -2
  152. package/dist/lib/utils/build-creative-return-builders.mjs.map +1 -1
  153. package/dist/lib/utils/response-unwrapper.js +1 -1
  154. package/dist/lib/utils/response-unwrapper.js.map +1 -1
  155. package/dist/lib/utils/response-unwrapper.mjs +1 -1
  156. package/dist/lib/utils/response-unwrapper.mjs.map +1 -1
  157. package/dist/lib/validation/schema-loader.d.ts.map +1 -1
  158. package/dist/lib/validation/schema-loader.js +16 -7
  159. package/dist/lib/validation/schema-loader.js.map +1 -1
  160. package/dist/lib/validation/schema-loader.mjs +16 -7
  161. package/dist/lib/validation/schema-loader.mjs.map +1 -1
  162. package/dist/lib/version.d.mts +3 -3
  163. package/dist/lib/version.d.ts +3 -3
  164. package/dist/lib/version.js +3 -3
  165. package/dist/lib/version.js.map +1 -1
  166. package/dist/lib/version.mjs +3 -3
  167. package/dist/lib/version.mjs.map +1 -1
  168. package/docs/llms.txt +2 -2
  169. package/docs/migration-5.x-to-6.x.md +3 -2
  170. package/docs/migration-task-registry-scoping.md +61 -0
  171. package/examples/hello_seller_adapter_guaranteed.ts +1 -1
  172. package/package.json +4 -4
  173. package/skills/build-decisioning-creative-template/SKILL.md +2 -2
  174. package/skills/build-decisioning-platform/SKILL.md +4 -2
  175. package/skills/build-decisioning-platform/advanced/REFERENCE.md +7 -6
@@ -18,7 +18,8 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var task_registry_exports = {};
20
20
  __export(task_registry_exports, {
21
- createInMemoryTaskRegistry: () => createInMemoryTaskRegistry
21
+ createInMemoryTaskRegistry: () => createInMemoryTaskRegistry,
22
+ taskRecordMatchesScope: () => taskRecordMatchesScope
22
23
  });
23
24
  module.exports = __toCommonJS(task_registry_exports);
24
25
  var import_node_crypto = require("node:crypto");
@@ -26,17 +27,27 @@ const TERMINAL_TASK_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "
26
27
  function isTerminalTaskStatus(status) {
27
28
  return TERMINAL_TASK_STATUSES.has(status);
28
29
  }
30
+ function taskRecordMatchesScope(record, scope) {
31
+ if (record.accountId !== scope.accountId) return false;
32
+ return record.ownerScope === scope.ownerScope || record.ownerScope === void 0 && scope.ownerScope === `account:${scope.accountId}`;
33
+ }
34
+ function taskStorageKey(taskId, scope) {
35
+ return JSON.stringify([scope.accountId, scope.ownerScope, taskId]);
36
+ }
29
37
  function createInMemoryTaskRegistry() {
30
38
  const tasks = /* @__PURE__ */ new Map();
31
39
  const backgrounds = /* @__PURE__ */ new Map();
32
40
  return {
41
+ scopeVersion: 1,
33
42
  async create(opts) {
34
43
  const taskId = opts.overrideTaskId ?? `task_${(0, import_node_crypto.randomUUID)()}`;
35
- if (tasks.has(taskId)) {
44
+ const scope = { accountId: opts.accountId, ownerScope: opts.ownerScope ?? `account:${opts.accountId}` };
45
+ const storageKey = taskStorageKey(taskId, scope);
46
+ if (tasks.has(storageKey)) {
36
47
  throw new Error(`task_id already registered: ${taskId}`);
37
48
  }
38
49
  const now = (/* @__PURE__ */ new Date()).toISOString();
39
- tasks.set(taskId, {
50
+ tasks.set(storageKey, {
40
51
  taskId,
41
52
  tool: opts.tool,
42
53
  accountId: opts.accountId,
@@ -48,28 +59,31 @@ function createInMemoryTaskRegistry() {
48
59
  });
49
60
  return { taskId };
50
61
  },
51
- async getTask(taskId) {
52
- const record = tasks.get(taskId);
62
+ async getTask(taskId, scope) {
63
+ const record = tasks.get(taskStorageKey(taskId, scope));
53
64
  return record ?? null;
54
65
  },
66
+ async _getTaskUnsafe(taskId) {
67
+ const matches = Array.from(tasks.values()).filter((record) => record.taskId === taskId);
68
+ return matches.length === 1 ? matches[0] : null;
69
+ },
55
70
  async list(opts) {
56
- if (opts.ownerScope === void 0) return { tasks: [] };
57
71
  return {
58
72
  tasks: Array.from(tasks.values()).filter(
59
- (record) => record.accountId === opts.accountId && (record.ownerScope === opts.ownerScope || record.ownerScope === void 0 && opts.ownerScope === `account:${opts.accountId}`)
73
+ (record) => record.accountId === opts.accountId && record.ownerScope === opts.ownerScope
60
74
  )
61
75
  };
62
76
  },
63
- async complete(taskId, result) {
64
- const existing = tasks.get(taskId);
77
+ async complete(taskId, scope, result) {
78
+ const existing = tasks.get(taskStorageKey(taskId, scope));
65
79
  if (!existing) return;
66
80
  if (isTerminalTaskStatus(existing.status)) return;
67
81
  existing.status = "completed";
68
82
  existing.result = result;
69
83
  existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
70
84
  },
71
- async fail(taskId, error, result) {
72
- const existing = tasks.get(taskId);
85
+ async fail(taskId, scope, error, result) {
86
+ const existing = tasks.get(taskStorageKey(taskId, scope));
73
87
  if (!existing) return;
74
88
  if (isTerminalTaskStatus(existing.status)) return;
75
89
  existing.status = "failed";
@@ -78,29 +92,34 @@ function createInMemoryTaskRegistry() {
78
92
  existing.statusMessage = error.message;
79
93
  existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
80
94
  },
81
- async updateProgress(taskId, progress) {
82
- const existing = tasks.get(taskId);
95
+ async updateProgress(taskId, scope, progress) {
96
+ const existing = tasks.get(taskStorageKey(taskId, scope));
83
97
  if (!existing) return;
84
98
  if (isTerminalTaskStatus(existing.status)) return;
85
99
  if (existing.status === "submitted") existing.status = "working";
86
100
  existing.progress = progress;
87
101
  existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
88
102
  },
89
- _registerBackground(taskId, completion) {
103
+ _registerBackground(taskId, scope, completion) {
104
+ const storageKey = taskStorageKey(taskId, scope);
90
105
  const composed = completion.then(
91
106
  () => {
92
- if (backgrounds.get(taskId) === composed) backgrounds.delete(taskId);
107
+ if (backgrounds.get(storageKey) === composed) backgrounds.delete(storageKey);
93
108
  },
94
109
  () => {
95
- if (backgrounds.get(taskId) === composed) backgrounds.delete(taskId);
110
+ if (backgrounds.get(storageKey) === composed) backgrounds.delete(storageKey);
96
111
  }
97
112
  );
98
- backgrounds.set(taskId, composed);
113
+ backgrounds.set(storageKey, composed);
99
114
  },
100
- async awaitTask(taskId) {
101
- const pending = backgrounds.get(taskId);
115
+ async awaitTask(taskId, scope) {
116
+ const pending = backgrounds.get(taskStorageKey(taskId, scope));
102
117
  if (pending) await pending;
103
118
  },
119
+ async _awaitTaskUnsafe(taskId) {
120
+ const pending = Array.from(backgrounds.entries()).filter(([key]) => JSON.parse(key)[2] === taskId).map(([, completion]) => completion);
121
+ await Promise.all(pending);
122
+ },
104
123
  clear() {
105
124
  tasks.clear();
106
125
  backgrounds.clear();
@@ -109,6 +128,7 @@ function createInMemoryTaskRegistry() {
109
128
  }
110
129
  // Annotate the CommonJS export names for ESM import in node:
111
130
  0 && (module.exports = {
112
- createInMemoryTaskRegistry
131
+ createInMemoryTaskRegistry,
132
+ taskRecordMatchesScope
113
133
  });
114
134
  //# sourceMappingURL=task-registry.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/lib/server/decisioning/runtime/task-registry.ts"],"sourcesContent":["/**\n * In-memory task registry for the v6.0 alpha runtime.\n *\n * The framework owns task lifecycle. When an adopter method returns a\n * `TaskHandoff` marker, the framework:\n * 1. Allocates a `taskId` and writes a `submitted` record.\n * 2. Returns the submitted envelope to the buyer immediately.\n * 3. Runs the handoff function in the background.\n * 4. Updates the record on progress (`updateProgress`) and terminal\n * state (`complete` / `fail`) from the method's return/throw.\n *\n * Adopters never call into the registry directly. Wire-level `tasks/get`\n * integration (so buyers can poll the lifecycle) reads via `getTask`;\n * test harnesses use `awaitTask` to flush the background promise\n * deterministically.\n *\n * Status: Preview / 6.0.\n *\n * @public\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { AdcpStructuredError, TaskHandoffProgress } from '../async-outcome';\n\n/**\n * AdCP-spec task lifecycle states. Mirrors `enums/task-status.json` —\n * the v6 framework writes `'submitted'` on create, transitions to\n * `'working'` on the first `updateProgress()` call, and terminates at\n * `'completed'` / `'failed'`. The other states (`'input-required'`,\n * `'canceled'`, `'rejected'`, `'auth-required'`, `'unknown'`) are\n * reserved for adopter-emitted transitions via the forthcoming\n * `taskRegistry.transition()` API (v6.1).\n */\nexport type TaskStatus =\n | 'submitted'\n | 'working'\n | 'input-required'\n | 'completed'\n | 'canceled'\n | 'failed'\n | 'rejected'\n | 'auth-required'\n | 'unknown';\n\nconst TERMINAL_TASK_STATUSES: ReadonlySet<TaskStatus> = new Set(['completed', 'failed', 'rejected', 'canceled']);\n\nfunction isTerminalTaskStatus(status: TaskStatus): boolean {\n return TERMINAL_TASK_STATUSES.has(status);\n}\n\nexport interface TaskRecord<TResult = unknown, TError extends AdcpStructuredError = AdcpStructuredError> {\n taskId: string;\n /** Tool name that started the task (e.g., 'create_media_buy'). */\n tool: string;\n /** Account that started the task — sessionKey-like for cross-request scoping. */\n accountId: string;\n /** Tenant/principal owner scope used with accountId for list/read isolation. */\n ownerScope?: string;\n /** Current lifecycle state — full AdCP-spec `task-status` enum. */\n status: TaskStatus;\n /** Status message on the final arm (`error.message` on failed). */\n statusMessage?: string;\n /** Canonical terminal artifact on `completed`, `failed`, or `rejected`. */\n result?: TResult;\n /** Terminal error on `failed`. */\n error?: TError;\n /**\n * Intermediate progress from `TaskHandoffContext.update(...)` calls.\n * Written by the background handoff function; surfaced to buyers polling\n * `tasks_get` via the spec `progress` field.\n */\n progress?: TaskHandoffProgress;\n /**\n * Whether the buyer wired `push_notification_config.url` on the original\n * request. Surfaced to the buyer via `tasks_get`'s spec-defined\n * `has_webhook: boolean` field so they can decide between long-poll vs.\n * single-shot polling.\n */\n hasWebhook?: boolean;\n /** ISO 8601 timestamps. */\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface TaskRegistryListOptions {\n accountId: string;\n /**\n * Caller ownership scope derived from buyer agent, credential, session,\n * or the single-principal account fallback. Optional for source\n * compatibility with older custom registries, but buyer-visible protocol\n * task tools pass it whenever they can identify a caller and built-in\n * registries fail closed when it is absent.\n */\n ownerScope?: string;\n}\n\nexport interface TaskRegistryListResult<TResult = unknown> {\n tasks: TaskRecord<TResult>[];\n}\n\n// All read/write methods are async to accommodate storage-backed\n// implementations (`createPostgresTaskRegistry`). The in-memory impl\n// resolves immediately. The framework `await`s every call, so the\n// in-memory case pays one microtask per dispatch — negligible.\nexport interface TaskRegistry {\n /**\n * Allocate a new task record. Returns the `taskId` the framework hands\n * to `platform.xxxTask(taskId, ...)`. Initial status is `submitted`.\n *\n * `hasWebhook: true` when the buyer wired `push_notification_config.url`\n * — surfaced via `tasks_get`'s `has_webhook` field. Defaults to `false`.\n *\n * `overrideTaskId` — when set, the registry uses this exact string as the\n * task id instead of minting a fresh one. Throws if the id is already\n * registered (uniqueness is the caller's responsibility).\n */\n create(opts: {\n tool: string;\n accountId: string;\n ownerScope?: string;\n hasWebhook?: boolean;\n overrideTaskId?: string;\n }): Promise<{ taskId: string }>;\n\n /** Read a task by id. Returns `null` if unknown. */\n getTask<TResult = unknown>(taskId: string): Promise<TaskRecord<TResult> | null>;\n\n /**\n * List tasks owned by a resolved account. Buyer-facing AdCP task\n * reconciliation must use this scoped surface, never the transport-level\n * MCP TaskStore, because MCP task records do not carry AdCP ownership,\n * protocol, or tool metadata.\n *\n * Multi-tenant deployments that share an AdCP account across principals\n * must persist and filter by `ownerScope`. Rows without `ownerScope` are\n * treated as legacy account-scoped rows: built-in registries return them\n * only for the account-fallback owner scope (`account:${accountId}`), not\n * for credential-, agent-, or session-scoped callers.\n */\n list?(opts: TaskRegistryListOptions): Promise<TaskRegistryListResult>;\n\n /**\n * Mark a task `completed` with the method's return value. No-op if the\n * task is already terminal (idempotent).\n */\n complete<TResult>(taskId: string, result: TResult): Promise<void>;\n\n /**\n * Mark a task `failed` with the structured error and, when available, the\n * canonical terminal artifact. No-op if the task is already terminal.\n */\n fail(taskId: string, error: AdcpStructuredError, result?: unknown): Promise<void>;\n\n /**\n * Record intermediate progress from `TaskHandoffContext.update(...)`.\n * Transitions the task from `'submitted'` → `'working'` on the first\n * call. No-op on already-terminal tasks. The `progress` payload is\n * written to the record and surfaced to buyers polling `tasks_get`.\n */\n updateProgress(taskId: string, progress: TaskHandoffProgress): Promise<void>;\n\n /**\n * Register the background completion promise the framework spawned for\n * the `*Task` invocation. Tests await this for deterministic settlement;\n * production callers don't need it.\n *\n * @internal\n */\n _registerBackground(taskId: string, completion: Promise<void>): void;\n\n /**\n * Await any registered background completion for a task. Resolves\n * immediately if no background is registered or it has already settled.\n * Used by test harnesses + `tasks/get` integration.\n */\n awaitTask(taskId: string): Promise<void>;\n\n /**\n * Optional test-harness flush. In-memory registries expose this so\n * `AdcpServer.compliance.reset()` can clear hardcoded overrideTaskId\n * records between repeated storyboard runs. Persistent registries should\n * omit it unless they can safely flush their configured backend.\n */\n clear?(): void | Promise<void>;\n}\n\nexport function createInMemoryTaskRegistry(): TaskRegistry {\n const tasks = new Map<string, TaskRecord<unknown>>();\n const backgrounds = new Map<string, Promise<void>>();\n\n return {\n async create(opts: {\n tool: string;\n accountId: string;\n ownerScope?: string;\n hasWebhook?: boolean;\n overrideTaskId?: string;\n }): Promise<{ taskId: string }> {\n const taskId = opts.overrideTaskId ?? `task_${randomUUID()}`;\n if (tasks.has(taskId)) {\n throw new Error(`task_id already registered: ${taskId}`);\n }\n const now = new Date().toISOString();\n tasks.set(taskId, {\n taskId,\n tool: opts.tool,\n accountId: opts.accountId,\n ownerScope: opts.ownerScope ?? `account:${opts.accountId}`,\n status: 'submitted',\n ...(opts.hasWebhook && { hasWebhook: true }),\n createdAt: now,\n updatedAt: now,\n });\n return { taskId };\n },\n\n async getTask<TResult = unknown>(taskId: string): Promise<TaskRecord<TResult> | null> {\n const record = tasks.get(taskId);\n return (record as TaskRecord<TResult> | undefined) ?? null;\n },\n\n async list(opts: TaskRegistryListOptions): Promise<TaskRegistryListResult> {\n if (opts.ownerScope === undefined) return { tasks: [] };\n return {\n tasks: Array.from(tasks.values()).filter(\n record =>\n record.accountId === opts.accountId &&\n (record.ownerScope === opts.ownerScope ||\n (record.ownerScope === undefined && opts.ownerScope === `account:${opts.accountId}`))\n ),\n };\n },\n\n async complete<TResult>(taskId: string, result: TResult): Promise<void> {\n const existing = tasks.get(taskId);\n if (!existing) return;\n if (isTerminalTaskStatus(existing.status)) return;\n existing.status = 'completed';\n existing.result = result;\n existing.updatedAt = new Date().toISOString();\n },\n\n async fail(taskId: string, error: AdcpStructuredError, result?: unknown): Promise<void> {\n const existing = tasks.get(taskId);\n if (!existing) return;\n if (isTerminalTaskStatus(existing.status)) return;\n existing.status = 'failed';\n existing.error = error;\n if (result !== undefined) existing.result = result;\n existing.statusMessage = error.message;\n existing.updatedAt = new Date().toISOString();\n },\n\n async updateProgress(taskId: string, progress: TaskHandoffProgress): Promise<void> {\n const existing = tasks.get(taskId);\n if (!existing) return;\n if (isTerminalTaskStatus(existing.status)) return;\n if (existing.status === 'submitted') existing.status = 'working';\n existing.progress = progress;\n existing.updatedAt = new Date().toISOString();\n },\n\n _registerBackground(taskId: string, completion: Promise<void>): void {\n const composed: Promise<void> = completion.then(\n () => {\n if (backgrounds.get(taskId) === composed) backgrounds.delete(taskId);\n },\n () => {\n if (backgrounds.get(taskId) === composed) backgrounds.delete(taskId);\n }\n );\n backgrounds.set(taskId, composed);\n },\n\n async awaitTask(taskId: string): Promise<void> {\n const pending = backgrounds.get(taskId);\n if (pending) await pending;\n },\n\n clear(): void {\n tasks.clear();\n backgrounds.clear();\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBA,yBAA2B;AAuB3B,MAAM,yBAAkD,oBAAI,IAAI,CAAC,aAAa,UAAU,YAAY,UAAU,CAAC;AAE/G,SAAS,qBAAqB,QAA6B;AACzD,SAAO,uBAAuB,IAAI,MAAM;AAC1C;AA0IO,SAAS,6BAA2C;AACzD,QAAM,QAAQ,oBAAI,IAAiC;AACnD,QAAM,cAAc,oBAAI,IAA2B;AAEnD,SAAO;AAAA,IACL,MAAM,OAAO,MAMmB;AAC9B,YAAM,SAAS,KAAK,kBAAkB,YAAQ,+BAAW,CAAC;AAC1D,UAAI,MAAM,IAAI,MAAM,GAAG;AACrB,cAAM,IAAI,MAAM,+BAA+B,MAAM,EAAE;AAAA,MACzD;AACA,YAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,YAAM,IAAI,QAAQ;AAAA,QAChB;AAAA,QACA,MAAM,KAAK;AAAA,QACX,WAAW,KAAK;AAAA,QAChB,YAAY,KAAK,cAAc,WAAW,KAAK,SAAS;AAAA,QACxD,QAAQ;AAAA,QACR,GAAI,KAAK,cAAc,EAAE,YAAY,KAAK;AAAA,QAC1C,WAAW;AAAA,QACX,WAAW;AAAA,MACb,CAAC;AACD,aAAO,EAAE,OAAO;AAAA,IAClB;AAAA,IAEA,MAAM,QAA2B,QAAqD;AACpF,YAAM,SAAS,MAAM,IAAI,MAAM;AAC/B,aAAQ,UAA8C;AAAA,IACxD;AAAA,IAEA,MAAM,KAAK,MAAgE;AACzE,UAAI,KAAK,eAAe,OAAW,QAAO,EAAE,OAAO,CAAC,EAAE;AACtD,aAAO;AAAA,QACL,OAAO,MAAM,KAAK,MAAM,OAAO,CAAC,EAAE;AAAA,UAChC,YACE,OAAO,cAAc,KAAK,cACzB,OAAO,eAAe,KAAK,cACzB,OAAO,eAAe,UAAa,KAAK,eAAe,WAAW,KAAK,SAAS;AAAA,QACvF;AAAA,MACF;AAAA,IACF;AAAA,IAEA,MAAM,SAAkB,QAAgB,QAAgC;AACtE,YAAM,WAAW,MAAM,IAAI,MAAM;AACjC,UAAI,CAAC,SAAU;AACf,UAAI,qBAAqB,SAAS,MAAM,EAAG;AAC3C,eAAS,SAAS;AAClB,eAAS,SAAS;AAClB,eAAS,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IAC9C;AAAA,IAEA,MAAM,KAAK,QAAgB,OAA4B,QAAiC;AACtF,YAAM,WAAW,MAAM,IAAI,MAAM;AACjC,UAAI,CAAC,SAAU;AACf,UAAI,qBAAqB,SAAS,MAAM,EAAG;AAC3C,eAAS,SAAS;AAClB,eAAS,QAAQ;AACjB,UAAI,WAAW,OAAW,UAAS,SAAS;AAC5C,eAAS,gBAAgB,MAAM;AAC/B,eAAS,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IAC9C;AAAA,IAEA,MAAM,eAAe,QAAgB,UAA8C;AACjF,YAAM,WAAW,MAAM,IAAI,MAAM;AACjC,UAAI,CAAC,SAAU;AACf,UAAI,qBAAqB,SAAS,MAAM,EAAG;AAC3C,UAAI,SAAS,WAAW,YAAa,UAAS,SAAS;AACvD,eAAS,WAAW;AACpB,eAAS,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IAC9C;AAAA,IAEA,oBAAoB,QAAgB,YAAiC;AACnE,YAAM,WAA0B,WAAW;AAAA,QACzC,MAAM;AACJ,cAAI,YAAY,IAAI,MAAM,MAAM,SAAU,aAAY,OAAO,MAAM;AAAA,QACrE;AAAA,QACA,MAAM;AACJ,cAAI,YAAY,IAAI,MAAM,MAAM,SAAU,aAAY,OAAO,MAAM;AAAA,QACrE;AAAA,MACF;AACA,kBAAY,IAAI,QAAQ,QAAQ;AAAA,IAClC;AAAA,IAEA,MAAM,UAAU,QAA+B;AAC7C,YAAM,UAAU,YAAY,IAAI,MAAM;AACtC,UAAI,QAAS,OAAM;AAAA,IACrB;AAAA,IAEA,QAAc;AACZ,YAAM,MAAM;AACZ,kBAAY,MAAM;AAAA,IACpB;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../../../src/lib/server/decisioning/runtime/task-registry.ts"],"sourcesContent":["/**\n * In-memory task registry for the v6.0 alpha runtime.\n *\n * The framework owns task lifecycle. When an adopter method returns a\n * `TaskHandoff` marker, the framework:\n * 1. Allocates a `taskId` and writes a `submitted` record.\n * 2. Returns the submitted envelope to the buyer immediately.\n * 3. Runs the handoff function in the background.\n * 4. Updates the record on progress (`updateProgress`) and terminal\n * state (`complete` / `fail`) from the method's return/throw.\n *\n * Adopters never call into the registry directly. Wire-level `tasks/get`\n * integration (so buyers can poll the lifecycle) reads via scoped `getTask`;\n * test harnesses use `awaitTask` to flush the background promise\n * deterministically.\n *\n * Status: Preview / 6.0.\n *\n * @public\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { AdcpStructuredError, TaskHandoffProgress } from '../async-outcome';\n\n/**\n * AdCP-spec task lifecycle states. Mirrors `enums/task-status.json` —\n * the v6 framework writes `'submitted'` on create, transitions to\n * `'working'` on the first `updateProgress()` call, and terminates at\n * `'completed'` / `'failed'`. The other states (`'input-required'`,\n * `'canceled'`, `'rejected'`, `'auth-required'`, `'unknown'`) are\n * reserved for adopter-emitted transitions via the forthcoming\n * `taskRegistry.transition()` API (v6.1).\n */\nexport type TaskStatus =\n | 'submitted'\n | 'working'\n | 'input-required'\n | 'completed'\n | 'canceled'\n | 'failed'\n | 'rejected'\n | 'auth-required'\n | 'unknown';\n\nconst TERMINAL_TASK_STATUSES: ReadonlySet<TaskStatus> = new Set(['completed', 'failed', 'rejected', 'canceled']);\n\nfunction isTerminalTaskStatus(status: TaskStatus): boolean {\n return TERMINAL_TASK_STATUSES.has(status);\n}\n\nexport interface TaskRecord<TResult = unknown, TError extends AdcpStructuredError = AdcpStructuredError> {\n taskId: string;\n /** Tool name that started the task (e.g., 'create_media_buy'). */\n tool: string;\n /** Account that started the task — sessionKey-like for cross-request scoping. */\n accountId: string;\n /** Tenant/principal owner scope used with accountId for list/read isolation. */\n ownerScope?: string;\n /** Current lifecycle state — full AdCP-spec `task-status` enum. */\n status: TaskStatus;\n /** Status message on the final arm (`error.message` on failed). */\n statusMessage?: string;\n /** Canonical terminal artifact on `completed`, `failed`, or `rejected`. */\n result?: TResult;\n /** Terminal error on `failed`. */\n error?: TError;\n /**\n * Intermediate progress from `TaskHandoffContext.update(...)` calls.\n * Written by the background handoff function; surfaced to buyers polling\n * `tasks_get` via the spec `progress` field.\n */\n progress?: TaskHandoffProgress;\n /**\n * Whether the buyer wired `push_notification_config.url` on the original\n * request. Surfaced to the buyer via `tasks_get`'s spec-defined\n * `has_webhook: boolean` field so they can decide between long-poll vs.\n * single-shot polling.\n */\n hasWebhook?: boolean;\n /** ISO 8601 timestamps. */\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface TaskRegistryListOptions {\n accountId: string;\n /** Caller ownership scope derived from authenticated server context. */\n ownerScope: string;\n}\n\n/** Account/principal boundary required for buyer-visible task access. */\nexport interface TaskRegistryScope {\n accountId: string;\n ownerScope: string;\n}\n\n/** Defense-in-depth boundary check for custom registry results. */\nexport function taskRecordMatchesScope(record: TaskRecord, scope: TaskRegistryScope): boolean {\n if (record.accountId !== scope.accountId) return false;\n return (\n record.ownerScope === scope.ownerScope ||\n (record.ownerScope === undefined && scope.ownerScope === `account:${scope.accountId}`)\n );\n}\n\nfunction taskStorageKey(taskId: string, scope: TaskRegistryScope): string {\n return JSON.stringify([scope.accountId, scope.ownerScope, taskId]);\n}\n\nexport interface TaskRegistryListResult<TResult = unknown> {\n tasks: TaskRecord<TResult>[];\n}\n\n// All read/write methods are async to accommodate storage-backed\n// implementations (`createPostgresTaskRegistry`). The in-memory impl\n// resolves immediately. The framework `await`s every call, so the\n// in-memory case pays one microtask per dispatch — negligible.\nexport interface TaskRegistry {\n /** Confirms that lifecycle methods use the account/principal-scoped v1 signatures. */\n readonly scopeVersion: 1;\n\n /**\n * Allocate a new task record. Returns the `taskId` the framework hands\n * to `platform.xxxTask(taskId, ...)`. Initial status is `submitted`.\n *\n * `hasWebhook: true` when the buyer wired `push_notification_config.url`\n * — surfaced via `tasks_get`'s `has_webhook` field. Defaults to `false`.\n *\n * `overrideTaskId` — when set, the registry uses this exact string as the\n * task id instead of minting a fresh one. Throws if the id is already\n * registered in the same account/principal scope (uniqueness within that\n * scope is the caller's responsibility).\n */\n create(opts: {\n tool: string;\n accountId: string;\n ownerScope?: string;\n hasWebhook?: boolean;\n overrideTaskId?: string;\n }): Promise<{ taskId: string }>;\n\n /** Read a task within its account/principal boundary. */\n getTask<TResult = unknown>(taskId: string, scope: TaskRegistryScope): Promise<TaskRecord<TResult> | null>;\n\n /** Administrative/test access; returns null when an id is ambiguous across scopes. Never expose to buyer traffic. */\n _getTaskUnsafe<TResult = unknown>(taskId: string): Promise<TaskRecord<TResult> | null>;\n\n /**\n * List tasks owned by a resolved account. Buyer-facing AdCP task\n * reconciliation must use this scoped surface, never the transport-level\n * MCP TaskStore, because MCP task records do not carry AdCP ownership,\n * protocol, or tool metadata.\n *\n * Multi-tenant deployments that share an AdCP account across principals\n * must persist and filter by `ownerScope`. Rows without `ownerScope` are\n * treated as legacy account-scoped rows: built-in registries return them\n * only for the account-fallback owner scope (`account:${accountId}`), not\n * for credential-, agent-, or session-scoped callers.\n */\n list?(opts: TaskRegistryListOptions): Promise<TaskRegistryListResult>;\n\n /**\n * Mark a task `completed` with the method's return value. No-op if the\n * task is already terminal (idempotent).\n */\n complete<TResult>(taskId: string, scope: TaskRegistryScope, result: TResult): Promise<void>;\n\n /**\n * Mark a task `failed` with the structured error and, when available, the\n * canonical terminal artifact. No-op if the task is already terminal.\n */\n fail(taskId: string, scope: TaskRegistryScope, error: AdcpStructuredError, result?: unknown): Promise<void>;\n\n /**\n * Record intermediate progress from `TaskHandoffContext.update(...)`.\n * Transitions the task from `'submitted'` → `'working'` on the first\n * call. No-op on already-terminal tasks. The `progress` payload is\n * written to the record and surfaced to buyers polling `tasks_get`.\n */\n updateProgress(taskId: string, scope: TaskRegistryScope, progress: TaskHandoffProgress): Promise<void>;\n\n /**\n * Register the background completion promise the framework spawned for\n * the `*Task` invocation. Tests await this for deterministic settlement;\n * production callers don't need it.\n */\n _registerBackground(taskId: string, scope: TaskRegistryScope, completion: Promise<void>): void;\n\n /**\n * Await any registered background completion for a task. Resolves\n * immediately if no background is registered or it has already settled.\n * Used by test harnesses + `tasks/get` integration.\n */\n awaitTask(taskId: string, scope: TaskRegistryScope): Promise<void>;\n\n /** Administrative/test wait across every scope sharing this public task id. Never expose to buyer traffic. */\n _awaitTaskUnsafe(taskId: string): Promise<void>;\n\n /**\n * Optional test-harness flush. In-memory registries expose this so\n * `AdcpServer.compliance.reset()` can clear hardcoded overrideTaskId\n * records between repeated storyboard runs. Persistent registries should\n * omit it unless they can safely flush their configured backend.\n */\n clear?(): void | Promise<void>;\n}\n\nexport function createInMemoryTaskRegistry(): TaskRegistry {\n const tasks = new Map<string, TaskRecord<unknown>>();\n const backgrounds = new Map<string, Promise<void>>();\n\n return {\n scopeVersion: 1,\n async create(opts: {\n tool: string;\n accountId: string;\n ownerScope?: string;\n hasWebhook?: boolean;\n overrideTaskId?: string;\n }): Promise<{ taskId: string }> {\n const taskId = opts.overrideTaskId ?? `task_${randomUUID()}`;\n const scope = { accountId: opts.accountId, ownerScope: opts.ownerScope ?? `account:${opts.accountId}` };\n const storageKey = taskStorageKey(taskId, scope);\n if (tasks.has(storageKey)) {\n throw new Error(`task_id already registered: ${taskId}`);\n }\n const now = new Date().toISOString();\n tasks.set(storageKey, {\n taskId,\n tool: opts.tool,\n accountId: opts.accountId,\n ownerScope: opts.ownerScope ?? `account:${opts.accountId}`,\n status: 'submitted',\n ...(opts.hasWebhook && { hasWebhook: true }),\n createdAt: now,\n updatedAt: now,\n });\n return { taskId };\n },\n\n async getTask<TResult = unknown>(taskId: string, scope: TaskRegistryScope): Promise<TaskRecord<TResult> | null> {\n const record = tasks.get(taskStorageKey(taskId, scope));\n return (record as TaskRecord<TResult> | undefined) ?? null;\n },\n\n async _getTaskUnsafe<TResult = unknown>(taskId: string): Promise<TaskRecord<TResult> | null> {\n const matches = Array.from(tasks.values()).filter(record => record.taskId === taskId);\n return matches.length === 1 ? (matches[0] as TaskRecord<TResult>) : null;\n },\n\n async list(opts: TaskRegistryListOptions): Promise<TaskRegistryListResult> {\n return {\n tasks: Array.from(tasks.values()).filter(\n record => record.accountId === opts.accountId && record.ownerScope === opts.ownerScope\n ),\n };\n },\n\n async complete<TResult>(taskId: string, scope: TaskRegistryScope, result: TResult): Promise<void> {\n const existing = tasks.get(taskStorageKey(taskId, scope));\n if (!existing) return;\n if (isTerminalTaskStatus(existing.status)) return;\n existing.status = 'completed';\n existing.result = result;\n existing.updatedAt = new Date().toISOString();\n },\n\n async fail(taskId: string, scope: TaskRegistryScope, error: AdcpStructuredError, result?: unknown): Promise<void> {\n const existing = tasks.get(taskStorageKey(taskId, scope));\n if (!existing) return;\n if (isTerminalTaskStatus(existing.status)) return;\n existing.status = 'failed';\n existing.error = error;\n if (result !== undefined) existing.result = result;\n existing.statusMessage = error.message;\n existing.updatedAt = new Date().toISOString();\n },\n\n async updateProgress(taskId: string, scope: TaskRegistryScope, progress: TaskHandoffProgress): Promise<void> {\n const existing = tasks.get(taskStorageKey(taskId, scope));\n if (!existing) return;\n if (isTerminalTaskStatus(existing.status)) return;\n if (existing.status === 'submitted') existing.status = 'working';\n existing.progress = progress;\n existing.updatedAt = new Date().toISOString();\n },\n\n _registerBackground(taskId: string, scope: TaskRegistryScope, completion: Promise<void>): void {\n const storageKey = taskStorageKey(taskId, scope);\n const composed: Promise<void> = completion.then(\n () => {\n if (backgrounds.get(storageKey) === composed) backgrounds.delete(storageKey);\n },\n () => {\n if (backgrounds.get(storageKey) === composed) backgrounds.delete(storageKey);\n }\n );\n backgrounds.set(storageKey, composed);\n },\n\n async awaitTask(taskId: string, scope: TaskRegistryScope): Promise<void> {\n const pending = backgrounds.get(taskStorageKey(taskId, scope));\n if (pending) await pending;\n },\n\n async _awaitTaskUnsafe(taskId: string): Promise<void> {\n const pending = Array.from(backgrounds.entries())\n .filter(([key]) => (JSON.parse(key) as [string, string, string])[2] === taskId)\n .map(([, completion]) => completion);\n await Promise.all(pending);\n },\n\n clear(): void {\n tasks.clear();\n backgrounds.clear();\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -3,17 +3,27 @@ const TERMINAL_TASK_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "
3
3
  function isTerminalTaskStatus(status) {
4
4
  return TERMINAL_TASK_STATUSES.has(status);
5
5
  }
6
+ function taskRecordMatchesScope(record, scope) {
7
+ if (record.accountId !== scope.accountId) return false;
8
+ return record.ownerScope === scope.ownerScope || record.ownerScope === void 0 && scope.ownerScope === `account:${scope.accountId}`;
9
+ }
10
+ function taskStorageKey(taskId, scope) {
11
+ return JSON.stringify([scope.accountId, scope.ownerScope, taskId]);
12
+ }
6
13
  function createInMemoryTaskRegistry() {
7
14
  const tasks = /* @__PURE__ */ new Map();
8
15
  const backgrounds = /* @__PURE__ */ new Map();
9
16
  return {
17
+ scopeVersion: 1,
10
18
  async create(opts) {
11
19
  const taskId = opts.overrideTaskId ?? `task_${randomUUID()}`;
12
- if (tasks.has(taskId)) {
20
+ const scope = { accountId: opts.accountId, ownerScope: opts.ownerScope ?? `account:${opts.accountId}` };
21
+ const storageKey = taskStorageKey(taskId, scope);
22
+ if (tasks.has(storageKey)) {
13
23
  throw new Error(`task_id already registered: ${taskId}`);
14
24
  }
15
25
  const now = (/* @__PURE__ */ new Date()).toISOString();
16
- tasks.set(taskId, {
26
+ tasks.set(storageKey, {
17
27
  taskId,
18
28
  tool: opts.tool,
19
29
  accountId: opts.accountId,
@@ -25,28 +35,31 @@ function createInMemoryTaskRegistry() {
25
35
  });
26
36
  return { taskId };
27
37
  },
28
- async getTask(taskId) {
29
- const record = tasks.get(taskId);
38
+ async getTask(taskId, scope) {
39
+ const record = tasks.get(taskStorageKey(taskId, scope));
30
40
  return record ?? null;
31
41
  },
42
+ async _getTaskUnsafe(taskId) {
43
+ const matches = Array.from(tasks.values()).filter((record) => record.taskId === taskId);
44
+ return matches.length === 1 ? matches[0] : null;
45
+ },
32
46
  async list(opts) {
33
- if (opts.ownerScope === void 0) return { tasks: [] };
34
47
  return {
35
48
  tasks: Array.from(tasks.values()).filter(
36
- (record) => record.accountId === opts.accountId && (record.ownerScope === opts.ownerScope || record.ownerScope === void 0 && opts.ownerScope === `account:${opts.accountId}`)
49
+ (record) => record.accountId === opts.accountId && record.ownerScope === opts.ownerScope
37
50
  )
38
51
  };
39
52
  },
40
- async complete(taskId, result) {
41
- const existing = tasks.get(taskId);
53
+ async complete(taskId, scope, result) {
54
+ const existing = tasks.get(taskStorageKey(taskId, scope));
42
55
  if (!existing) return;
43
56
  if (isTerminalTaskStatus(existing.status)) return;
44
57
  existing.status = "completed";
45
58
  existing.result = result;
46
59
  existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
47
60
  },
48
- async fail(taskId, error, result) {
49
- const existing = tasks.get(taskId);
61
+ async fail(taskId, scope, error, result) {
62
+ const existing = tasks.get(taskStorageKey(taskId, scope));
50
63
  if (!existing) return;
51
64
  if (isTerminalTaskStatus(existing.status)) return;
52
65
  existing.status = "failed";
@@ -55,29 +68,34 @@ function createInMemoryTaskRegistry() {
55
68
  existing.statusMessage = error.message;
56
69
  existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
57
70
  },
58
- async updateProgress(taskId, progress) {
59
- const existing = tasks.get(taskId);
71
+ async updateProgress(taskId, scope, progress) {
72
+ const existing = tasks.get(taskStorageKey(taskId, scope));
60
73
  if (!existing) return;
61
74
  if (isTerminalTaskStatus(existing.status)) return;
62
75
  if (existing.status === "submitted") existing.status = "working";
63
76
  existing.progress = progress;
64
77
  existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
65
78
  },
66
- _registerBackground(taskId, completion) {
79
+ _registerBackground(taskId, scope, completion) {
80
+ const storageKey = taskStorageKey(taskId, scope);
67
81
  const composed = completion.then(
68
82
  () => {
69
- if (backgrounds.get(taskId) === composed) backgrounds.delete(taskId);
83
+ if (backgrounds.get(storageKey) === composed) backgrounds.delete(storageKey);
70
84
  },
71
85
  () => {
72
- if (backgrounds.get(taskId) === composed) backgrounds.delete(taskId);
86
+ if (backgrounds.get(storageKey) === composed) backgrounds.delete(storageKey);
73
87
  }
74
88
  );
75
- backgrounds.set(taskId, composed);
89
+ backgrounds.set(storageKey, composed);
76
90
  },
77
- async awaitTask(taskId) {
78
- const pending = backgrounds.get(taskId);
91
+ async awaitTask(taskId, scope) {
92
+ const pending = backgrounds.get(taskStorageKey(taskId, scope));
79
93
  if (pending) await pending;
80
94
  },
95
+ async _awaitTaskUnsafe(taskId) {
96
+ const pending = Array.from(backgrounds.entries()).filter(([key]) => JSON.parse(key)[2] === taskId).map(([, completion]) => completion);
97
+ await Promise.all(pending);
98
+ },
81
99
  clear() {
82
100
  tasks.clear();
83
101
  backgrounds.clear();
@@ -85,6 +103,7 @@ function createInMemoryTaskRegistry() {
85
103
  };
86
104
  }
87
105
  export {
88
- createInMemoryTaskRegistry
106
+ createInMemoryTaskRegistry,
107
+ taskRecordMatchesScope
89
108
  };
90
109
  //# sourceMappingURL=task-registry.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/lib/server/decisioning/runtime/task-registry.ts"],"sourcesContent":["/**\n * In-memory task registry for the v6.0 alpha runtime.\n *\n * The framework owns task lifecycle. When an adopter method returns a\n * `TaskHandoff` marker, the framework:\n * 1. Allocates a `taskId` and writes a `submitted` record.\n * 2. Returns the submitted envelope to the buyer immediately.\n * 3. Runs the handoff function in the background.\n * 4. Updates the record on progress (`updateProgress`) and terminal\n * state (`complete` / `fail`) from the method's return/throw.\n *\n * Adopters never call into the registry directly. Wire-level `tasks/get`\n * integration (so buyers can poll the lifecycle) reads via `getTask`;\n * test harnesses use `awaitTask` to flush the background promise\n * deterministically.\n *\n * Status: Preview / 6.0.\n *\n * @public\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { AdcpStructuredError, TaskHandoffProgress } from '../async-outcome';\n\n/**\n * AdCP-spec task lifecycle states. Mirrors `enums/task-status.json` —\n * the v6 framework writes `'submitted'` on create, transitions to\n * `'working'` on the first `updateProgress()` call, and terminates at\n * `'completed'` / `'failed'`. The other states (`'input-required'`,\n * `'canceled'`, `'rejected'`, `'auth-required'`, `'unknown'`) are\n * reserved for adopter-emitted transitions via the forthcoming\n * `taskRegistry.transition()` API (v6.1).\n */\nexport type TaskStatus =\n | 'submitted'\n | 'working'\n | 'input-required'\n | 'completed'\n | 'canceled'\n | 'failed'\n | 'rejected'\n | 'auth-required'\n | 'unknown';\n\nconst TERMINAL_TASK_STATUSES: ReadonlySet<TaskStatus> = new Set(['completed', 'failed', 'rejected', 'canceled']);\n\nfunction isTerminalTaskStatus(status: TaskStatus): boolean {\n return TERMINAL_TASK_STATUSES.has(status);\n}\n\nexport interface TaskRecord<TResult = unknown, TError extends AdcpStructuredError = AdcpStructuredError> {\n taskId: string;\n /** Tool name that started the task (e.g., 'create_media_buy'). */\n tool: string;\n /** Account that started the task — sessionKey-like for cross-request scoping. */\n accountId: string;\n /** Tenant/principal owner scope used with accountId for list/read isolation. */\n ownerScope?: string;\n /** Current lifecycle state — full AdCP-spec `task-status` enum. */\n status: TaskStatus;\n /** Status message on the final arm (`error.message` on failed). */\n statusMessage?: string;\n /** Canonical terminal artifact on `completed`, `failed`, or `rejected`. */\n result?: TResult;\n /** Terminal error on `failed`. */\n error?: TError;\n /**\n * Intermediate progress from `TaskHandoffContext.update(...)` calls.\n * Written by the background handoff function; surfaced to buyers polling\n * `tasks_get` via the spec `progress` field.\n */\n progress?: TaskHandoffProgress;\n /**\n * Whether the buyer wired `push_notification_config.url` on the original\n * request. Surfaced to the buyer via `tasks_get`'s spec-defined\n * `has_webhook: boolean` field so they can decide between long-poll vs.\n * single-shot polling.\n */\n hasWebhook?: boolean;\n /** ISO 8601 timestamps. */\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface TaskRegistryListOptions {\n accountId: string;\n /**\n * Caller ownership scope derived from buyer agent, credential, session,\n * or the single-principal account fallback. Optional for source\n * compatibility with older custom registries, but buyer-visible protocol\n * task tools pass it whenever they can identify a caller and built-in\n * registries fail closed when it is absent.\n */\n ownerScope?: string;\n}\n\nexport interface TaskRegistryListResult<TResult = unknown> {\n tasks: TaskRecord<TResult>[];\n}\n\n// All read/write methods are async to accommodate storage-backed\n// implementations (`createPostgresTaskRegistry`). The in-memory impl\n// resolves immediately. The framework `await`s every call, so the\n// in-memory case pays one microtask per dispatch — negligible.\nexport interface TaskRegistry {\n /**\n * Allocate a new task record. Returns the `taskId` the framework hands\n * to `platform.xxxTask(taskId, ...)`. Initial status is `submitted`.\n *\n * `hasWebhook: true` when the buyer wired `push_notification_config.url`\n * — surfaced via `tasks_get`'s `has_webhook` field. Defaults to `false`.\n *\n * `overrideTaskId` — when set, the registry uses this exact string as the\n * task id instead of minting a fresh one. Throws if the id is already\n * registered (uniqueness is the caller's responsibility).\n */\n create(opts: {\n tool: string;\n accountId: string;\n ownerScope?: string;\n hasWebhook?: boolean;\n overrideTaskId?: string;\n }): Promise<{ taskId: string }>;\n\n /** Read a task by id. Returns `null` if unknown. */\n getTask<TResult = unknown>(taskId: string): Promise<TaskRecord<TResult> | null>;\n\n /**\n * List tasks owned by a resolved account. Buyer-facing AdCP task\n * reconciliation must use this scoped surface, never the transport-level\n * MCP TaskStore, because MCP task records do not carry AdCP ownership,\n * protocol, or tool metadata.\n *\n * Multi-tenant deployments that share an AdCP account across principals\n * must persist and filter by `ownerScope`. Rows without `ownerScope` are\n * treated as legacy account-scoped rows: built-in registries return them\n * only for the account-fallback owner scope (`account:${accountId}`), not\n * for credential-, agent-, or session-scoped callers.\n */\n list?(opts: TaskRegistryListOptions): Promise<TaskRegistryListResult>;\n\n /**\n * Mark a task `completed` with the method's return value. No-op if the\n * task is already terminal (idempotent).\n */\n complete<TResult>(taskId: string, result: TResult): Promise<void>;\n\n /**\n * Mark a task `failed` with the structured error and, when available, the\n * canonical terminal artifact. No-op if the task is already terminal.\n */\n fail(taskId: string, error: AdcpStructuredError, result?: unknown): Promise<void>;\n\n /**\n * Record intermediate progress from `TaskHandoffContext.update(...)`.\n * Transitions the task from `'submitted'` → `'working'` on the first\n * call. No-op on already-terminal tasks. The `progress` payload is\n * written to the record and surfaced to buyers polling `tasks_get`.\n */\n updateProgress(taskId: string, progress: TaskHandoffProgress): Promise<void>;\n\n /**\n * Register the background completion promise the framework spawned for\n * the `*Task` invocation. Tests await this for deterministic settlement;\n * production callers don't need it.\n *\n * @internal\n */\n _registerBackground(taskId: string, completion: Promise<void>): void;\n\n /**\n * Await any registered background completion for a task. Resolves\n * immediately if no background is registered or it has already settled.\n * Used by test harnesses + `tasks/get` integration.\n */\n awaitTask(taskId: string): Promise<void>;\n\n /**\n * Optional test-harness flush. In-memory registries expose this so\n * `AdcpServer.compliance.reset()` can clear hardcoded overrideTaskId\n * records between repeated storyboard runs. Persistent registries should\n * omit it unless they can safely flush their configured backend.\n */\n clear?(): void | Promise<void>;\n}\n\nexport function createInMemoryTaskRegistry(): TaskRegistry {\n const tasks = new Map<string, TaskRecord<unknown>>();\n const backgrounds = new Map<string, Promise<void>>();\n\n return {\n async create(opts: {\n tool: string;\n accountId: string;\n ownerScope?: string;\n hasWebhook?: boolean;\n overrideTaskId?: string;\n }): Promise<{ taskId: string }> {\n const taskId = opts.overrideTaskId ?? `task_${randomUUID()}`;\n if (tasks.has(taskId)) {\n throw new Error(`task_id already registered: ${taskId}`);\n }\n const now = new Date().toISOString();\n tasks.set(taskId, {\n taskId,\n tool: opts.tool,\n accountId: opts.accountId,\n ownerScope: opts.ownerScope ?? `account:${opts.accountId}`,\n status: 'submitted',\n ...(opts.hasWebhook && { hasWebhook: true }),\n createdAt: now,\n updatedAt: now,\n });\n return { taskId };\n },\n\n async getTask<TResult = unknown>(taskId: string): Promise<TaskRecord<TResult> | null> {\n const record = tasks.get(taskId);\n return (record as TaskRecord<TResult> | undefined) ?? null;\n },\n\n async list(opts: TaskRegistryListOptions): Promise<TaskRegistryListResult> {\n if (opts.ownerScope === undefined) return { tasks: [] };\n return {\n tasks: Array.from(tasks.values()).filter(\n record =>\n record.accountId === opts.accountId &&\n (record.ownerScope === opts.ownerScope ||\n (record.ownerScope === undefined && opts.ownerScope === `account:${opts.accountId}`))\n ),\n };\n },\n\n async complete<TResult>(taskId: string, result: TResult): Promise<void> {\n const existing = tasks.get(taskId);\n if (!existing) return;\n if (isTerminalTaskStatus(existing.status)) return;\n existing.status = 'completed';\n existing.result = result;\n existing.updatedAt = new Date().toISOString();\n },\n\n async fail(taskId: string, error: AdcpStructuredError, result?: unknown): Promise<void> {\n const existing = tasks.get(taskId);\n if (!existing) return;\n if (isTerminalTaskStatus(existing.status)) return;\n existing.status = 'failed';\n existing.error = error;\n if (result !== undefined) existing.result = result;\n existing.statusMessage = error.message;\n existing.updatedAt = new Date().toISOString();\n },\n\n async updateProgress(taskId: string, progress: TaskHandoffProgress): Promise<void> {\n const existing = tasks.get(taskId);\n if (!existing) return;\n if (isTerminalTaskStatus(existing.status)) return;\n if (existing.status === 'submitted') existing.status = 'working';\n existing.progress = progress;\n existing.updatedAt = new Date().toISOString();\n },\n\n _registerBackground(taskId: string, completion: Promise<void>): void {\n const composed: Promise<void> = completion.then(\n () => {\n if (backgrounds.get(taskId) === composed) backgrounds.delete(taskId);\n },\n () => {\n if (backgrounds.get(taskId) === composed) backgrounds.delete(taskId);\n }\n );\n backgrounds.set(taskId, composed);\n },\n\n async awaitTask(taskId: string): Promise<void> {\n const pending = backgrounds.get(taskId);\n if (pending) await pending;\n },\n\n clear(): void {\n tasks.clear();\n backgrounds.clear();\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../../src/lib/server/decisioning/runtime/task-registry.ts"],"sourcesContent":["/**\n * In-memory task registry for the v6.0 alpha runtime.\n *\n * The framework owns task lifecycle. When an adopter method returns a\n * `TaskHandoff` marker, the framework:\n * 1. Allocates a `taskId` and writes a `submitted` record.\n * 2. Returns the submitted envelope to the buyer immediately.\n * 3. Runs the handoff function in the background.\n * 4. Updates the record on progress (`updateProgress`) and terminal\n * state (`complete` / `fail`) from the method's return/throw.\n *\n * Adopters never call into the registry directly. Wire-level `tasks/get`\n * integration (so buyers can poll the lifecycle) reads via scoped `getTask`;\n * test harnesses use `awaitTask` to flush the background promise\n * deterministically.\n *\n * Status: Preview / 6.0.\n *\n * @public\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { AdcpStructuredError, TaskHandoffProgress } from '../async-outcome';\n\n/**\n * AdCP-spec task lifecycle states. Mirrors `enums/task-status.json` —\n * the v6 framework writes `'submitted'` on create, transitions to\n * `'working'` on the first `updateProgress()` call, and terminates at\n * `'completed'` / `'failed'`. The other states (`'input-required'`,\n * `'canceled'`, `'rejected'`, `'auth-required'`, `'unknown'`) are\n * reserved for adopter-emitted transitions via the forthcoming\n * `taskRegistry.transition()` API (v6.1).\n */\nexport type TaskStatus =\n | 'submitted'\n | 'working'\n | 'input-required'\n | 'completed'\n | 'canceled'\n | 'failed'\n | 'rejected'\n | 'auth-required'\n | 'unknown';\n\nconst TERMINAL_TASK_STATUSES: ReadonlySet<TaskStatus> = new Set(['completed', 'failed', 'rejected', 'canceled']);\n\nfunction isTerminalTaskStatus(status: TaskStatus): boolean {\n return TERMINAL_TASK_STATUSES.has(status);\n}\n\nexport interface TaskRecord<TResult = unknown, TError extends AdcpStructuredError = AdcpStructuredError> {\n taskId: string;\n /** Tool name that started the task (e.g., 'create_media_buy'). */\n tool: string;\n /** Account that started the task — sessionKey-like for cross-request scoping. */\n accountId: string;\n /** Tenant/principal owner scope used with accountId for list/read isolation. */\n ownerScope?: string;\n /** Current lifecycle state — full AdCP-spec `task-status` enum. */\n status: TaskStatus;\n /** Status message on the final arm (`error.message` on failed). */\n statusMessage?: string;\n /** Canonical terminal artifact on `completed`, `failed`, or `rejected`. */\n result?: TResult;\n /** Terminal error on `failed`. */\n error?: TError;\n /**\n * Intermediate progress from `TaskHandoffContext.update(...)` calls.\n * Written by the background handoff function; surfaced to buyers polling\n * `tasks_get` via the spec `progress` field.\n */\n progress?: TaskHandoffProgress;\n /**\n * Whether the buyer wired `push_notification_config.url` on the original\n * request. Surfaced to the buyer via `tasks_get`'s spec-defined\n * `has_webhook: boolean` field so they can decide between long-poll vs.\n * single-shot polling.\n */\n hasWebhook?: boolean;\n /** ISO 8601 timestamps. */\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface TaskRegistryListOptions {\n accountId: string;\n /** Caller ownership scope derived from authenticated server context. */\n ownerScope: string;\n}\n\n/** Account/principal boundary required for buyer-visible task access. */\nexport interface TaskRegistryScope {\n accountId: string;\n ownerScope: string;\n}\n\n/** Defense-in-depth boundary check for custom registry results. */\nexport function taskRecordMatchesScope(record: TaskRecord, scope: TaskRegistryScope): boolean {\n if (record.accountId !== scope.accountId) return false;\n return (\n record.ownerScope === scope.ownerScope ||\n (record.ownerScope === undefined && scope.ownerScope === `account:${scope.accountId}`)\n );\n}\n\nfunction taskStorageKey(taskId: string, scope: TaskRegistryScope): string {\n return JSON.stringify([scope.accountId, scope.ownerScope, taskId]);\n}\n\nexport interface TaskRegistryListResult<TResult = unknown> {\n tasks: TaskRecord<TResult>[];\n}\n\n// All read/write methods are async to accommodate storage-backed\n// implementations (`createPostgresTaskRegistry`). The in-memory impl\n// resolves immediately. The framework `await`s every call, so the\n// in-memory case pays one microtask per dispatch — negligible.\nexport interface TaskRegistry {\n /** Confirms that lifecycle methods use the account/principal-scoped v1 signatures. */\n readonly scopeVersion: 1;\n\n /**\n * Allocate a new task record. Returns the `taskId` the framework hands\n * to `platform.xxxTask(taskId, ...)`. Initial status is `submitted`.\n *\n * `hasWebhook: true` when the buyer wired `push_notification_config.url`\n * — surfaced via `tasks_get`'s `has_webhook` field. Defaults to `false`.\n *\n * `overrideTaskId` — when set, the registry uses this exact string as the\n * task id instead of minting a fresh one. Throws if the id is already\n * registered in the same account/principal scope (uniqueness within that\n * scope is the caller's responsibility).\n */\n create(opts: {\n tool: string;\n accountId: string;\n ownerScope?: string;\n hasWebhook?: boolean;\n overrideTaskId?: string;\n }): Promise<{ taskId: string }>;\n\n /** Read a task within its account/principal boundary. */\n getTask<TResult = unknown>(taskId: string, scope: TaskRegistryScope): Promise<TaskRecord<TResult> | null>;\n\n /** Administrative/test access; returns null when an id is ambiguous across scopes. Never expose to buyer traffic. */\n _getTaskUnsafe<TResult = unknown>(taskId: string): Promise<TaskRecord<TResult> | null>;\n\n /**\n * List tasks owned by a resolved account. Buyer-facing AdCP task\n * reconciliation must use this scoped surface, never the transport-level\n * MCP TaskStore, because MCP task records do not carry AdCP ownership,\n * protocol, or tool metadata.\n *\n * Multi-tenant deployments that share an AdCP account across principals\n * must persist and filter by `ownerScope`. Rows without `ownerScope` are\n * treated as legacy account-scoped rows: built-in registries return them\n * only for the account-fallback owner scope (`account:${accountId}`), not\n * for credential-, agent-, or session-scoped callers.\n */\n list?(opts: TaskRegistryListOptions): Promise<TaskRegistryListResult>;\n\n /**\n * Mark a task `completed` with the method's return value. No-op if the\n * task is already terminal (idempotent).\n */\n complete<TResult>(taskId: string, scope: TaskRegistryScope, result: TResult): Promise<void>;\n\n /**\n * Mark a task `failed` with the structured error and, when available, the\n * canonical terminal artifact. No-op if the task is already terminal.\n */\n fail(taskId: string, scope: TaskRegistryScope, error: AdcpStructuredError, result?: unknown): Promise<void>;\n\n /**\n * Record intermediate progress from `TaskHandoffContext.update(...)`.\n * Transitions the task from `'submitted'` → `'working'` on the first\n * call. No-op on already-terminal tasks. The `progress` payload is\n * written to the record and surfaced to buyers polling `tasks_get`.\n */\n updateProgress(taskId: string, scope: TaskRegistryScope, progress: TaskHandoffProgress): Promise<void>;\n\n /**\n * Register the background completion promise the framework spawned for\n * the `*Task` invocation. Tests await this for deterministic settlement;\n * production callers don't need it.\n */\n _registerBackground(taskId: string, scope: TaskRegistryScope, completion: Promise<void>): void;\n\n /**\n * Await any registered background completion for a task. Resolves\n * immediately if no background is registered or it has already settled.\n * Used by test harnesses + `tasks/get` integration.\n */\n awaitTask(taskId: string, scope: TaskRegistryScope): Promise<void>;\n\n /** Administrative/test wait across every scope sharing this public task id. Never expose to buyer traffic. */\n _awaitTaskUnsafe(taskId: string): Promise<void>;\n\n /**\n * Optional test-harness flush. In-memory registries expose this so\n * `AdcpServer.compliance.reset()` can clear hardcoded overrideTaskId\n * records between repeated storyboard runs. Persistent registries should\n * omit it unless they can safely flush their configured backend.\n */\n clear?(): void | Promise<void>;\n}\n\nexport function createInMemoryTaskRegistry(): TaskRegistry {\n const tasks = new Map<string, TaskRecord<unknown>>();\n const backgrounds = new Map<string, Promise<void>>();\n\n return {\n scopeVersion: 1,\n async create(opts: {\n tool: string;\n accountId: string;\n ownerScope?: string;\n hasWebhook?: boolean;\n overrideTaskId?: string;\n }): Promise<{ taskId: string }> {\n const taskId = opts.overrideTaskId ?? `task_${randomUUID()}`;\n const scope = { accountId: opts.accountId, ownerScope: opts.ownerScope ?? `account:${opts.accountId}` };\n const storageKey = taskStorageKey(taskId, scope);\n if (tasks.has(storageKey)) {\n throw new Error(`task_id already registered: ${taskId}`);\n }\n const now = new Date().toISOString();\n tasks.set(storageKey, {\n taskId,\n tool: opts.tool,\n accountId: opts.accountId,\n ownerScope: opts.ownerScope ?? `account:${opts.accountId}`,\n status: 'submitted',\n ...(opts.hasWebhook && { hasWebhook: true }),\n createdAt: now,\n updatedAt: now,\n });\n return { taskId };\n },\n\n async getTask<TResult = unknown>(taskId: string, scope: TaskRegistryScope): Promise<TaskRecord<TResult> | null> {\n const record = tasks.get(taskStorageKey(taskId, scope));\n return (record as TaskRecord<TResult> | undefined) ?? null;\n },\n\n async _getTaskUnsafe<TResult = unknown>(taskId: string): Promise<TaskRecord<TResult> | null> {\n const matches = Array.from(tasks.values()).filter(record => record.taskId === taskId);\n return matches.length === 1 ? (matches[0] as TaskRecord<TResult>) : null;\n },\n\n async list(opts: TaskRegistryListOptions): Promise<TaskRegistryListResult> {\n return {\n tasks: Array.from(tasks.values()).filter(\n record => record.accountId === opts.accountId && record.ownerScope === opts.ownerScope\n ),\n };\n },\n\n async complete<TResult>(taskId: string, scope: TaskRegistryScope, result: TResult): Promise<void> {\n const existing = tasks.get(taskStorageKey(taskId, scope));\n if (!existing) return;\n if (isTerminalTaskStatus(existing.status)) return;\n existing.status = 'completed';\n existing.result = result;\n existing.updatedAt = new Date().toISOString();\n },\n\n async fail(taskId: string, scope: TaskRegistryScope, error: AdcpStructuredError, result?: unknown): Promise<void> {\n const existing = tasks.get(taskStorageKey(taskId, scope));\n if (!existing) return;\n if (isTerminalTaskStatus(existing.status)) return;\n existing.status = 'failed';\n existing.error = error;\n if (result !== undefined) existing.result = result;\n existing.statusMessage = error.message;\n existing.updatedAt = new Date().toISOString();\n },\n\n async updateProgress(taskId: string, scope: TaskRegistryScope, progress: TaskHandoffProgress): Promise<void> {\n const existing = tasks.get(taskStorageKey(taskId, scope));\n if (!existing) return;\n if (isTerminalTaskStatus(existing.status)) return;\n if (existing.status === 'submitted') existing.status = 'working';\n existing.progress = progress;\n existing.updatedAt = new Date().toISOString();\n },\n\n _registerBackground(taskId: string, scope: TaskRegistryScope, completion: Promise<void>): void {\n const storageKey = taskStorageKey(taskId, scope);\n const composed: Promise<void> = completion.then(\n () => {\n if (backgrounds.get(storageKey) === composed) backgrounds.delete(storageKey);\n },\n () => {\n if (backgrounds.get(storageKey) === composed) backgrounds.delete(storageKey);\n }\n );\n backgrounds.set(storageKey, composed);\n },\n\n async awaitTask(taskId: string, scope: TaskRegistryScope): Promise<void> {\n const pending = backgrounds.get(taskStorageKey(taskId, scope));\n if (pending) await pending;\n },\n\n async _awaitTaskUnsafe(taskId: string): Promise<void> {\n const pending = Array.from(backgrounds.entries())\n .filter(([key]) => (JSON.parse(key) as [string, string, string])[2] === taskId)\n .map(([, completion]) => completion);\n await Promise.all(pending);\n },\n\n clear(): void {\n tasks.clear();\n backgrounds.clear();\n },\n };\n}\n"],"mappings":"AAqBA,SAAS,kBAAkB;AAuB3B,MAAM,yBAAkD,oBAAI,IAAI,CAAC,aAAa,UAAU,YAAY,UAAU,CAAC;AAE/G,SAAS,qBAAqB,QAA6B;AACzD,SAAO,uBAAuB,IAAI,MAAM;AAC1C;AAiDO,SAAS,uBAAuB,QAAoB,OAAmC;AAC5F,MAAI,OAAO,cAAc,MAAM,UAAW,QAAO;AACjD,SACE,OAAO,eAAe,MAAM,cAC3B,OAAO,eAAe,UAAa,MAAM,eAAe,WAAW,MAAM,SAAS;AAEvF;AAEA,SAAS,eAAe,QAAgB,OAAkC;AACxE,SAAO,KAAK,UAAU,CAAC,MAAM,WAAW,MAAM,YAAY,MAAM,CAAC;AACnE;AAoGO,SAAS,6BAA2C;AACzD,QAAM,QAAQ,oBAAI,IAAiC;AACnD,QAAM,cAAc,oBAAI,IAA2B;AAEnD,SAAO;AAAA,IACL,cAAc;AAAA,IACd,MAAM,OAAO,MAMmB;AAC9B,YAAM,SAAS,KAAK,kBAAkB,QAAQ,WAAW,CAAC;AAC1D,YAAM,QAAQ,EAAE,WAAW,KAAK,WAAW,YAAY,KAAK,cAAc,WAAW,KAAK,SAAS,GAAG;AACtG,YAAM,aAAa,eAAe,QAAQ,KAAK;AAC/C,UAAI,MAAM,IAAI,UAAU,GAAG;AACzB,cAAM,IAAI,MAAM,+BAA+B,MAAM,EAAE;AAAA,MACzD;AACA,YAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,YAAM,IAAI,YAAY;AAAA,QACpB;AAAA,QACA,MAAM,KAAK;AAAA,QACX,WAAW,KAAK;AAAA,QAChB,YAAY,KAAK,cAAc,WAAW,KAAK,SAAS;AAAA,QACxD,QAAQ;AAAA,QACR,GAAI,KAAK,cAAc,EAAE,YAAY,KAAK;AAAA,QAC1C,WAAW;AAAA,QACX,WAAW;AAAA,MACb,CAAC;AACD,aAAO,EAAE,OAAO;AAAA,IAClB;AAAA,IAEA,MAAM,QAA2B,QAAgB,OAA+D;AAC9G,YAAM,SAAS,MAAM,IAAI,eAAe,QAAQ,KAAK,CAAC;AACtD,aAAQ,UAA8C;AAAA,IACxD;AAAA,IAEA,MAAM,eAAkC,QAAqD;AAC3F,YAAM,UAAU,MAAM,KAAK,MAAM,OAAO,CAAC,EAAE,OAAO,YAAU,OAAO,WAAW,MAAM;AACpF,aAAO,QAAQ,WAAW,IAAK,QAAQ,CAAC,IAA4B;AAAA,IACtE;AAAA,IAEA,MAAM,KAAK,MAAgE;AACzE,aAAO;AAAA,QACL,OAAO,MAAM,KAAK,MAAM,OAAO,CAAC,EAAE;AAAA,UAChC,YAAU,OAAO,cAAc,KAAK,aAAa,OAAO,eAAe,KAAK;AAAA,QAC9E;AAAA,MACF;AAAA,IACF;AAAA,IAEA,MAAM,SAAkB,QAAgB,OAA0B,QAAgC;AAChG,YAAM,WAAW,MAAM,IAAI,eAAe,QAAQ,KAAK,CAAC;AACxD,UAAI,CAAC,SAAU;AACf,UAAI,qBAAqB,SAAS,MAAM,EAAG;AAC3C,eAAS,SAAS;AAClB,eAAS,SAAS;AAClB,eAAS,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IAC9C;AAAA,IAEA,MAAM,KAAK,QAAgB,OAA0B,OAA4B,QAAiC;AAChH,YAAM,WAAW,MAAM,IAAI,eAAe,QAAQ,KAAK,CAAC;AACxD,UAAI,CAAC,SAAU;AACf,UAAI,qBAAqB,SAAS,MAAM,EAAG;AAC3C,eAAS,SAAS;AAClB,eAAS,QAAQ;AACjB,UAAI,WAAW,OAAW,UAAS,SAAS;AAC5C,eAAS,gBAAgB,MAAM;AAC/B,eAAS,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IAC9C;AAAA,IAEA,MAAM,eAAe,QAAgB,OAA0B,UAA8C;AAC3G,YAAM,WAAW,MAAM,IAAI,eAAe,QAAQ,KAAK,CAAC;AACxD,UAAI,CAAC,SAAU;AACf,UAAI,qBAAqB,SAAS,MAAM,EAAG;AAC3C,UAAI,SAAS,WAAW,YAAa,UAAS,SAAS;AACvD,eAAS,WAAW;AACpB,eAAS,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IAC9C;AAAA,IAEA,oBAAoB,QAAgB,OAA0B,YAAiC;AAC7F,YAAM,aAAa,eAAe,QAAQ,KAAK;AAC/C,YAAM,WAA0B,WAAW;AAAA,QACzC,MAAM;AACJ,cAAI,YAAY,IAAI,UAAU,MAAM,SAAU,aAAY,OAAO,UAAU;AAAA,QAC7E;AAAA,QACA,MAAM;AACJ,cAAI,YAAY,IAAI,UAAU,MAAM,SAAU,aAAY,OAAO,UAAU;AAAA,QAC7E;AAAA,MACF;AACA,kBAAY,IAAI,YAAY,QAAQ;AAAA,IACtC;AAAA,IAEA,MAAM,UAAU,QAAgB,OAAyC;AACvE,YAAM,UAAU,YAAY,IAAI,eAAe,QAAQ,KAAK,CAAC;AAC7D,UAAI,QAAS,OAAM;AAAA,IACrB;AAAA,IAEA,MAAM,iBAAiB,QAA+B;AACpD,YAAM,UAAU,MAAM,KAAK,YAAY,QAAQ,CAAC,EAC7C,OAAO,CAAC,CAAC,GAAG,MAAO,KAAK,MAAM,GAAG,EAA+B,CAAC,MAAM,MAAM,EAC7E,IAAI,CAAC,CAAC,EAAE,UAAU,MAAM,UAAU;AACrC,YAAM,QAAQ,IAAI,OAAO;AAAA,IAC3B;AAAA,IAEA,QAAc;AACZ,YAAM,MAAM;AACZ,kBAAY,MAAM;AAAA,IACpB;AAAA,EACF;AACF;","names":[]}
@@ -28,7 +28,7 @@
28
28
  */
29
29
  import type { Account } from '../account.mjs';
30
30
  import type { RequestContext } from '../context.mjs';
31
- import type { TaskRegistry } from './task-registry.mjs';
31
+ import type { TaskRegistry, TaskRegistryScope } from './task-registry.mjs';
32
32
  import { type TaskHandoffContext } from '../async-outcome.mjs';
33
33
  import type { CtxMetadataStore } from '../../ctx-metadata/index.mjs';
34
34
  export declare function buildRequestContext<TCtxMeta = Record<string, unknown>>(handlerCtx: HandlerContext<Account<TCtxMeta>>, ctxMetadataStore?: CtxMetadataStore, input?: Readonly<Record<string, unknown>>): RequestContext<Account<TCtxMeta>>;
@@ -50,4 +50,4 @@ export declare function buildRequestContext<TCtxMeta = Record<string, unknown>>(
50
50
  * `heartbeat()` remains a no-op stub (v6.1); it is a liveness / TTL-reset
51
51
  * signal for operator infrastructure, not buyer-facing.
52
52
  */
53
- export declare function buildHandoffContext(taskRegistry: TaskRegistry, taskId: string): TaskHandoffContext;
53
+ export declare function buildHandoffContext(taskRegistry: TaskRegistry, taskId: string, scope: TaskRegistryScope): TaskHandoffContext;
@@ -28,7 +28,7 @@
28
28
  */
29
29
  import type { Account } from '../account';
30
30
  import type { RequestContext } from '../context';
31
- import type { TaskRegistry } from './task-registry';
31
+ import type { TaskRegistry, TaskRegistryScope } from './task-registry';
32
32
  import { type TaskHandoffContext } from '../async-outcome';
33
33
  import type { CtxMetadataStore } from '../../ctx-metadata';
34
34
  export declare function buildRequestContext<TCtxMeta = Record<string, unknown>>(handlerCtx: HandlerContext<Account<TCtxMeta>>, ctxMetadataStore?: CtxMetadataStore, input?: Readonly<Record<string, unknown>>): RequestContext<Account<TCtxMeta>>;
@@ -50,5 +50,5 @@ export declare function buildRequestContext<TCtxMeta = Record<string, unknown>>(
50
50
  * `heartbeat()` remains a no-op stub (v6.1); it is a liveness / TTL-reset
51
51
  * signal for operator infrastructure, not buyer-facing.
52
52
  */
53
- export declare function buildHandoffContext(taskRegistry: TaskRegistry, taskId: string): TaskHandoffContext;
53
+ export declare function buildHandoffContext(taskRegistry: TaskRegistry, taskId: string, scope: TaskRegistryScope): TaskHandoffContext;
54
54
  //# sourceMappingURL=to-context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"to-context.d.ts","sourceRoot":"","sources":["../../../../../src/lib/server/decisioning/runtime/to-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,KAAK,EAAE,cAAc,EAAuB,MAAM,YAAY,CAAC;AACtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAEL,KAAK,kBAAkB,EAGxB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,gBAAgB,EAAgC,MAAM,oBAAoB,CAAC;AAgDzF,wBAAgB,mBAAmB,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpE,UAAU,EAAE,cAAc,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAC7C,gBAAgB,CAAC,EAAE,gBAAgB,EACnC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACxC,cAAc,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CA2EnC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,kBAAkB,CAgBlG"}
1
+ {"version":3,"file":"to-context.d.ts","sourceRoot":"","sources":["../../../../../src/lib/server/decisioning/runtime/to-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,KAAK,EAAE,cAAc,EAAuB,MAAM,YAAY,CAAC;AACtE,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACvE,OAAO,EAEL,KAAK,kBAAkB,EAGxB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,gBAAgB,EAAgC,MAAM,oBAAoB,CAAC;AAgDzF,wBAAgB,mBAAmB,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpE,UAAU,EAAE,cAAc,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAC7C,gBAAgB,CAAC,EAAE,gBAAgB,EACnC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACxC,cAAc,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CA2EnC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,iBAAiB,GACvB,kBAAkB,CAgBpB"}
@@ -103,12 +103,12 @@ function buildRequestContext(handlerCtx, ctxMetadataStore, input) {
103
103
  }
104
104
  };
105
105
  }
106
- function buildHandoffContext(taskRegistry, taskId) {
106
+ function buildHandoffContext(taskRegistry, taskId, scope) {
107
107
  return {
108
108
  id: taskId,
109
109
  update: async (progress) => {
110
110
  try {
111
- await taskRegistry.updateProgress(taskId, progress);
111
+ await taskRegistry.updateProgress(taskId, scope, progress);
112
112
  } catch {
113
113
  }
114
114
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/lib/server/decisioning/runtime/to-context.ts"],"sourcesContent":["/**\n * Translate the existing `HandlerContext` into the v6 `RequestContext` shape\n * that platform methods receive.\n *\n * The handler-style framework already resolves the account, sets sessionKey,\n * and exposes `store` + `authInfo` + `emitWebhook`. The new context layers\n * `state.*` (sync state reads), `resolve.*` (async framework-mediated\n * resolvers), and `handoffToTask(...)` (the unified hybrid-seller handoff\n * primitive) on top.\n *\n * **Stub status — v6.0 alpha.** `state.*` (workflow-step reads, proposal\n * lookups, governance JWS) and `resolve.*` (property/collection-list +\n * format fetchers) are NOT yet wired. The state readers return empty\n * results; the resolvers throw. Touching them in a platform method will\n * crash the request — the framework hasn't connected them to an underlying\n * store / fetch layer yet.\n *\n * Adopters spiking against the preview surface MUST avoid `ctx.state.*`\n * and `ctx.resolve.*` until the wire-up commits land in rc.1. Use\n * `ctx.account`, `ctx.handoffToTask(...)`, and the structured-error /\n * status-change primitives only.\n *\n * @internal — framework-internal wiring; not adopter surface. The\n * exported helpers (`buildRequestContext`, `buildHandoffContext`) are\n * called from the dispatch seam in `from-platform.ts`. Adopters should\n * never construct a `RequestContext` themselves; the framework supplies\n * one to every specialism method call.\n */\n\nimport type { HandlerContext } from '../../create-adcp-server';\nimport type { Account } from '../account';\nimport type { RequestContext, CtxMetadataAccessor } from '../context';\nimport type { TaskRegistry } from './task-registry';\nimport {\n _createTaskHandoff,\n type TaskHandoffContext,\n type TaskHandoff,\n type TaskHandoffOptions,\n} from '../async-outcome';\nimport type { CtxMetadataStore, ResourceKind, CtxMetadataRef } from '../../ctx-metadata';\n\n/**\n * Build an account-scoped CtxMetadataAccessor for a single request.\n *\n * Account scope comes from `ctx.account.id` — accessor methods don't take\n * an account param. When `account.id` is null/undefined (no-account tools),\n * the accessor methods reject — no-account tools cannot use ctx_metadata\n * (cross-tenant collision risk via missing scope).\n */\nfunction buildCtxMetadataAccessor(store: CtxMetadataStore, accountId: string): CtxMetadataAccessor {\n return {\n get(kind: ResourceKind, id: string) {\n return store.get(accountId, kind, id);\n },\n bulkGet(refs: readonly CtxMetadataRef[]) {\n return store.bulkGet(accountId, refs);\n },\n set(kind: ResourceKind, id: string, value: unknown, ttlSeconds?: number) {\n return store.set(accountId, kind, id, value, ttlSeconds);\n },\n delete(kind: ResourceKind, id: string) {\n return store.delete(accountId, kind, id);\n },\n account(id: string) {\n return store.get(accountId, 'account', id);\n },\n product(id: string) {\n return store.get(accountId, 'product', id);\n },\n mediaBuy(id: string) {\n return store.get(accountId, 'media_buy', id);\n },\n package(id: string) {\n return store.get(accountId, 'package', id);\n },\n creative(id: string) {\n return store.get(accountId, 'creative', id);\n },\n audience(id: string) {\n return store.get(accountId, 'audience', id);\n },\n signal(id: string) {\n return store.get(accountId, 'signal', id);\n },\n };\n}\n\nexport function buildRequestContext<TCtxMeta = Record<string, unknown>>(\n handlerCtx: HandlerContext<Account<TCtxMeta>>,\n ctxMetadataStore?: CtxMetadataStore,\n input?: Readonly<Record<string, unknown>>\n): RequestContext<Account<TCtxMeta>> {\n // `account` may legitimately be undefined for tools whose wire request\n // doesn't carry an `account` field AND whose `resolveAccountFromAuth`\n // returned null (`'explicit'`-mode adopters who don't model the\n // no-account tools, or buyers calling without auth). Adopter handlers\n // for those tools are responsible for either deriving the account\n // themselves (e.g., via `media_buy_id` ownership) or throwing\n // `AdcpError('ACCOUNT_NOT_FOUND')` if account is required.\n //\n // The `RequestContext.account` type is non-optional for ergonomic typing\n // — adopters writing handlers for the 90% case (tools with `account` on\n // the wire) shouldn't have to optional-chain everywhere. Adopters of\n // no-account tools either:\n // 1. Declare `resolution: 'derived'` and return a singleton from\n // `accounts.resolve(undefined)` — `ctx.account` is always set\n // 2. Implement only `'explicit'` and never claim no-account\n // specialisms — the tool is unreachable\n // 3. Read `ctx.account` defensively (`as Account | undefined` cast)\n // and look up by request body when missing\n const account = handlerCtx.account as Account<TCtxMeta>;\n\n const stubResolver = (name: string) => async (): Promise<never> => {\n throw new Error(\n `ctx.resolve.${name}: not yet wired in v6.0 alpha — landing in rc.1. ` +\n `Avoid touching ctx.resolve.* in adopter code until the framework ` +\n `connects this resolver to an underlying fetcher.`\n );\n };\n\n // Bind ctx-metadata accessor when store wired AND account scope present.\n // No-account tools (provide_performance_feedback, list_creative_formats)\n // get `ctx.ctxMetadata = undefined` even when the store is wired — cannot\n // use ctx_metadata without an account boundary (cross-tenant risk).\n const ctxMetadata =\n ctxMetadataStore != null && account != null && (account.id ?? '') !== ''\n ? buildCtxMetadataAccessor(ctxMetadataStore, account.id)\n : undefined;\n\n return {\n account,\n ...(handlerCtx.agent != null && { agent: handlerCtx.agent }),\n ...(handlerCtx.callerMutationScope != null && {\n callerMutationScope: Object.freeze({ ...handlerCtx.callerMutationScope }),\n }),\n ...(handlerCtx.proposalRefinementScope != null && {\n proposalRefinementScope: Object.freeze({ ...handlerCtx.proposalRefinementScope }),\n }),\n ...(input != null && { input }),\n state: {\n findByObject: () => [],\n findProposalById: () => null,\n governanceContext: () => null,\n workflowSteps: () => [],\n },\n resolve: {\n propertyList: stubResolver('propertyList'),\n collectionList: stubResolver('collectionList'),\n creativeFormat: stubResolver('creativeFormat'),\n },\n ctxMetadata,\n handoffToTask<TResult>(\n fn: (taskCtx: TaskHandoffContext) => Promise<TResult>,\n options?: TaskHandoffOptions\n ): TaskHandoff<TResult> {\n if (options?.task_id !== undefined) {\n if (typeof options.task_id !== 'string' || options.task_id.length === 0) {\n throw new Error('handoffToTask options.task_id must be a non-empty string');\n }\n if (options.task_id.length > 128) {\n throw new Error('handoffToTask options.task_id must be ≤ 128 characters');\n }\n }\n return _createTaskHandoff(fn, options);\n },\n };\n}\n\n/**\n * Construct a `TaskHandoffContext` from a registry + framework-issued\n * task id. The framework calls this AFTER detecting a `TaskHandoff`\n * marker on a method's return — the handoff function gets a context\n * carrying the framework-allocated `taskId` plus `update`/`heartbeat`\n * affordances.\n *\n * `update(progress)` writes the progress payload to the task record and\n * transitions status `submitted` → `working`. Buyers polling `tasks_get`\n * see the `progress` object and the `'working'` status — this is the\n * buyer-facing UX signal that distinguishes \"stuck/no news\" from\n * \"step 2/3, awaiting trafficker.\" Errors from the registry write are\n * swallowed so a transient DB hiccup doesn't abort the adopter's handoff\n * function.\n *\n * `heartbeat()` remains a no-op stub (v6.1); it is a liveness / TTL-reset\n * signal for operator infrastructure, not buyer-facing.\n */\nexport function buildHandoffContext(taskRegistry: TaskRegistry, taskId: string): TaskHandoffContext {\n return {\n id: taskId,\n update: async progress => {\n try {\n await taskRegistry.updateProgress(taskId, progress);\n } catch {\n // Swallow — a transient registry write failure must not abort the\n // adopter's background handoff function. The buyer-facing impact is\n // a missed progress event, not a failed task.\n }\n },\n heartbeat: async () => {\n await Promise.resolve();\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../../src/lib/server/decisioning/runtime/to-context.ts"],"sourcesContent":["/**\n * Translate the existing `HandlerContext` into the v6 `RequestContext` shape\n * that platform methods receive.\n *\n * The handler-style framework already resolves the account, sets sessionKey,\n * and exposes `store` + `authInfo` + `emitWebhook`. The new context layers\n * `state.*` (sync state reads), `resolve.*` (async framework-mediated\n * resolvers), and `handoffToTask(...)` (the unified hybrid-seller handoff\n * primitive) on top.\n *\n * **Stub status — v6.0 alpha.** `state.*` (workflow-step reads, proposal\n * lookups, governance JWS) and `resolve.*` (property/collection-list +\n * format fetchers) are NOT yet wired. The state readers return empty\n * results; the resolvers throw. Touching them in a platform method will\n * crash the request — the framework hasn't connected them to an underlying\n * store / fetch layer yet.\n *\n * Adopters spiking against the preview surface MUST avoid `ctx.state.*`\n * and `ctx.resolve.*` until the wire-up commits land in rc.1. Use\n * `ctx.account`, `ctx.handoffToTask(...)`, and the structured-error /\n * status-change primitives only.\n *\n * @internal — framework-internal wiring; not adopter surface. The\n * exported helpers (`buildRequestContext`, `buildHandoffContext`) are\n * called from the dispatch seam in `from-platform.ts`. Adopters should\n * never construct a `RequestContext` themselves; the framework supplies\n * one to every specialism method call.\n */\n\nimport type { HandlerContext } from '../../create-adcp-server';\nimport type { Account } from '../account';\nimport type { RequestContext, CtxMetadataAccessor } from '../context';\nimport type { TaskRegistry, TaskRegistryScope } from './task-registry';\nimport {\n _createTaskHandoff,\n type TaskHandoffContext,\n type TaskHandoff,\n type TaskHandoffOptions,\n} from '../async-outcome';\nimport type { CtxMetadataStore, ResourceKind, CtxMetadataRef } from '../../ctx-metadata';\n\n/**\n * Build an account-scoped CtxMetadataAccessor for a single request.\n *\n * Account scope comes from `ctx.account.id` — accessor methods don't take\n * an account param. When `account.id` is null/undefined (no-account tools),\n * the accessor methods reject — no-account tools cannot use ctx_metadata\n * (cross-tenant collision risk via missing scope).\n */\nfunction buildCtxMetadataAccessor(store: CtxMetadataStore, accountId: string): CtxMetadataAccessor {\n return {\n get(kind: ResourceKind, id: string) {\n return store.get(accountId, kind, id);\n },\n bulkGet(refs: readonly CtxMetadataRef[]) {\n return store.bulkGet(accountId, refs);\n },\n set(kind: ResourceKind, id: string, value: unknown, ttlSeconds?: number) {\n return store.set(accountId, kind, id, value, ttlSeconds);\n },\n delete(kind: ResourceKind, id: string) {\n return store.delete(accountId, kind, id);\n },\n account(id: string) {\n return store.get(accountId, 'account', id);\n },\n product(id: string) {\n return store.get(accountId, 'product', id);\n },\n mediaBuy(id: string) {\n return store.get(accountId, 'media_buy', id);\n },\n package(id: string) {\n return store.get(accountId, 'package', id);\n },\n creative(id: string) {\n return store.get(accountId, 'creative', id);\n },\n audience(id: string) {\n return store.get(accountId, 'audience', id);\n },\n signal(id: string) {\n return store.get(accountId, 'signal', id);\n },\n };\n}\n\nexport function buildRequestContext<TCtxMeta = Record<string, unknown>>(\n handlerCtx: HandlerContext<Account<TCtxMeta>>,\n ctxMetadataStore?: CtxMetadataStore,\n input?: Readonly<Record<string, unknown>>\n): RequestContext<Account<TCtxMeta>> {\n // `account` may legitimately be undefined for tools whose wire request\n // doesn't carry an `account` field AND whose `resolveAccountFromAuth`\n // returned null (`'explicit'`-mode adopters who don't model the\n // no-account tools, or buyers calling without auth). Adopter handlers\n // for those tools are responsible for either deriving the account\n // themselves (e.g., via `media_buy_id` ownership) or throwing\n // `AdcpError('ACCOUNT_NOT_FOUND')` if account is required.\n //\n // The `RequestContext.account` type is non-optional for ergonomic typing\n // — adopters writing handlers for the 90% case (tools with `account` on\n // the wire) shouldn't have to optional-chain everywhere. Adopters of\n // no-account tools either:\n // 1. Declare `resolution: 'derived'` and return a singleton from\n // `accounts.resolve(undefined)` — `ctx.account` is always set\n // 2. Implement only `'explicit'` and never claim no-account\n // specialisms — the tool is unreachable\n // 3. Read `ctx.account` defensively (`as Account | undefined` cast)\n // and look up by request body when missing\n const account = handlerCtx.account as Account<TCtxMeta>;\n\n const stubResolver = (name: string) => async (): Promise<never> => {\n throw new Error(\n `ctx.resolve.${name}: not yet wired in v6.0 alpha — landing in rc.1. ` +\n `Avoid touching ctx.resolve.* in adopter code until the framework ` +\n `connects this resolver to an underlying fetcher.`\n );\n };\n\n // Bind ctx-metadata accessor when store wired AND account scope present.\n // No-account tools (provide_performance_feedback, list_creative_formats)\n // get `ctx.ctxMetadata = undefined` even when the store is wired — cannot\n // use ctx_metadata without an account boundary (cross-tenant risk).\n const ctxMetadata =\n ctxMetadataStore != null && account != null && (account.id ?? '') !== ''\n ? buildCtxMetadataAccessor(ctxMetadataStore, account.id)\n : undefined;\n\n return {\n account,\n ...(handlerCtx.agent != null && { agent: handlerCtx.agent }),\n ...(handlerCtx.callerMutationScope != null && {\n callerMutationScope: Object.freeze({ ...handlerCtx.callerMutationScope }),\n }),\n ...(handlerCtx.proposalRefinementScope != null && {\n proposalRefinementScope: Object.freeze({ ...handlerCtx.proposalRefinementScope }),\n }),\n ...(input != null && { input }),\n state: {\n findByObject: () => [],\n findProposalById: () => null,\n governanceContext: () => null,\n workflowSteps: () => [],\n },\n resolve: {\n propertyList: stubResolver('propertyList'),\n collectionList: stubResolver('collectionList'),\n creativeFormat: stubResolver('creativeFormat'),\n },\n ctxMetadata,\n handoffToTask<TResult>(\n fn: (taskCtx: TaskHandoffContext) => Promise<TResult>,\n options?: TaskHandoffOptions\n ): TaskHandoff<TResult> {\n if (options?.task_id !== undefined) {\n if (typeof options.task_id !== 'string' || options.task_id.length === 0) {\n throw new Error('handoffToTask options.task_id must be a non-empty string');\n }\n if (options.task_id.length > 128) {\n throw new Error('handoffToTask options.task_id must be ≤ 128 characters');\n }\n }\n return _createTaskHandoff(fn, options);\n },\n };\n}\n\n/**\n * Construct a `TaskHandoffContext` from a registry + framework-issued\n * task id. The framework calls this AFTER detecting a `TaskHandoff`\n * marker on a method's return — the handoff function gets a context\n * carrying the framework-allocated `taskId` plus `update`/`heartbeat`\n * affordances.\n *\n * `update(progress)` writes the progress payload to the task record and\n * transitions status `submitted` → `working`. Buyers polling `tasks_get`\n * see the `progress` object and the `'working'` status — this is the\n * buyer-facing UX signal that distinguishes \"stuck/no news\" from\n * \"step 2/3, awaiting trafficker.\" Errors from the registry write are\n * swallowed so a transient DB hiccup doesn't abort the adopter's handoff\n * function.\n *\n * `heartbeat()` remains a no-op stub (v6.1); it is a liveness / TTL-reset\n * signal for operator infrastructure, not buyer-facing.\n */\nexport function buildHandoffContext(\n taskRegistry: TaskRegistry,\n taskId: string,\n scope: TaskRegistryScope\n): TaskHandoffContext {\n return {\n id: taskId,\n update: async progress => {\n try {\n await taskRegistry.updateProgress(taskId, scope, progress);\n } catch {\n // Swallow — a transient registry write failure must not abort the\n // adopter's background handoff function. The buyer-facing impact is\n // a missed progress event, not a failed task.\n }\n },\n heartbeat: async () => {\n await Promise.resolve();\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -81,12 +81,12 @@ function buildRequestContext(handlerCtx, ctxMetadataStore, input) {
81
81
  }
82
82
  };
83
83
  }
84
- function buildHandoffContext(taskRegistry, taskId) {
84
+ function buildHandoffContext(taskRegistry, taskId, scope) {
85
85
  return {
86
86
  id: taskId,
87
87
  update: async (progress) => {
88
88
  try {
89
- await taskRegistry.updateProgress(taskId, progress);
89
+ await taskRegistry.updateProgress(taskId, scope, progress);
90
90
  } catch {
91
91
  }
92
92
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/lib/server/decisioning/runtime/to-context.ts"],"sourcesContent":["/**\n * Translate the existing `HandlerContext` into the v6 `RequestContext` shape\n * that platform methods receive.\n *\n * The handler-style framework already resolves the account, sets sessionKey,\n * and exposes `store` + `authInfo` + `emitWebhook`. The new context layers\n * `state.*` (sync state reads), `resolve.*` (async framework-mediated\n * resolvers), and `handoffToTask(...)` (the unified hybrid-seller handoff\n * primitive) on top.\n *\n * **Stub status — v6.0 alpha.** `state.*` (workflow-step reads, proposal\n * lookups, governance JWS) and `resolve.*` (property/collection-list +\n * format fetchers) are NOT yet wired. The state readers return empty\n * results; the resolvers throw. Touching them in a platform method will\n * crash the request — the framework hasn't connected them to an underlying\n * store / fetch layer yet.\n *\n * Adopters spiking against the preview surface MUST avoid `ctx.state.*`\n * and `ctx.resolve.*` until the wire-up commits land in rc.1. Use\n * `ctx.account`, `ctx.handoffToTask(...)`, and the structured-error /\n * status-change primitives only.\n *\n * @internal — framework-internal wiring; not adopter surface. The\n * exported helpers (`buildRequestContext`, `buildHandoffContext`) are\n * called from the dispatch seam in `from-platform.ts`. Adopters should\n * never construct a `RequestContext` themselves; the framework supplies\n * one to every specialism method call.\n */\n\nimport type { HandlerContext } from '../../create-adcp-server';\nimport type { Account } from '../account';\nimport type { RequestContext, CtxMetadataAccessor } from '../context';\nimport type { TaskRegistry } from './task-registry';\nimport {\n _createTaskHandoff,\n type TaskHandoffContext,\n type TaskHandoff,\n type TaskHandoffOptions,\n} from '../async-outcome';\nimport type { CtxMetadataStore, ResourceKind, CtxMetadataRef } from '../../ctx-metadata';\n\n/**\n * Build an account-scoped CtxMetadataAccessor for a single request.\n *\n * Account scope comes from `ctx.account.id` — accessor methods don't take\n * an account param. When `account.id` is null/undefined (no-account tools),\n * the accessor methods reject — no-account tools cannot use ctx_metadata\n * (cross-tenant collision risk via missing scope).\n */\nfunction buildCtxMetadataAccessor(store: CtxMetadataStore, accountId: string): CtxMetadataAccessor {\n return {\n get(kind: ResourceKind, id: string) {\n return store.get(accountId, kind, id);\n },\n bulkGet(refs: readonly CtxMetadataRef[]) {\n return store.bulkGet(accountId, refs);\n },\n set(kind: ResourceKind, id: string, value: unknown, ttlSeconds?: number) {\n return store.set(accountId, kind, id, value, ttlSeconds);\n },\n delete(kind: ResourceKind, id: string) {\n return store.delete(accountId, kind, id);\n },\n account(id: string) {\n return store.get(accountId, 'account', id);\n },\n product(id: string) {\n return store.get(accountId, 'product', id);\n },\n mediaBuy(id: string) {\n return store.get(accountId, 'media_buy', id);\n },\n package(id: string) {\n return store.get(accountId, 'package', id);\n },\n creative(id: string) {\n return store.get(accountId, 'creative', id);\n },\n audience(id: string) {\n return store.get(accountId, 'audience', id);\n },\n signal(id: string) {\n return store.get(accountId, 'signal', id);\n },\n };\n}\n\nexport function buildRequestContext<TCtxMeta = Record<string, unknown>>(\n handlerCtx: HandlerContext<Account<TCtxMeta>>,\n ctxMetadataStore?: CtxMetadataStore,\n input?: Readonly<Record<string, unknown>>\n): RequestContext<Account<TCtxMeta>> {\n // `account` may legitimately be undefined for tools whose wire request\n // doesn't carry an `account` field AND whose `resolveAccountFromAuth`\n // returned null (`'explicit'`-mode adopters who don't model the\n // no-account tools, or buyers calling without auth). Adopter handlers\n // for those tools are responsible for either deriving the account\n // themselves (e.g., via `media_buy_id` ownership) or throwing\n // `AdcpError('ACCOUNT_NOT_FOUND')` if account is required.\n //\n // The `RequestContext.account` type is non-optional for ergonomic typing\n // — adopters writing handlers for the 90% case (tools with `account` on\n // the wire) shouldn't have to optional-chain everywhere. Adopters of\n // no-account tools either:\n // 1. Declare `resolution: 'derived'` and return a singleton from\n // `accounts.resolve(undefined)` — `ctx.account` is always set\n // 2. Implement only `'explicit'` and never claim no-account\n // specialisms — the tool is unreachable\n // 3. Read `ctx.account` defensively (`as Account | undefined` cast)\n // and look up by request body when missing\n const account = handlerCtx.account as Account<TCtxMeta>;\n\n const stubResolver = (name: string) => async (): Promise<never> => {\n throw new Error(\n `ctx.resolve.${name}: not yet wired in v6.0 alpha — landing in rc.1. ` +\n `Avoid touching ctx.resolve.* in adopter code until the framework ` +\n `connects this resolver to an underlying fetcher.`\n );\n };\n\n // Bind ctx-metadata accessor when store wired AND account scope present.\n // No-account tools (provide_performance_feedback, list_creative_formats)\n // get `ctx.ctxMetadata = undefined` even when the store is wired — cannot\n // use ctx_metadata without an account boundary (cross-tenant risk).\n const ctxMetadata =\n ctxMetadataStore != null && account != null && (account.id ?? '') !== ''\n ? buildCtxMetadataAccessor(ctxMetadataStore, account.id)\n : undefined;\n\n return {\n account,\n ...(handlerCtx.agent != null && { agent: handlerCtx.agent }),\n ...(handlerCtx.callerMutationScope != null && {\n callerMutationScope: Object.freeze({ ...handlerCtx.callerMutationScope }),\n }),\n ...(handlerCtx.proposalRefinementScope != null && {\n proposalRefinementScope: Object.freeze({ ...handlerCtx.proposalRefinementScope }),\n }),\n ...(input != null && { input }),\n state: {\n findByObject: () => [],\n findProposalById: () => null,\n governanceContext: () => null,\n workflowSteps: () => [],\n },\n resolve: {\n propertyList: stubResolver('propertyList'),\n collectionList: stubResolver('collectionList'),\n creativeFormat: stubResolver('creativeFormat'),\n },\n ctxMetadata,\n handoffToTask<TResult>(\n fn: (taskCtx: TaskHandoffContext) => Promise<TResult>,\n options?: TaskHandoffOptions\n ): TaskHandoff<TResult> {\n if (options?.task_id !== undefined) {\n if (typeof options.task_id !== 'string' || options.task_id.length === 0) {\n throw new Error('handoffToTask options.task_id must be a non-empty string');\n }\n if (options.task_id.length > 128) {\n throw new Error('handoffToTask options.task_id must be ≤ 128 characters');\n }\n }\n return _createTaskHandoff(fn, options);\n },\n };\n}\n\n/**\n * Construct a `TaskHandoffContext` from a registry + framework-issued\n * task id. The framework calls this AFTER detecting a `TaskHandoff`\n * marker on a method's return — the handoff function gets a context\n * carrying the framework-allocated `taskId` plus `update`/`heartbeat`\n * affordances.\n *\n * `update(progress)` writes the progress payload to the task record and\n * transitions status `submitted` → `working`. Buyers polling `tasks_get`\n * see the `progress` object and the `'working'` status — this is the\n * buyer-facing UX signal that distinguishes \"stuck/no news\" from\n * \"step 2/3, awaiting trafficker.\" Errors from the registry write are\n * swallowed so a transient DB hiccup doesn't abort the adopter's handoff\n * function.\n *\n * `heartbeat()` remains a no-op stub (v6.1); it is a liveness / TTL-reset\n * signal for operator infrastructure, not buyer-facing.\n */\nexport function buildHandoffContext(taskRegistry: TaskRegistry, taskId: string): TaskHandoffContext {\n return {\n id: taskId,\n update: async progress => {\n try {\n await taskRegistry.updateProgress(taskId, progress);\n } catch {\n // Swallow — a transient registry write failure must not abort the\n // adopter's background handoff function. The buyer-facing impact is\n // a missed progress event, not a failed task.\n }\n },\n heartbeat: async () => {\n await Promise.resolve();\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../../src/lib/server/decisioning/runtime/to-context.ts"],"sourcesContent":["/**\n * Translate the existing `HandlerContext` into the v6 `RequestContext` shape\n * that platform methods receive.\n *\n * The handler-style framework already resolves the account, sets sessionKey,\n * and exposes `store` + `authInfo` + `emitWebhook`. The new context layers\n * `state.*` (sync state reads), `resolve.*` (async framework-mediated\n * resolvers), and `handoffToTask(...)` (the unified hybrid-seller handoff\n * primitive) on top.\n *\n * **Stub status — v6.0 alpha.** `state.*` (workflow-step reads, proposal\n * lookups, governance JWS) and `resolve.*` (property/collection-list +\n * format fetchers) are NOT yet wired. The state readers return empty\n * results; the resolvers throw. Touching them in a platform method will\n * crash the request — the framework hasn't connected them to an underlying\n * store / fetch layer yet.\n *\n * Adopters spiking against the preview surface MUST avoid `ctx.state.*`\n * and `ctx.resolve.*` until the wire-up commits land in rc.1. Use\n * `ctx.account`, `ctx.handoffToTask(...)`, and the structured-error /\n * status-change primitives only.\n *\n * @internal — framework-internal wiring; not adopter surface. The\n * exported helpers (`buildRequestContext`, `buildHandoffContext`) are\n * called from the dispatch seam in `from-platform.ts`. Adopters should\n * never construct a `RequestContext` themselves; the framework supplies\n * one to every specialism method call.\n */\n\nimport type { HandlerContext } from '../../create-adcp-server';\nimport type { Account } from '../account';\nimport type { RequestContext, CtxMetadataAccessor } from '../context';\nimport type { TaskRegistry, TaskRegistryScope } from './task-registry';\nimport {\n _createTaskHandoff,\n type TaskHandoffContext,\n type TaskHandoff,\n type TaskHandoffOptions,\n} from '../async-outcome';\nimport type { CtxMetadataStore, ResourceKind, CtxMetadataRef } from '../../ctx-metadata';\n\n/**\n * Build an account-scoped CtxMetadataAccessor for a single request.\n *\n * Account scope comes from `ctx.account.id` — accessor methods don't take\n * an account param. When `account.id` is null/undefined (no-account tools),\n * the accessor methods reject — no-account tools cannot use ctx_metadata\n * (cross-tenant collision risk via missing scope).\n */\nfunction buildCtxMetadataAccessor(store: CtxMetadataStore, accountId: string): CtxMetadataAccessor {\n return {\n get(kind: ResourceKind, id: string) {\n return store.get(accountId, kind, id);\n },\n bulkGet(refs: readonly CtxMetadataRef[]) {\n return store.bulkGet(accountId, refs);\n },\n set(kind: ResourceKind, id: string, value: unknown, ttlSeconds?: number) {\n return store.set(accountId, kind, id, value, ttlSeconds);\n },\n delete(kind: ResourceKind, id: string) {\n return store.delete(accountId, kind, id);\n },\n account(id: string) {\n return store.get(accountId, 'account', id);\n },\n product(id: string) {\n return store.get(accountId, 'product', id);\n },\n mediaBuy(id: string) {\n return store.get(accountId, 'media_buy', id);\n },\n package(id: string) {\n return store.get(accountId, 'package', id);\n },\n creative(id: string) {\n return store.get(accountId, 'creative', id);\n },\n audience(id: string) {\n return store.get(accountId, 'audience', id);\n },\n signal(id: string) {\n return store.get(accountId, 'signal', id);\n },\n };\n}\n\nexport function buildRequestContext<TCtxMeta = Record<string, unknown>>(\n handlerCtx: HandlerContext<Account<TCtxMeta>>,\n ctxMetadataStore?: CtxMetadataStore,\n input?: Readonly<Record<string, unknown>>\n): RequestContext<Account<TCtxMeta>> {\n // `account` may legitimately be undefined for tools whose wire request\n // doesn't carry an `account` field AND whose `resolveAccountFromAuth`\n // returned null (`'explicit'`-mode adopters who don't model the\n // no-account tools, or buyers calling without auth). Adopter handlers\n // for those tools are responsible for either deriving the account\n // themselves (e.g., via `media_buy_id` ownership) or throwing\n // `AdcpError('ACCOUNT_NOT_FOUND')` if account is required.\n //\n // The `RequestContext.account` type is non-optional for ergonomic typing\n // — adopters writing handlers for the 90% case (tools with `account` on\n // the wire) shouldn't have to optional-chain everywhere. Adopters of\n // no-account tools either:\n // 1. Declare `resolution: 'derived'` and return a singleton from\n // `accounts.resolve(undefined)` — `ctx.account` is always set\n // 2. Implement only `'explicit'` and never claim no-account\n // specialisms — the tool is unreachable\n // 3. Read `ctx.account` defensively (`as Account | undefined` cast)\n // and look up by request body when missing\n const account = handlerCtx.account as Account<TCtxMeta>;\n\n const stubResolver = (name: string) => async (): Promise<never> => {\n throw new Error(\n `ctx.resolve.${name}: not yet wired in v6.0 alpha — landing in rc.1. ` +\n `Avoid touching ctx.resolve.* in adopter code until the framework ` +\n `connects this resolver to an underlying fetcher.`\n );\n };\n\n // Bind ctx-metadata accessor when store wired AND account scope present.\n // No-account tools (provide_performance_feedback, list_creative_formats)\n // get `ctx.ctxMetadata = undefined` even when the store is wired — cannot\n // use ctx_metadata without an account boundary (cross-tenant risk).\n const ctxMetadata =\n ctxMetadataStore != null && account != null && (account.id ?? '') !== ''\n ? buildCtxMetadataAccessor(ctxMetadataStore, account.id)\n : undefined;\n\n return {\n account,\n ...(handlerCtx.agent != null && { agent: handlerCtx.agent }),\n ...(handlerCtx.callerMutationScope != null && {\n callerMutationScope: Object.freeze({ ...handlerCtx.callerMutationScope }),\n }),\n ...(handlerCtx.proposalRefinementScope != null && {\n proposalRefinementScope: Object.freeze({ ...handlerCtx.proposalRefinementScope }),\n }),\n ...(input != null && { input }),\n state: {\n findByObject: () => [],\n findProposalById: () => null,\n governanceContext: () => null,\n workflowSteps: () => [],\n },\n resolve: {\n propertyList: stubResolver('propertyList'),\n collectionList: stubResolver('collectionList'),\n creativeFormat: stubResolver('creativeFormat'),\n },\n ctxMetadata,\n handoffToTask<TResult>(\n fn: (taskCtx: TaskHandoffContext) => Promise<TResult>,\n options?: TaskHandoffOptions\n ): TaskHandoff<TResult> {\n if (options?.task_id !== undefined) {\n if (typeof options.task_id !== 'string' || options.task_id.length === 0) {\n throw new Error('handoffToTask options.task_id must be a non-empty string');\n }\n if (options.task_id.length > 128) {\n throw new Error('handoffToTask options.task_id must be ≤ 128 characters');\n }\n }\n return _createTaskHandoff(fn, options);\n },\n };\n}\n\n/**\n * Construct a `TaskHandoffContext` from a registry + framework-issued\n * task id. The framework calls this AFTER detecting a `TaskHandoff`\n * marker on a method's return — the handoff function gets a context\n * carrying the framework-allocated `taskId` plus `update`/`heartbeat`\n * affordances.\n *\n * `update(progress)` writes the progress payload to the task record and\n * transitions status `submitted` → `working`. Buyers polling `tasks_get`\n * see the `progress` object and the `'working'` status — this is the\n * buyer-facing UX signal that distinguishes \"stuck/no news\" from\n * \"step 2/3, awaiting trafficker.\" Errors from the registry write are\n * swallowed so a transient DB hiccup doesn't abort the adopter's handoff\n * function.\n *\n * `heartbeat()` remains a no-op stub (v6.1); it is a liveness / TTL-reset\n * signal for operator infrastructure, not buyer-facing.\n */\nexport function buildHandoffContext(\n taskRegistry: TaskRegistry,\n taskId: string,\n scope: TaskRegistryScope\n): TaskHandoffContext {\n return {\n id: taskId,\n update: async progress => {\n try {\n await taskRegistry.updateProgress(taskId, scope, progress);\n } catch {\n // Swallow — a transient registry write failure must not abort the\n // adopter's background handoff function. The buyer-facing impact is\n // a missed progress event, not a failed task.\n }\n },\n heartbeat: async () => {\n await Promise.resolve();\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -27,7 +27,7 @@ import type { Account, NoAccountCtx } from '../account.mjs';
27
27
  import type { RequestContext } from '../context.mjs';
28
28
  import type { TaskHandoff } from '../async-outcome.mjs';
29
29
  import type { ServerPayload } from '../../../types/server-payload.mjs';
30
- import type { BuildCreativeRequest, CreativeManifest, PreviewCreativeRequest as LegacyPreviewCreativeRequest, PreviewCreativeResponse as LegacyPreviewCreativeResponse, ListCreativeFormatsRequest, ListCreativeFormatsResponse, GetCreativeDeliveryRequest, GetCreativeDeliveryResponse, BuildCreativeSuccess, BuildCreativeMultiSuccess } from '../../../types/tools.generated.mjs';
30
+ import type { BuildCreativeRequest, CreativeManifest, PreviewCreativeRequest as LegacyPreviewCreativeRequest, PreviewCreativeResponse as LegacyPreviewCreativeResponse, ListCreativeFormatsRequest, ListCreativeFormatsResponse, GetCreativeDeliveryRequest, GetCreativeDeliveryResponse, BuildCreativeSuccess, BuildCreativeMultiSuccess, BuildCreativeVariantSuccess } from '../../../types/tools.generated.mjs';
31
31
  import type { CanonicalSyncCreativeAsset, CanonicalCreativeResponse, CanonicalListCreativesRequest, CanonicalListCreativesResponse, CanonicalPreviewCreativeRequest, CanonicalPreviewCreativeResponse } from '../../../v2/projection/creative-delivery.mjs';
32
32
  import type { SyncCreativesRow } from './sales.mjs';
33
33
  type SyncCreative = CanonicalSyncCreativeAsset;
@@ -40,7 +40,8 @@ export type GetCreativeDeliveryPayload = ServerPayload<CanonicalCreativeResponse
40
40
  export type LegacyGetCreativeDeliveryPayload = ServerPayload<GetCreativeDeliveryResponse>;
41
41
  export type LegacyBuildCreativePayload = ServerPayload<BuildCreativeSuccess>;
42
42
  export type LegacyBuildCreativeMultiPayload = ServerPayload<BuildCreativeMultiSuccess>;
43
- export type LegacyBuildCreativeReturn = CreativeManifest | CreativeManifest[] | LegacyBuildCreativePayload | LegacyBuildCreativeMultiPayload;
43
+ export type BuildCreativeVariantPayload = ServerPayload<BuildCreativeVariantSuccess>;
44
+ export type LegacyBuildCreativeReturn = CreativeManifest | CreativeManifest[] | LegacyBuildCreativePayload | LegacyBuildCreativeMultiPayload | BuildCreativeVariantPayload;
44
45
  interface CreativeAdServerPlatformBase<TCtxMeta> {
45
46
  /**
46
47
  * Build / retrieve creative tags. Two invocation modes per the spec: