@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
@@ -1,10 +1,18 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  const DEFAULT_TABLE = "adcp_decisioning_tasks";
3
3
  const VALID_IDENTIFIER = /^[a-z_][a-z0-9_]*$/;
4
+ const VALID_NAMESPACE = /^[A-Za-z0-9_.:-]{1,255}$/;
4
5
  function assertValidIdentifier(name) {
5
- if (!VALID_IDENTIFIER.test(name)) {
6
+ if (!VALID_IDENTIFIER.test(name) || Buffer.byteLength(name, "utf8") > 40) {
6
7
  throw new Error(
7
- `Invalid table name "${name}": must be lowercase letters, digits, or underscores, starting with a letter or underscore.`
8
+ `Invalid table name "${name}": must be lowercase letters, digits, or underscores, starting with a letter or underscore, and at most 40 bytes.`
9
+ );
10
+ }
11
+ }
12
+ function assertValidNamespace(namespace) {
13
+ if (typeof namespace !== "string" || !VALID_NAMESPACE.test(namespace)) {
14
+ throw new Error(
15
+ "Invalid registry namespace: namespace is required and must be 1-255 ASCII letters, digits, dots, underscores, colons, or hyphens."
8
16
  );
9
17
  }
10
18
  }
@@ -27,14 +35,17 @@ function safeStringify(value, taskId) {
27
35
  }
28
36
  }
29
37
  function getDecisioningTaskRegistryMigration(options) {
38
+ assertValidNamespace(options?.namespace);
30
39
  const table = options?.tableName ?? DEFAULT_TABLE;
31
40
  assertValidIdentifier(table);
41
+ const namespace = options.namespace;
32
42
  return `
33
43
  CREATE TABLE IF NOT EXISTS ${table} (
34
- task_id TEXT PRIMARY KEY,
44
+ registry_namespace TEXT NOT NULL DEFAULT '${namespace}',
45
+ task_id TEXT NOT NULL,
35
46
  tool TEXT NOT NULL,
36
47
  account_id TEXT NOT NULL,
37
- owner_scope TEXT,
48
+ owner_scope TEXT NOT NULL,
38
49
  status TEXT NOT NULL DEFAULT 'submitted',
39
50
  status_message TEXT,
40
51
  result JSONB,
@@ -65,6 +76,45 @@ CREATE INDEX IF NOT EXISTS idx_${table}_status_created
65
76
  ALTER TABLE ${table}
66
77
  ADD COLUMN IF NOT EXISTS owner_scope TEXT;
67
78
 
79
+ ALTER TABLE ${table}
80
+ ADD COLUMN IF NOT EXISTS registry_namespace TEXT NOT NULL DEFAULT '__adcp_legacy_unscoped__';
81
+
82
+ UPDATE ${table}
83
+ SET registry_namespace = '${namespace}'
84
+ WHERE registry_namespace = '__adcp_legacy_unscoped__';
85
+
86
+ ALTER TABLE ${table}
87
+ ALTER COLUMN registry_namespace SET DEFAULT '${namespace}';
88
+
89
+ UPDATE ${table}
90
+ SET owner_scope = 'account:' || account_id
91
+ WHERE owner_scope IS NULL;
92
+
93
+ ALTER TABLE ${table}
94
+ ALTER COLUMN owner_scope SET NOT NULL;
95
+
96
+ DO $$
97
+ DECLARE
98
+ current_primary_key TEXT;
99
+ BEGIN
100
+ PERFORM pg_advisory_xact_lock(hashtext('adcp-task-registry:${table}'));
101
+ SELECT conname INTO current_primary_key
102
+ FROM pg_constraint
103
+ WHERE conrelid = '${table}'::regclass AND contype = 'p';
104
+ IF current_primary_key IS NOT NULL AND current_primary_key <> '${table}_scope_pkey' THEN
105
+ EXECUTE format('ALTER TABLE %I DROP CONSTRAINT %I', '${table}', current_primary_key);
106
+ END IF;
107
+ IF NOT EXISTS (
108
+ SELECT 1 FROM pg_constraint
109
+ WHERE conname = '${table}_scope_pkey'
110
+ AND conrelid = '${table}'::regclass
111
+ ) THEN
112
+ ALTER TABLE ${table}
113
+ ADD CONSTRAINT ${table}_scope_pkey
114
+ PRIMARY KEY (registry_namespace, account_id, owner_scope, task_id);
115
+ END IF;
116
+ END $$;
117
+
68
118
  CREATE INDEX IF NOT EXISTS idx_${table}_owner_account
69
119
  ON ${table}(owner_scope, account_id);
70
120
  `.trim();
@@ -89,18 +139,22 @@ function createPostgresTaskRegistry(opts) {
89
139
  const table = opts.tableName ?? DEFAULT_TABLE;
90
140
  assertValidIdentifier(table);
91
141
  const pool = opts.pool;
142
+ const namespace = opts.namespace;
143
+ assertValidNamespace(namespace);
92
144
  const backgrounds = /* @__PURE__ */ new Map();
93
145
  return {
146
+ scopeVersion: 1,
94
147
  async create(createOpts) {
95
148
  const taskId = createOpts.overrideTaskId ?? `task_${randomUUID()}`;
96
149
  const result = await pool.query(
97
- `INSERT INTO ${table} (task_id, tool, account_id, owner_scope, status, has_webhook) VALUES ($1, $2, $3, $4, 'submitted', $5) ON CONFLICT (task_id) DO NOTHING`,
150
+ `INSERT INTO ${table} (task_id, tool, account_id, owner_scope, status, has_webhook, registry_namespace) VALUES ($1, $2, $3, $4, 'submitted', $5, $6) ON CONFLICT (registry_namespace, account_id, owner_scope, task_id) DO NOTHING`,
98
151
  [
99
152
  taskId,
100
153
  createOpts.tool,
101
154
  createOpts.accountId,
102
155
  createOpts.ownerScope ?? `account:${createOpts.accountId}`,
103
- createOpts.hasWebhook === true
156
+ createOpts.hasWebhook === true,
157
+ namespace
104
158
  ]
105
159
  );
106
160
  if ((result.rowCount ?? 0) === 0) {
@@ -108,73 +162,89 @@ function createPostgresTaskRegistry(opts) {
108
162
  }
109
163
  return { taskId };
110
164
  },
111
- async getTask(taskId) {
165
+ async getTask(taskId, scope) {
112
166
  const { rows } = await pool.query(
113
167
  `SELECT task_id, tool, account_id, owner_scope, status, status_message, result, error, progress, has_webhook, created_at, updated_at
114
- FROM ${table} WHERE task_id = $1`,
115
- [taskId]
168
+ FROM ${table} WHERE task_id = $1 AND registry_namespace = $2 AND account_id = $3 AND owner_scope = $4`,
169
+ [taskId, namespace, scope.accountId, scope.ownerScope]
116
170
  );
117
171
  if (rows.length === 0) return null;
118
172
  return rowToRecord(rows[0]);
119
173
  },
174
+ async _getTaskUnsafe(taskId) {
175
+ const { rows } = await pool.query(
176
+ `SELECT task_id, tool, account_id, owner_scope, status, status_message, result, error, progress, has_webhook, created_at, updated_at
177
+ FROM ${table} WHERE task_id = $1 AND registry_namespace = $2 LIMIT 2`,
178
+ [taskId, namespace]
179
+ );
180
+ if (rows.length !== 1) return null;
181
+ return rowToRecord(rows[0]);
182
+ },
120
183
  async list(listOpts) {
121
- if (listOpts.ownerScope === void 0) return { tasks: [] };
122
184
  const { rows } = await pool.query(
123
185
  `SELECT task_id, tool, account_id, owner_scope, status, status_message, result, error, progress, has_webhook, created_at, updated_at
124
186
  FROM ${table}
125
- WHERE account_id = $1 AND (owner_scope = $2 OR (owner_scope IS NULL AND $2 = $3))
187
+ WHERE registry_namespace = $1 AND account_id = $2 AND owner_scope = $3
126
188
  ORDER BY created_at DESC, task_id DESC`,
127
- [listOpts.accountId, listOpts.ownerScope, `account:${listOpts.accountId}`]
189
+ [namespace, listOpts.accountId, listOpts.ownerScope]
128
190
  );
129
191
  return { tasks: rows.map((row) => rowToRecord(row)) };
130
192
  },
131
- async complete(taskId, result) {
193
+ async complete(taskId, scope, result) {
132
194
  const json = safeStringify(result, taskId);
133
195
  assertResultSize(json, taskId);
134
196
  await pool.query(
135
197
  `UPDATE ${table}
136
- SET status = 'completed', result = $2::jsonb, updated_at = NOW()
137
- WHERE task_id = $1 AND status NOT IN ('completed', 'failed', 'rejected', 'canceled')`,
138
- [taskId, json]
198
+ SET status = 'completed', result = $5::jsonb, updated_at = NOW()
199
+ WHERE task_id = $1 AND registry_namespace = $2 AND account_id = $3 AND owner_scope = $4
200
+ AND status NOT IN ('completed', 'failed', 'rejected', 'canceled')`,
201
+ [taskId, namespace, scope.accountId, scope.ownerScope, json]
139
202
  );
140
203
  },
141
- async fail(taskId, error, result) {
204
+ async fail(taskId, scope, error, result) {
142
205
  const errorJson = safeStringify(error, taskId);
143
206
  const resultJson = result === void 0 ? void 0 : safeStringify(result, taskId);
144
207
  assertResultSize(errorJson, taskId);
145
208
  if (resultJson !== void 0) assertResultSize(resultJson, taskId);
146
209
  await pool.query(
147
210
  `UPDATE ${table}
148
- SET status = 'failed', error = $2::jsonb, result = $3::jsonb, status_message = $4, updated_at = NOW()
149
- WHERE task_id = $1 AND status NOT IN ('completed', 'failed', 'rejected', 'canceled')`,
150
- [taskId, errorJson, resultJson ?? null, error.message]
211
+ SET status = 'failed', error = $5::jsonb, result = $6::jsonb, status_message = $7, updated_at = NOW()
212
+ WHERE task_id = $1 AND registry_namespace = $2 AND account_id = $3 AND owner_scope = $4
213
+ AND status NOT IN ('completed', 'failed', 'rejected', 'canceled')`,
214
+ [taskId, namespace, scope.accountId, scope.ownerScope, errorJson, resultJson ?? null, error.message]
151
215
  );
152
216
  },
153
- async updateProgress(taskId, progress) {
217
+ async updateProgress(taskId, scope, progress) {
154
218
  const json = safeStringify(progress, taskId);
155
219
  await pool.query(
156
220
  `UPDATE ${table}
157
- SET progress = $2::jsonb,
221
+ SET progress = $5::jsonb,
158
222
  status = CASE WHEN status = 'submitted' THEN 'working' ELSE status END,
159
223
  updated_at = NOW()
160
- WHERE task_id = $1 AND status NOT IN ('completed', 'failed', 'rejected', 'canceled')`,
161
- [taskId, json]
224
+ WHERE task_id = $1 AND registry_namespace = $2 AND account_id = $3 AND owner_scope = $4
225
+ AND status NOT IN ('completed', 'failed', 'rejected', 'canceled')`,
226
+ [taskId, namespace, scope.accountId, scope.ownerScope, json]
162
227
  );
163
228
  },
164
- _registerBackground(taskId, completion) {
229
+ _registerBackground(taskId, scope, completion) {
230
+ const backgroundKey = JSON.stringify([scope.accountId, scope.ownerScope, taskId]);
165
231
  const composed = completion.then(
166
232
  () => {
167
- if (backgrounds.get(taskId) === composed) backgrounds.delete(taskId);
233
+ if (backgrounds.get(backgroundKey) === composed) backgrounds.delete(backgroundKey);
168
234
  },
169
235
  () => {
170
- if (backgrounds.get(taskId) === composed) backgrounds.delete(taskId);
236
+ if (backgrounds.get(backgroundKey) === composed) backgrounds.delete(backgroundKey);
171
237
  }
172
238
  );
173
- backgrounds.set(taskId, composed);
239
+ backgrounds.set(backgroundKey, composed);
174
240
  },
175
- async awaitTask(taskId) {
176
- const pending = backgrounds.get(taskId);
241
+ async awaitTask(taskId, scope) {
242
+ const pending = backgrounds.get(JSON.stringify([scope.accountId, scope.ownerScope, taskId]));
177
243
  if (pending) await pending;
244
+ },
245
+ async _awaitTaskUnsafe(taskId) {
246
+ const pending = Array.from(backgrounds.entries()).filter(([key]) => JSON.parse(key)[2] === taskId).map(([, completion]) => completion);
247
+ await Promise.all(pending);
178
248
  }
179
249
  };
180
250
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/lib/server/decisioning/runtime/postgres-task-registry.ts"],"sourcesContent":["/**\n * PostgreSQL-backed `TaskRegistry` for the v6.0 decisioning runtime.\n *\n * The default `createInMemoryTaskRegistry()` loses task state on process\n * restart and doesn't share state across instances behind a load balancer —\n * fine for tests and local dev, broken for production HITL paths\n * (`createMediaBuyTask`, `syncCreativesTask`, etc.). Wire this in via\n * `createAdcpServerFromPlatform({ taskRegistry: createPostgresTaskRegistry({ pool }) })`\n * to persist task lifecycle across requests, processes, and crashes.\n *\n * @example\n * ```typescript\n * import { Pool } from 'pg';\n * import {\n * createAdcpServerFromPlatform,\n * createPostgresTaskRegistry,\n * getDecisioningTaskRegistryMigration,\n * } from '@adcp/sdk/server/decisioning';\n *\n * const pool = new Pool({ connectionString: process.env.DATABASE_URL });\n *\n * // Run once at boot — idempotent CREATE TABLE IF NOT EXISTS.\n * await pool.query(getDecisioningTaskRegistryMigration());\n *\n * const server = createAdcpServerFromPlatform(platform, {\n * name: 'My Ad Network',\n * version: '1.0.0',\n * taskRegistry: createPostgresTaskRegistry({ pool }),\n * });\n * ```\n *\n * **Background-completion lifecycle.** `_registerBackground` / `awaitTask`\n * are PROCESS-LOCAL — promises don't serialize. When a HITL `*Task` method\n * is invoked, the returned promise lives on the originating process; if\n * that process restarts before the method completes, the task record stays\n * in `submitted` state in Postgres but no `awaitTask` resolution is\n * possible from a different instance. Production HITL flows that span\n * process boundaries should drive completion via webhook → an explicit\n * `complete()` / `fail()` call from the webhook handler, not via\n * `awaitTask`. The MCP `tasks/get` wire path reads via `getTask` and is\n * already cross-instance.\n *\n * Status: Preview / 6.0.\n *\n * @public\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { AdcpStructuredError, TaskHandoffProgress } from '../async-outcome';\nimport type { TaskRecord, TaskRegistry, TaskStatus } from './task-registry';\n\n/**\n * Minimal subset of the `pg.Pool` interface used by the registry.\n * Mirrors `PgQueryable` in `postgres-task-store.ts` so callers can pass\n * pools, transaction wrappers, or any pg-compatible query executor.\n */\nexport interface PgQueryable {\n query(text: string, values?: unknown[]): Promise<{ rows: Record<string, unknown>[]; rowCount: number | null }>;\n}\n\nexport interface CreatePostgresTaskRegistryOptions {\n /** A `pg.Pool` instance (or any `PgQueryable`). */\n pool: PgQueryable;\n /**\n * Table name. Defaults to `'adcp_decisioning_tasks'` (vendor-prefixed to\n * avoid collisions with the MCP-level `adcp_mcp_tasks` table from\n * `PostgresTaskStore` and any consumer tables).\n *\n * Must match `^[a-z_][a-z0-9_]*$` — schema-qualified names are not\n * supported by this validator. Set search_path on the pool if you need\n * a non-default schema.\n */\n tableName?: string;\n}\n\n/** Default table name — vendor-prefixed to avoid collisions. */\nconst DEFAULT_TABLE = 'adcp_decisioning_tasks';\n\n/** Validates a SQL identifier to prevent injection via table names. */\nconst VALID_IDENTIFIER = /^[a-z_][a-z0-9_]*$/;\n\nfunction assertValidIdentifier(name: string): void {\n if (!VALID_IDENTIFIER.test(name)) {\n throw new Error(\n `Invalid table name \"${name}\": must be lowercase letters, digits, ` +\n `or underscores, starting with a letter or underscore.`\n );\n }\n}\n\n/**\n * Cap on `result` / `error` JSONB column size. Adopter `*Task` returns are\n * written verbatim — a buggy or malicious adopter handing back a 1GB\n * result would OOM the Node process before pg complains. 4MB matches the\n * default Postgres `toast` row threshold and gives plenty of headroom for\n * legitimate task payloads.\n *\n * **This cap protects the DB write path only.** Adopter code that\n * serializes `result` / `error` for logs, metrics, or downstream services\n * MUST impose its own size cap — `JSON.stringify(result)` in a logger\n * call is unbounded.\n */\nconst MAX_RESULT_BYTES = 4 * 1024 * 1024;\n\nfunction assertResultSize(json: string, taskId: string): void {\n // `Buffer.byteLength` is utf-8 byte length, which is what Postgres stores.\n if (Buffer.byteLength(json, 'utf8') > MAX_RESULT_BYTES) {\n throw new Error(\n `Task ${taskId}: result/error JSON exceeds ${MAX_RESULT_BYTES} bytes ` +\n `(adopter *Task method returned an oversized payload — investigate ` +\n `whether the body should be persisted via blob storage and referenced).`\n );\n }\n}\n\n/**\n * Wrap `JSON.stringify` with a clearer error when the adopter `*Task`\n * return contains circular references. Default `TypeError: Converting\n * circular structure to JSON` doesn't surface the task id; this version\n * bubbles a registry-write error pointing at the adopter return shape.\n */\nfunction safeStringify(value: unknown, taskId: string): string {\n try {\n return JSON.stringify(value);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(\n `Task ${taskId}: adopter *Task return is not JSON-serializable: ${msg}. ` +\n `Strip circular refs / non-plain-data before returning from your *Task method.`\n );\n }\n}\n\n/**\n * Generate the SQL DDL for the decisioning task registry table.\n *\n * Run once at boot (idempotent — `CREATE TABLE IF NOT EXISTS`). Constraint\n * and index names are derived from the table name to avoid collisions when\n * multiple registries share the same database.\n *\n * @example\n * ```typescript\n * import { getDecisioningTaskRegistryMigration } from '@adcp/sdk/server/decisioning';\n * await pool.query(getDecisioningTaskRegistryMigration());\n * await pool.query(getDecisioningTaskRegistryMigration({ tableName: 'my_tasks' }));\n * ```\n */\nexport function getDecisioningTaskRegistryMigration(options?: { tableName?: string }): string {\n const table = options?.tableName ?? DEFAULT_TABLE;\n assertValidIdentifier(table);\n return `\nCREATE TABLE IF NOT EXISTS ${table} (\n task_id TEXT PRIMARY KEY,\n tool TEXT NOT NULL,\n account_id TEXT NOT NULL,\n owner_scope TEXT,\n status TEXT NOT NULL DEFAULT 'submitted',\n status_message TEXT,\n result JSONB,\n error JSONB,\n progress JSONB,\n has_webhook BOOLEAN NOT NULL DEFAULT FALSE,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n\n CONSTRAINT ${table}_valid_status CHECK (\n -- Framework-written values: 'submitted' (initial), 'working'\n -- (after first updateProgress() call), 'completed' / 'failed'\n -- (terminal). The other 5 spec-defined states ('input-required',\n -- 'canceled', 'rejected', 'auth-required', 'unknown') are reserved\n -- for adopter-emitted transitions via the v6.1\n -- \\`taskRegistry.transition()\\` API; the v6.1 migration will widen\n -- this CHECK.\n status IN ('submitted', 'working', 'completed', 'failed')\n )\n);\n\nCREATE INDEX IF NOT EXISTS idx_${table}_account_id\n ON ${table}(account_id);\n\nCREATE INDEX IF NOT EXISTS idx_${table}_status_created\n ON ${table}(status, created_at);\n\nALTER TABLE ${table}\n ADD COLUMN IF NOT EXISTS owner_scope TEXT;\n\nCREATE INDEX IF NOT EXISTS idx_${table}_owner_account\n ON ${table}(owner_scope, account_id);\n`.trim();\n}\n\ninterface DbTaskRow {\n task_id: string;\n tool: string;\n account_id: string;\n owner_scope: string | null;\n status: TaskStatus;\n status_message: string | null;\n result: unknown;\n error: AdcpStructuredError | null;\n progress: TaskHandoffProgress | null;\n has_webhook: boolean;\n created_at: Date;\n updated_at: Date;\n}\n\nfunction rowToRecord<TResult>(row: DbTaskRow): TaskRecord<TResult> {\n return {\n taskId: row.task_id,\n tool: row.tool,\n accountId: row.account_id,\n ...(row.owner_scope ? { ownerScope: row.owner_scope } : {}),\n status: row.status,\n ...(row.status_message !== null && { statusMessage: row.status_message }),\n ...(row.result !== null && row.result !== undefined && { result: row.result as TResult }),\n ...(row.error !== null && row.error !== undefined && { error: row.error }),\n ...(row.progress !== null && row.progress !== undefined && { progress: row.progress }),\n ...(row.has_webhook && { hasWebhook: true }),\n createdAt: row.created_at.toISOString(),\n updatedAt: row.updated_at.toISOString(),\n };\n}\n\n/**\n * Build a Postgres-backed `TaskRegistry`.\n *\n * Idempotency: `complete()` and `fail()` are no-ops on already-terminal\n * tasks, matching `createInMemoryTaskRegistry()`. The terminal-state guard\n * is enforced via SQL `WHERE status = 'submitted'` predicates so concurrent\n * webhook deliveries can't race to overwrite each other.\n *\n * **Multi-tenant deployments — accountId namespacing.** When sharing a\n * single Postgres registry across tenants in a `TenantRegistry`\n * deployment (one table for all tenants), prefix `account_id` per-tenant\n * to prevent cross-tenant collisions. If tenant A and tenant B both\n * resolve a buyer to literal `account_id: 'acme'`, their tasks land in\n * the same row keyspace and `tasks_get` can't distinguish ownership\n * by `account_id` alone (the tenant boundary upstream IS protected by\n * `accounts.resolve(ref, ctx)` returning each tenant's own Account, but\n * the registry sees the same `acme` string in both rows). Recommended\n * pattern: adopters' `accounts.resolve` returns\n * `id: \\`tenant_${tenantId}_${accountId}\\`` so the table-level keys\n * stay distinct. Alternative: separate `tableName` per tenant, one\n * `createPostgresTaskRegistry({ pool, tableName })` per `TenantRegistry`\n * tenant — heavier ops, stronger isolation.\n */\nexport function createPostgresTaskRegistry(opts: CreatePostgresTaskRegistryOptions): TaskRegistry {\n const table = opts.tableName ?? DEFAULT_TABLE;\n assertValidIdentifier(table);\n const pool = opts.pool;\n\n // Process-local background tracking — see file header note. Promises\n // can't be persisted; cross-instance HITL completion happens via\n // webhook → explicit complete()/fail(), not via awaitTask.\n const backgrounds = new Map<string, Promise<void>>();\n\n return {\n async create(createOpts: {\n tool: string;\n accountId: string;\n ownerScope?: string;\n hasWebhook?: boolean;\n overrideTaskId?: string;\n }): Promise<{ taskId: string }> {\n const taskId = createOpts.overrideTaskId ?? `task_${randomUUID()}`;\n const result = await pool.query(\n `INSERT INTO ${table} (task_id, tool, account_id, owner_scope, status, has_webhook) VALUES ($1, $2, $3, $4, 'submitted', $5) ON CONFLICT (task_id) DO NOTHING`,\n [\n taskId,\n createOpts.tool,\n createOpts.accountId,\n createOpts.ownerScope ?? `account:${createOpts.accountId}`,\n createOpts.hasWebhook === true,\n ]\n );\n if ((result.rowCount ?? 0) === 0) {\n throw new Error(`task_id already registered: ${taskId}`);\n }\n return { taskId };\n },\n\n async getTask<TResult = unknown>(taskId: string): Promise<TaskRecord<TResult> | null> {\n const { rows } = await pool.query(\n `SELECT task_id, tool, account_id, owner_scope, status, status_message, result, error, progress, has_webhook, created_at, updated_at\n FROM ${table} WHERE task_id = $1`,\n [taskId]\n );\n if (rows.length === 0) return null;\n return rowToRecord<TResult>(rows[0] as unknown as DbTaskRow);\n },\n\n async list(listOpts: { accountId: string; ownerScope?: string }): Promise<{ tasks: TaskRecord[] }> {\n if (listOpts.ownerScope === undefined) return { tasks: [] };\n const { rows } = await pool.query(\n `SELECT task_id, tool, account_id, owner_scope, status, status_message, result, error, progress, has_webhook, created_at, updated_at\n FROM ${table}\n WHERE account_id = $1 AND (owner_scope = $2 OR (owner_scope IS NULL AND $2 = $3))\n ORDER BY created_at DESC, task_id DESC`,\n [listOpts.accountId, listOpts.ownerScope, `account:${listOpts.accountId}`]\n );\n return { tasks: rows.map(row => rowToRecord(row as unknown as DbTaskRow)) };\n },\n\n async complete<TResult>(taskId: string, result: TResult): Promise<void> {\n const json = safeStringify(result, taskId);\n assertResultSize(json, taskId);\n await pool.query(\n `UPDATE ${table}\n SET status = 'completed', result = $2::jsonb, updated_at = NOW()\n WHERE task_id = $1 AND status NOT IN ('completed', 'failed', 'rejected', 'canceled')`,\n [taskId, json]\n );\n },\n\n async fail(taskId: string, error: AdcpStructuredError, result?: unknown): Promise<void> {\n const errorJson = safeStringify(error, taskId);\n const resultJson = result === undefined ? undefined : safeStringify(result, taskId);\n assertResultSize(errorJson, taskId);\n if (resultJson !== undefined) assertResultSize(resultJson, taskId);\n await pool.query(\n `UPDATE ${table}\n SET status = 'failed', error = $2::jsonb, result = $3::jsonb, status_message = $4, updated_at = NOW()\n WHERE task_id = $1 AND status NOT IN ('completed', 'failed', 'rejected', 'canceled')`,\n [taskId, errorJson, resultJson ?? null, error.message]\n );\n },\n\n async updateProgress(taskId: string, progress: TaskHandoffProgress): Promise<void> {\n const json = safeStringify(progress, taskId);\n await pool.query(\n `UPDATE ${table}\n SET progress = $2::jsonb,\n status = CASE WHEN status = 'submitted' THEN 'working' ELSE status END,\n updated_at = NOW()\n WHERE task_id = $1 AND status NOT IN ('completed', 'failed', 'rejected', 'canceled')`,\n [taskId, json]\n );\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 } satisfies TaskRegistry;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../../src/lib/server/decisioning/runtime/postgres-task-registry.ts"],"sourcesContent":["/**\n * PostgreSQL-backed `TaskRegistry` for the v6.0 decisioning runtime.\n *\n * The default `createInMemoryTaskRegistry()` loses task state on process\n * restart and doesn't share state across instances behind a load balancer —\n * fine for tests and local dev, broken for production HITL paths\n * (`createMediaBuyTask`, `syncCreativesTask`, etc.). Wire this in via\n * `createAdcpServerFromPlatform({ taskRegistry: createPostgresTaskRegistry({ pool, namespace }) })`\n * to persist task lifecycle across requests, processes, and crashes.\n *\n * @example\n * ```typescript\n * import { Pool } from 'pg';\n * import {\n * createAdcpServerFromPlatform,\n * createPostgresTaskRegistry,\n * getDecisioningTaskRegistryMigration,\n * } from '@adcp/sdk/server/decisioning';\n *\n * const pool = new Pool({ connectionString: process.env.DATABASE_URL });\n *\n * const taskRegistryNamespace = 'tenant:my-agent';\n * // Run once at boot, using the same trusted namespace as the registry.\n * await pool.query(getDecisioningTaskRegistryMigration({ namespace: taskRegistryNamespace }));\n *\n * const server = createAdcpServerFromPlatform(platform, {\n * name: 'My Ad Network',\n * version: '1.0.0',\n * taskRegistry: createPostgresTaskRegistry({ pool, namespace: taskRegistryNamespace }),\n * });\n * ```\n *\n * **Background-completion lifecycle.** `_registerBackground` / `awaitTask`\n * are PROCESS-LOCAL — promises don't serialize. When a HITL `*Task` method\n * is invoked, the returned promise lives on the originating process; if\n * that process restarts before the method completes, the task record stays\n * in `submitted` state in Postgres but no `awaitTask` resolution is\n * possible from a different instance. Production HITL flows that span\n * process boundaries should drive completion via webhook → an explicit\n * `complete()` / `fail()` call from the webhook handler, not via\n * `awaitTask`. The MCP `tasks/get` wire path reads via `getTask` and is\n * already cross-instance.\n *\n * Status: Preview / 6.0.\n *\n * @public\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { AdcpStructuredError, TaskHandoffProgress } from '../async-outcome';\nimport type { TaskRecord, TaskRegistry, TaskRegistryScope, TaskStatus } from './task-registry';\n\n/**\n * Minimal subset of the `pg.Pool` interface used by the registry.\n * Mirrors `PgQueryable` in `postgres-task-store.ts` so callers can pass\n * pools, transaction wrappers, or any pg-compatible query executor.\n */\nexport interface PgQueryable {\n query(text: string, values?: unknown[]): Promise<{ rows: Record<string, unknown>[]; rowCount: number | null }>;\n}\n\nexport interface CreatePostgresTaskRegistryOptions {\n /** A `pg.Pool` instance (or any `PgQueryable`). */\n pool: PgQueryable;\n /** Trusted deployment/tenant namespace. Use a distinct value per hosted tenant. */\n namespace: string;\n /**\n * Table name. Defaults to `'adcp_decisioning_tasks'` (vendor-prefixed to\n * avoid collisions with the MCP-level `adcp_mcp_tasks` table from\n * `PostgresTaskStore` and any consumer tables).\n *\n * Must match `^[a-z_][a-z0-9_]*$` — schema-qualified names are not\n * supported by this validator. Set search_path on the pool if you need\n * a non-default schema.\n */\n tableName?: string;\n}\n\n/** Default table name — vendor-prefixed to avoid collisions. */\nconst DEFAULT_TABLE = 'adcp_decisioning_tasks';\n\n/** Validates a SQL identifier to prevent injection via table names. */\nconst VALID_IDENTIFIER = /^[a-z_][a-z0-9_]*$/;\nconst VALID_NAMESPACE = /^[A-Za-z0-9_.:-]{1,255}$/;\n\nfunction assertValidIdentifier(name: string): void {\n if (!VALID_IDENTIFIER.test(name) || Buffer.byteLength(name, 'utf8') > 40) {\n throw new Error(\n `Invalid table name \"${name}\": must be lowercase letters, digits, ` +\n `or underscores, starting with a letter or underscore, and at most 40 bytes.`\n );\n }\n}\n\nfunction assertValidNamespace(namespace: unknown): asserts namespace is string {\n if (typeof namespace !== 'string' || !VALID_NAMESPACE.test(namespace)) {\n throw new Error(\n 'Invalid registry namespace: namespace is required and must be 1-255 ASCII letters, digits, dots, underscores, colons, or hyphens.'\n );\n }\n}\n\n/**\n * Cap on `result` / `error` JSONB column size. Adopter `*Task` returns are\n * written verbatim — a buggy or malicious adopter handing back a 1GB\n * result would OOM the Node process before pg complains. 4MB matches the\n * default Postgres `toast` row threshold and gives plenty of headroom for\n * legitimate task payloads.\n *\n * **This cap protects the DB write path only.** Adopter code that\n * serializes `result` / `error` for logs, metrics, or downstream services\n * MUST impose its own size cap — `JSON.stringify(result)` in a logger\n * call is unbounded.\n */\nconst MAX_RESULT_BYTES = 4 * 1024 * 1024;\n\nfunction assertResultSize(json: string, taskId: string): void {\n // `Buffer.byteLength` is utf-8 byte length, which is what Postgres stores.\n if (Buffer.byteLength(json, 'utf8') > MAX_RESULT_BYTES) {\n throw new Error(\n `Task ${taskId}: result/error JSON exceeds ${MAX_RESULT_BYTES} bytes ` +\n `(adopter *Task method returned an oversized payload — investigate ` +\n `whether the body should be persisted via blob storage and referenced).`\n );\n }\n}\n\n/**\n * Wrap `JSON.stringify` with a clearer error when the adopter `*Task`\n * return contains circular references. Default `TypeError: Converting\n * circular structure to JSON` doesn't surface the task id; this version\n * bubbles a registry-write error pointing at the adopter return shape.\n */\nfunction safeStringify(value: unknown, taskId: string): string {\n try {\n return JSON.stringify(value);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(\n `Task ${taskId}: adopter *Task return is not JSON-serializable: ${msg}. ` +\n `Strip circular refs / non-plain-data before returning from your *Task method.`\n );\n }\n}\n\n/**\n * Generate the SQL DDL for the decisioning task registry table.\n *\n * Run once at boot (idempotent — `CREATE TABLE IF NOT EXISTS`). Constraint\n * and index names are derived from the table name to avoid collisions when\n * multiple registries share the same database.\n *\n * @example\n * ```typescript\n * import { getDecisioningTaskRegistryMigration } from '@adcp/sdk/server/decisioning';\n * await pool.query(getDecisioningTaskRegistryMigration({ namespace: tenantId }));\n * await pool.query(getDecisioningTaskRegistryMigration({ tableName: 'my_tasks', namespace: tenantId }));\n * ```\n */\nexport function getDecisioningTaskRegistryMigration(options: { tableName?: string; namespace: string }): string {\n assertValidNamespace(options?.namespace);\n const table = options?.tableName ?? DEFAULT_TABLE;\n assertValidIdentifier(table);\n const namespace = options.namespace;\n return `\nCREATE TABLE IF NOT EXISTS ${table} (\n registry_namespace TEXT NOT NULL DEFAULT '${namespace}',\n task_id TEXT NOT NULL,\n tool TEXT NOT NULL,\n account_id TEXT NOT NULL,\n owner_scope TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'submitted',\n status_message TEXT,\n result JSONB,\n error JSONB,\n progress JSONB,\n has_webhook BOOLEAN NOT NULL DEFAULT FALSE,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n\n CONSTRAINT ${table}_valid_status CHECK (\n -- Framework-written values: 'submitted' (initial), 'working'\n -- (after first updateProgress() call), 'completed' / 'failed'\n -- (terminal). The other 5 spec-defined states ('input-required',\n -- 'canceled', 'rejected', 'auth-required', 'unknown') are reserved\n -- for adopter-emitted transitions via the v6.1\n -- \\`taskRegistry.transition()\\` API; the v6.1 migration will widen\n -- this CHECK.\n status IN ('submitted', 'working', 'completed', 'failed')\n )\n);\n\nCREATE INDEX IF NOT EXISTS idx_${table}_account_id\n ON ${table}(account_id);\n\nCREATE INDEX IF NOT EXISTS idx_${table}_status_created\n ON ${table}(status, created_at);\n\nALTER TABLE ${table}\n ADD COLUMN IF NOT EXISTS owner_scope TEXT;\n\nALTER TABLE ${table}\n ADD COLUMN IF NOT EXISTS registry_namespace TEXT NOT NULL DEFAULT '__adcp_legacy_unscoped__';\n\nUPDATE ${table}\n SET registry_namespace = '${namespace}'\n WHERE registry_namespace = '__adcp_legacy_unscoped__';\n\nALTER TABLE ${table}\n ALTER COLUMN registry_namespace SET DEFAULT '${namespace}';\n\nUPDATE ${table}\n SET owner_scope = 'account:' || account_id\n WHERE owner_scope IS NULL;\n\nALTER TABLE ${table}\n ALTER COLUMN owner_scope SET NOT NULL;\n\nDO $$\nDECLARE\n current_primary_key TEXT;\nBEGIN\n PERFORM pg_advisory_xact_lock(hashtext('adcp-task-registry:${table}'));\n SELECT conname INTO current_primary_key\n FROM pg_constraint\n WHERE conrelid = '${table}'::regclass AND contype = 'p';\n IF current_primary_key IS NOT NULL AND current_primary_key <> '${table}_scope_pkey' THEN\n EXECUTE format('ALTER TABLE %I DROP CONSTRAINT %I', '${table}', current_primary_key);\n END IF;\n IF NOT EXISTS (\n SELECT 1 FROM pg_constraint\n WHERE conname = '${table}_scope_pkey'\n AND conrelid = '${table}'::regclass\n ) THEN\n ALTER TABLE ${table}\n ADD CONSTRAINT ${table}_scope_pkey\n PRIMARY KEY (registry_namespace, account_id, owner_scope, task_id);\n END IF;\nEND $$;\n\nCREATE INDEX IF NOT EXISTS idx_${table}_owner_account\n ON ${table}(owner_scope, account_id);\n`.trim();\n}\n\ninterface DbTaskRow {\n task_id: string;\n tool: string;\n account_id: string;\n owner_scope: string | null;\n status: TaskStatus;\n status_message: string | null;\n result: unknown;\n error: AdcpStructuredError | null;\n progress: TaskHandoffProgress | null;\n has_webhook: boolean;\n created_at: Date;\n updated_at: Date;\n}\n\nfunction rowToRecord<TResult>(row: DbTaskRow): TaskRecord<TResult> {\n return {\n taskId: row.task_id,\n tool: row.tool,\n accountId: row.account_id,\n ...(row.owner_scope ? { ownerScope: row.owner_scope } : {}),\n status: row.status,\n ...(row.status_message !== null && { statusMessage: row.status_message }),\n ...(row.result !== null && row.result !== undefined && { result: row.result as TResult }),\n ...(row.error !== null && row.error !== undefined && { error: row.error }),\n ...(row.progress !== null && row.progress !== undefined && { progress: row.progress }),\n ...(row.has_webhook && { hasWebhook: true }),\n createdAt: row.created_at.toISOString(),\n updatedAt: row.updated_at.toISOString(),\n };\n}\n\n/**\n * Build a Postgres-backed `TaskRegistry`.\n *\n * Idempotency: `complete()` and `fail()` are no-ops on already-terminal\n * tasks, matching `createInMemoryTaskRegistry()`. The terminal-state guard\n * is enforced via SQL `WHERE status = 'submitted'` predicates so concurrent\n * webhook deliveries can't race to overwrite each other.\n *\n * **Multi-tenant deployments.** `namespace` is a trusted deployment/tenant\n * partition and participates in every query plus the primary key. Construct\n * one registry per tenant with a stable unique namespace; never derive it\n * from buyer request parameters. Tenants may safely share the same table.\n */\nexport function createPostgresTaskRegistry(opts: CreatePostgresTaskRegistryOptions): TaskRegistry {\n const table = opts.tableName ?? DEFAULT_TABLE;\n assertValidIdentifier(table);\n const pool = opts.pool;\n const namespace = opts.namespace;\n assertValidNamespace(namespace);\n\n // Process-local background tracking — see file header note. Promises\n // can't be persisted; cross-instance HITL completion happens via\n // webhook → explicit complete()/fail(), not via awaitTask.\n const backgrounds = new Map<string, Promise<void>>();\n\n return {\n scopeVersion: 1,\n async create(createOpts: {\n tool: string;\n accountId: string;\n ownerScope?: string;\n hasWebhook?: boolean;\n overrideTaskId?: string;\n }): Promise<{ taskId: string }> {\n const taskId = createOpts.overrideTaskId ?? `task_${randomUUID()}`;\n const result = await pool.query(\n `INSERT INTO ${table} (task_id, tool, account_id, owner_scope, status, has_webhook, registry_namespace) VALUES ($1, $2, $3, $4, 'submitted', $5, $6) ON CONFLICT (registry_namespace, account_id, owner_scope, task_id) DO NOTHING`,\n [\n taskId,\n createOpts.tool,\n createOpts.accountId,\n createOpts.ownerScope ?? `account:${createOpts.accountId}`,\n createOpts.hasWebhook === true,\n namespace,\n ]\n );\n if ((result.rowCount ?? 0) === 0) {\n throw new Error(`task_id already registered: ${taskId}`);\n }\n return { taskId };\n },\n\n async getTask<TResult = unknown>(taskId: string, scope: TaskRegistryScope): Promise<TaskRecord<TResult> | null> {\n const { rows } = await pool.query(\n `SELECT task_id, tool, account_id, owner_scope, status, status_message, result, error, progress, has_webhook, created_at, updated_at\n FROM ${table} WHERE task_id = $1 AND registry_namespace = $2 AND account_id = $3 AND owner_scope = $4`,\n [taskId, namespace, scope.accountId, scope.ownerScope]\n );\n if (rows.length === 0) return null;\n return rowToRecord<TResult>(rows[0] as unknown as DbTaskRow);\n },\n\n async _getTaskUnsafe<TResult = unknown>(taskId: string): Promise<TaskRecord<TResult> | null> {\n const { rows } = await pool.query(\n `SELECT task_id, tool, account_id, owner_scope, status, status_message, result, error, progress, has_webhook, created_at, updated_at\n FROM ${table} WHERE task_id = $1 AND registry_namespace = $2 LIMIT 2`,\n [taskId, namespace]\n );\n if (rows.length !== 1) return null;\n return rowToRecord<TResult>(rows[0] as unknown as DbTaskRow);\n },\n\n async list(listOpts: { accountId: string; ownerScope: string }): Promise<{ tasks: TaskRecord[] }> {\n const { rows } = await pool.query(\n `SELECT task_id, tool, account_id, owner_scope, status, status_message, result, error, progress, has_webhook, created_at, updated_at\n FROM ${table}\n WHERE registry_namespace = $1 AND account_id = $2 AND owner_scope = $3\n ORDER BY created_at DESC, task_id DESC`,\n [namespace, listOpts.accountId, listOpts.ownerScope]\n );\n return { tasks: rows.map(row => rowToRecord(row as unknown as DbTaskRow)) };\n },\n\n async complete<TResult>(taskId: string, scope: TaskRegistryScope, result: TResult): Promise<void> {\n const json = safeStringify(result, taskId);\n assertResultSize(json, taskId);\n await pool.query(\n `UPDATE ${table}\n SET status = 'completed', result = $5::jsonb, updated_at = NOW()\n WHERE task_id = $1 AND registry_namespace = $2 AND account_id = $3 AND owner_scope = $4\n AND status NOT IN ('completed', 'failed', 'rejected', 'canceled')`,\n [taskId, namespace, scope.accountId, scope.ownerScope, json]\n );\n },\n\n async fail(taskId: string, scope: TaskRegistryScope, error: AdcpStructuredError, result?: unknown): Promise<void> {\n const errorJson = safeStringify(error, taskId);\n const resultJson = result === undefined ? undefined : safeStringify(result, taskId);\n assertResultSize(errorJson, taskId);\n if (resultJson !== undefined) assertResultSize(resultJson, taskId);\n await pool.query(\n `UPDATE ${table}\n SET status = 'failed', error = $5::jsonb, result = $6::jsonb, status_message = $7, updated_at = NOW()\n WHERE task_id = $1 AND registry_namespace = $2 AND account_id = $3 AND owner_scope = $4\n AND status NOT IN ('completed', 'failed', 'rejected', 'canceled')`,\n [taskId, namespace, scope.accountId, scope.ownerScope, errorJson, resultJson ?? null, error.message]\n );\n },\n\n async updateProgress(taskId: string, scope: TaskRegistryScope, progress: TaskHandoffProgress): Promise<void> {\n const json = safeStringify(progress, taskId);\n await pool.query(\n `UPDATE ${table}\n SET progress = $5::jsonb,\n status = CASE WHEN status = 'submitted' THEN 'working' ELSE status END,\n updated_at = NOW()\n WHERE task_id = $1 AND registry_namespace = $2 AND account_id = $3 AND owner_scope = $4\n AND status NOT IN ('completed', 'failed', 'rejected', 'canceled')`,\n [taskId, namespace, scope.accountId, scope.ownerScope, json]\n );\n },\n\n _registerBackground(taskId: string, scope: TaskRegistryScope, completion: Promise<void>): void {\n const backgroundKey = JSON.stringify([scope.accountId, scope.ownerScope, taskId]);\n const composed: Promise<void> = completion.then(\n () => {\n if (backgrounds.get(backgroundKey) === composed) backgrounds.delete(backgroundKey);\n },\n () => {\n if (backgrounds.get(backgroundKey) === composed) backgrounds.delete(backgroundKey);\n }\n );\n backgrounds.set(backgroundKey, composed);\n },\n\n async awaitTask(taskId: string, scope: TaskRegistryScope): Promise<void> {\n const pending = backgrounds.get(JSON.stringify([scope.accountId, scope.ownerScope, taskId]));\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 } satisfies TaskRegistry;\n}\n"],"mappings":"AAgDA,SAAS,kBAAkB;AA+B3B,MAAM,gBAAgB;AAGtB,MAAM,mBAAmB;AACzB,MAAM,kBAAkB;AAExB,SAAS,sBAAsB,MAAoB;AACjD,MAAI,CAAC,iBAAiB,KAAK,IAAI,KAAK,OAAO,WAAW,MAAM,MAAM,IAAI,IAAI;AACxE,UAAM,IAAI;AAAA,MACR,uBAAuB,IAAI;AAAA,IAE7B;AAAA,EACF;AACF;AAEA,SAAS,qBAAqB,WAAiD;AAC7E,MAAI,OAAO,cAAc,YAAY,CAAC,gBAAgB,KAAK,SAAS,GAAG;AACrE,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;AAcA,MAAM,mBAAmB,IAAI,OAAO;AAEpC,SAAS,iBAAiB,MAAc,QAAsB;AAE5D,MAAI,OAAO,WAAW,MAAM,MAAM,IAAI,kBAAkB;AACtD,UAAM,IAAI;AAAA,MACR,QAAQ,MAAM,+BAA+B,gBAAgB;AAAA,IAG/D;AAAA,EACF;AACF;AAQA,SAAS,cAAc,OAAgB,QAAwB;AAC7D,MAAI;AACF,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B,SAAS,KAAK;AACZ,UAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,UAAM,IAAI;AAAA,MACR,QAAQ,MAAM,oDAAoD,GAAG;AAAA,IAEvE;AAAA,EACF;AACF;AAgBO,SAAS,oCAAoC,SAA4D;AAC9G,uBAAqB,SAAS,SAAS;AACvC,QAAM,QAAQ,SAAS,aAAa;AACpC,wBAAsB,KAAK;AAC3B,QAAM,YAAY,QAAQ;AAC1B,SAAO;AAAA,6BACoB,KAAK;AAAA,8CACY,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAcxC,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iCAYa,KAAK;AAAA,OAC/B,KAAK;AAAA;AAAA,iCAEqB,KAAK;AAAA,OAC/B,KAAK;AAAA;AAAA,cAEE,KAAK;AAAA;AAAA;AAAA,cAGL,KAAK;AAAA;AAAA;AAAA,SAGV,KAAK;AAAA,8BACgB,SAAS;AAAA;AAAA;AAAA,cAGzB,KAAK;AAAA,iDAC8B,SAAS;AAAA;AAAA,SAEjD,KAAK;AAAA;AAAA;AAAA;AAAA,cAIA,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+DAO4C,KAAK;AAAA;AAAA;AAAA,wBAG5C,KAAK;AAAA,mEACsC,KAAK;AAAA,2DACb,KAAK;AAAA;AAAA;AAAA;AAAA,uBAIzC,KAAK;AAAA,wBACJ,KAAK;AAAA;AAAA,kBAEX,KAAK;AAAA,uBACA,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,iCAKK,KAAK;AAAA,OAC/B,KAAK;AAAA,EACV,KAAK;AACP;AAiBA,SAAS,YAAqB,KAAqC;AACjE,SAAO;AAAA,IACL,QAAQ,IAAI;AAAA,IACZ,MAAM,IAAI;AAAA,IACV,WAAW,IAAI;AAAA,IACf,GAAI,IAAI,cAAc,EAAE,YAAY,IAAI,YAAY,IAAI,CAAC;AAAA,IACzD,QAAQ,IAAI;AAAA,IACZ,GAAI,IAAI,mBAAmB,QAAQ,EAAE,eAAe,IAAI,eAAe;AAAA,IACvE,GAAI,IAAI,WAAW,QAAQ,IAAI,WAAW,UAAa,EAAE,QAAQ,IAAI,OAAkB;AAAA,IACvF,GAAI,IAAI,UAAU,QAAQ,IAAI,UAAU,UAAa,EAAE,OAAO,IAAI,MAAM;AAAA,IACxE,GAAI,IAAI,aAAa,QAAQ,IAAI,aAAa,UAAa,EAAE,UAAU,IAAI,SAAS;AAAA,IACpF,GAAI,IAAI,eAAe,EAAE,YAAY,KAAK;AAAA,IAC1C,WAAW,IAAI,WAAW,YAAY;AAAA,IACtC,WAAW,IAAI,WAAW,YAAY;AAAA,EACxC;AACF;AAeO,SAAS,2BAA2B,MAAuD;AAChG,QAAM,QAAQ,KAAK,aAAa;AAChC,wBAAsB,KAAK;AAC3B,QAAM,OAAO,KAAK;AAClB,QAAM,YAAY,KAAK;AACvB,uBAAqB,SAAS;AAK9B,QAAM,cAAc,oBAAI,IAA2B;AAEnD,SAAO;AAAA,IACL,cAAc;AAAA,IACd,MAAM,OAAO,YAMmB;AAC9B,YAAM,SAAS,WAAW,kBAAkB,QAAQ,WAAW,CAAC;AAChE,YAAM,SAAS,MAAM,KAAK;AAAA,QACxB,eAAe,KAAK;AAAA,QACpB;AAAA,UACE;AAAA,UACA,WAAW;AAAA,UACX,WAAW;AAAA,UACX,WAAW,cAAc,WAAW,WAAW,SAAS;AAAA,UACxD,WAAW,eAAe;AAAA,UAC1B;AAAA,QACF;AAAA,MACF;AACA,WAAK,OAAO,YAAY,OAAO,GAAG;AAChC,cAAM,IAAI,MAAM,+BAA+B,MAAM,EAAE;AAAA,MACzD;AACA,aAAO,EAAE,OAAO;AAAA,IAClB;AAAA,IAEA,MAAM,QAA2B,QAAgB,OAA+D;AAC9G,YAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,QAC1B;AAAA,gBACQ,KAAK;AAAA,QACb,CAAC,QAAQ,WAAW,MAAM,WAAW,MAAM,UAAU;AAAA,MACvD;AACA,UAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,aAAO,YAAqB,KAAK,CAAC,CAAyB;AAAA,IAC7D;AAAA,IAEA,MAAM,eAAkC,QAAqD;AAC3F,YAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,QAC1B;AAAA,gBACQ,KAAK;AAAA,QACb,CAAC,QAAQ,SAAS;AAAA,MACpB;AACA,UAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,aAAO,YAAqB,KAAK,CAAC,CAAyB;AAAA,IAC7D;AAAA,IAEA,MAAM,KAAK,UAAuF;AAChG,YAAM,EAAE,KAAK,IAAI,MAAM,KAAK;AAAA,QAC1B;AAAA,gBACQ,KAAK;AAAA;AAAA;AAAA,QAGb,CAAC,WAAW,SAAS,WAAW,SAAS,UAAU;AAAA,MACrD;AACA,aAAO,EAAE,OAAO,KAAK,IAAI,SAAO,YAAY,GAA2B,CAAC,EAAE;AAAA,IAC5E;AAAA,IAEA,MAAM,SAAkB,QAAgB,OAA0B,QAAgC;AAChG,YAAM,OAAO,cAAc,QAAQ,MAAM;AACzC,uBAAiB,MAAM,MAAM;AAC7B,YAAM,KAAK;AAAA,QACT,UAAU,KAAK;AAAA;AAAA;AAAA;AAAA,QAIf,CAAC,QAAQ,WAAW,MAAM,WAAW,MAAM,YAAY,IAAI;AAAA,MAC7D;AAAA,IACF;AAAA,IAEA,MAAM,KAAK,QAAgB,OAA0B,OAA4B,QAAiC;AAChH,YAAM,YAAY,cAAc,OAAO,MAAM;AAC7C,YAAM,aAAa,WAAW,SAAY,SAAY,cAAc,QAAQ,MAAM;AAClF,uBAAiB,WAAW,MAAM;AAClC,UAAI,eAAe,OAAW,kBAAiB,YAAY,MAAM;AACjE,YAAM,KAAK;AAAA,QACT,UAAU,KAAK;AAAA;AAAA;AAAA;AAAA,QAIf,CAAC,QAAQ,WAAW,MAAM,WAAW,MAAM,YAAY,WAAW,cAAc,MAAM,MAAM,OAAO;AAAA,MACrG;AAAA,IACF;AAAA,IAEA,MAAM,eAAe,QAAgB,OAA0B,UAA8C;AAC3G,YAAM,OAAO,cAAc,UAAU,MAAM;AAC3C,YAAM,KAAK;AAAA,QACT,UAAU,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMf,CAAC,QAAQ,WAAW,MAAM,WAAW,MAAM,YAAY,IAAI;AAAA,MAC7D;AAAA,IACF;AAAA,IAEA,oBAAoB,QAAgB,OAA0B,YAAiC;AAC7F,YAAM,gBAAgB,KAAK,UAAU,CAAC,MAAM,WAAW,MAAM,YAAY,MAAM,CAAC;AAChF,YAAM,WAA0B,WAAW;AAAA,QACzC,MAAM;AACJ,cAAI,YAAY,IAAI,aAAa,MAAM,SAAU,aAAY,OAAO,aAAa;AAAA,QACnF;AAAA,QACA,MAAM;AACJ,cAAI,YAAY,IAAI,aAAa,MAAM,SAAU,aAAY,OAAO,aAAa;AAAA,QACnF;AAAA,MACF;AACA,kBAAY,IAAI,eAAe,QAAQ;AAAA,IACzC;AAAA,IAEA,MAAM,UAAU,QAAgB,OAAyC;AACvE,YAAM,UAAU,YAAY,IAAI,KAAK,UAAU,CAAC,MAAM,WAAW,MAAM,YAAY,MAAM,CAAC,CAAC;AAC3F,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,EACF;AACF;","names":[]}
@@ -10,7 +10,7 @@
10
10
  * state (`complete` / `fail`) from the method's return/throw.
11
11
  *
12
12
  * Adopters never call into the registry directly. Wire-level `tasks/get`
13
- * integration (so buyers can poll the lifecycle) reads via `getTask`;
13
+ * integration (so buyers can poll the lifecycle) reads via scoped `getTask`;
14
14
  * test harnesses use `awaitTask` to flush the background promise
15
15
  * deterministically.
16
16
  *
@@ -64,19 +64,22 @@ export interface TaskRecord<TResult = unknown, TError extends AdcpStructuredErro
64
64
  }
65
65
  export interface TaskRegistryListOptions {
66
66
  accountId: string;
67
- /**
68
- * Caller ownership scope derived from buyer agent, credential, session,
69
- * or the single-principal account fallback. Optional for source
70
- * compatibility with older custom registries, but buyer-visible protocol
71
- * task tools pass it whenever they can identify a caller and built-in
72
- * registries fail closed when it is absent.
73
- */
74
- ownerScope?: string;
67
+ /** Caller ownership scope derived from authenticated server context. */
68
+ ownerScope: string;
75
69
  }
70
+ /** Account/principal boundary required for buyer-visible task access. */
71
+ export interface TaskRegistryScope {
72
+ accountId: string;
73
+ ownerScope: string;
74
+ }
75
+ /** Defense-in-depth boundary check for custom registry results. */
76
+ export declare function taskRecordMatchesScope(record: TaskRecord, scope: TaskRegistryScope): boolean;
76
77
  export interface TaskRegistryListResult<TResult = unknown> {
77
78
  tasks: TaskRecord<TResult>[];
78
79
  }
79
80
  export interface TaskRegistry {
81
+ /** Confirms that lifecycle methods use the account/principal-scoped v1 signatures. */
82
+ readonly scopeVersion: 1;
80
83
  /**
81
84
  * Allocate a new task record. Returns the `taskId` the framework hands
82
85
  * to `platform.xxxTask(taskId, ...)`. Initial status is `submitted`.
@@ -86,7 +89,8 @@ export interface TaskRegistry {
86
89
  *
87
90
  * `overrideTaskId` — when set, the registry uses this exact string as the
88
91
  * task id instead of minting a fresh one. Throws if the id is already
89
- * registered (uniqueness is the caller's responsibility).
92
+ * registered in the same account/principal scope (uniqueness within that
93
+ * scope is the caller's responsibility).
90
94
  */
91
95
  create(opts: {
92
96
  tool: string;
@@ -97,8 +101,10 @@ export interface TaskRegistry {
97
101
  }): Promise<{
98
102
  taskId: string;
99
103
  }>;
100
- /** Read a task by id. Returns `null` if unknown. */
101
- getTask<TResult = unknown>(taskId: string): Promise<TaskRecord<TResult> | null>;
104
+ /** Read a task within its account/principal boundary. */
105
+ getTask<TResult = unknown>(taskId: string, scope: TaskRegistryScope): Promise<TaskRecord<TResult> | null>;
106
+ /** Administrative/test access; returns null when an id is ambiguous across scopes. Never expose to buyer traffic. */
107
+ _getTaskUnsafe<TResult = unknown>(taskId: string): Promise<TaskRecord<TResult> | null>;
102
108
  /**
103
109
  * List tasks owned by a resolved account. Buyer-facing AdCP task
104
110
  * reconciliation must use this scoped surface, never the transport-level
@@ -116,25 +122,33 @@ export interface TaskRegistry {
116
122
  * Mark a task `completed` with the method's return value. No-op if the
117
123
  * task is already terminal (idempotent).
118
124
  */
119
- complete<TResult>(taskId: string, result: TResult): Promise<void>;
125
+ complete<TResult>(taskId: string, scope: TaskRegistryScope, result: TResult): Promise<void>;
120
126
  /**
121
127
  * Mark a task `failed` with the structured error and, when available, the
122
128
  * canonical terminal artifact. No-op if the task is already terminal.
123
129
  */
124
- fail(taskId: string, error: AdcpStructuredError, result?: unknown): Promise<void>;
130
+ fail(taskId: string, scope: TaskRegistryScope, error: AdcpStructuredError, result?: unknown): Promise<void>;
125
131
  /**
126
132
  * Record intermediate progress from `TaskHandoffContext.update(...)`.
127
133
  * Transitions the task from `'submitted'` → `'working'` on the first
128
134
  * call. No-op on already-terminal tasks. The `progress` payload is
129
135
  * written to the record and surfaced to buyers polling `tasks_get`.
130
136
  */
131
- updateProgress(taskId: string, progress: TaskHandoffProgress): Promise<void>;
137
+ updateProgress(taskId: string, scope: TaskRegistryScope, progress: TaskHandoffProgress): Promise<void>;
138
+ /**
139
+ * Register the background completion promise the framework spawned for
140
+ * the `*Task` invocation. Tests await this for deterministic settlement;
141
+ * production callers don't need it.
142
+ */
143
+ _registerBackground(taskId: string, scope: TaskRegistryScope, completion: Promise<void>): void;
132
144
  /**
133
145
  * Await any registered background completion for a task. Resolves
134
146
  * immediately if no background is registered or it has already settled.
135
147
  * Used by test harnesses + `tasks/get` integration.
136
148
  */
137
- awaitTask(taskId: string): Promise<void>;
149
+ awaitTask(taskId: string, scope: TaskRegistryScope): Promise<void>;
150
+ /** Administrative/test wait across every scope sharing this public task id. Never expose to buyer traffic. */
151
+ _awaitTaskUnsafe(taskId: string): Promise<void>;
138
152
  /**
139
153
  * Optional test-harness flush. In-memory registries expose this so
140
154
  * `AdcpServer.compliance.reset()` can clear hardcoded overrideTaskId
@@ -10,7 +10,7 @@
10
10
  * state (`complete` / `fail`) from the method's return/throw.
11
11
  *
12
12
  * Adopters never call into the registry directly. Wire-level `tasks/get`
13
- * integration (so buyers can poll the lifecycle) reads via `getTask`;
13
+ * integration (so buyers can poll the lifecycle) reads via scoped `getTask`;
14
14
  * test harnesses use `awaitTask` to flush the background promise
15
15
  * deterministically.
16
16
  *
@@ -64,19 +64,22 @@ export interface TaskRecord<TResult = unknown, TError extends AdcpStructuredErro
64
64
  }
65
65
  export interface TaskRegistryListOptions {
66
66
  accountId: string;
67
- /**
68
- * Caller ownership scope derived from buyer agent, credential, session,
69
- * or the single-principal account fallback. Optional for source
70
- * compatibility with older custom registries, but buyer-visible protocol
71
- * task tools pass it whenever they can identify a caller and built-in
72
- * registries fail closed when it is absent.
73
- */
74
- ownerScope?: string;
67
+ /** Caller ownership scope derived from authenticated server context. */
68
+ ownerScope: string;
75
69
  }
70
+ /** Account/principal boundary required for buyer-visible task access. */
71
+ export interface TaskRegistryScope {
72
+ accountId: string;
73
+ ownerScope: string;
74
+ }
75
+ /** Defense-in-depth boundary check for custom registry results. */
76
+ export declare function taskRecordMatchesScope(record: TaskRecord, scope: TaskRegistryScope): boolean;
76
77
  export interface TaskRegistryListResult<TResult = unknown> {
77
78
  tasks: TaskRecord<TResult>[];
78
79
  }
79
80
  export interface TaskRegistry {
81
+ /** Confirms that lifecycle methods use the account/principal-scoped v1 signatures. */
82
+ readonly scopeVersion: 1;
80
83
  /**
81
84
  * Allocate a new task record. Returns the `taskId` the framework hands
82
85
  * to `platform.xxxTask(taskId, ...)`. Initial status is `submitted`.
@@ -86,7 +89,8 @@ export interface TaskRegistry {
86
89
  *
87
90
  * `overrideTaskId` — when set, the registry uses this exact string as the
88
91
  * task id instead of minting a fresh one. Throws if the id is already
89
- * registered (uniqueness is the caller's responsibility).
92
+ * registered in the same account/principal scope (uniqueness within that
93
+ * scope is the caller's responsibility).
90
94
  */
91
95
  create(opts: {
92
96
  tool: string;
@@ -97,8 +101,10 @@ export interface TaskRegistry {
97
101
  }): Promise<{
98
102
  taskId: string;
99
103
  }>;
100
- /** Read a task by id. Returns `null` if unknown. */
101
- getTask<TResult = unknown>(taskId: string): Promise<TaskRecord<TResult> | null>;
104
+ /** Read a task within its account/principal boundary. */
105
+ getTask<TResult = unknown>(taskId: string, scope: TaskRegistryScope): Promise<TaskRecord<TResult> | null>;
106
+ /** Administrative/test access; returns null when an id is ambiguous across scopes. Never expose to buyer traffic. */
107
+ _getTaskUnsafe<TResult = unknown>(taskId: string): Promise<TaskRecord<TResult> | null>;
102
108
  /**
103
109
  * List tasks owned by a resolved account. Buyer-facing AdCP task
104
110
  * reconciliation must use this scoped surface, never the transport-level
@@ -116,25 +122,33 @@ export interface TaskRegistry {
116
122
  * Mark a task `completed` with the method's return value. No-op if the
117
123
  * task is already terminal (idempotent).
118
124
  */
119
- complete<TResult>(taskId: string, result: TResult): Promise<void>;
125
+ complete<TResult>(taskId: string, scope: TaskRegistryScope, result: TResult): Promise<void>;
120
126
  /**
121
127
  * Mark a task `failed` with the structured error and, when available, the
122
128
  * canonical terminal artifact. No-op if the task is already terminal.
123
129
  */
124
- fail(taskId: string, error: AdcpStructuredError, result?: unknown): Promise<void>;
130
+ fail(taskId: string, scope: TaskRegistryScope, error: AdcpStructuredError, result?: unknown): Promise<void>;
125
131
  /**
126
132
  * Record intermediate progress from `TaskHandoffContext.update(...)`.
127
133
  * Transitions the task from `'submitted'` → `'working'` on the first
128
134
  * call. No-op on already-terminal tasks. The `progress` payload is
129
135
  * written to the record and surfaced to buyers polling `tasks_get`.
130
136
  */
131
- updateProgress(taskId: string, progress: TaskHandoffProgress): Promise<void>;
137
+ updateProgress(taskId: string, scope: TaskRegistryScope, progress: TaskHandoffProgress): Promise<void>;
138
+ /**
139
+ * Register the background completion promise the framework spawned for
140
+ * the `*Task` invocation. Tests await this for deterministic settlement;
141
+ * production callers don't need it.
142
+ */
143
+ _registerBackground(taskId: string, scope: TaskRegistryScope, completion: Promise<void>): void;
132
144
  /**
133
145
  * Await any registered background completion for a task. Resolves
134
146
  * immediately if no background is registered or it has already settled.
135
147
  * Used by test harnesses + `tasks/get` integration.
136
148
  */
137
- awaitTask(taskId: string): Promise<void>;
149
+ awaitTask(taskId: string, scope: TaskRegistryScope): Promise<void>;
150
+ /** Administrative/test wait across every scope sharing this public task id. Never expose to buyer traffic. */
151
+ _awaitTaskUnsafe(taskId: string): Promise<void>;
138
152
  /**
139
153
  * Optional test-harness flush. In-memory registries expose this so
140
154
  * `AdcpServer.compliance.reset()` can clear hardcoded overrideTaskId
@@ -1 +1 @@
1
- {"version":3,"file":"task-registry.d.ts","sourceRoot":"","sources":["../../../../../src/lib/server/decisioning/runtime/task-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAEjF;;;;;;;;GAQG;AACH,MAAM,MAAM,UAAU,GAClB,WAAW,GACX,SAAS,GACT,gBAAgB,GAChB,WAAW,GACX,UAAU,GACV,QAAQ,GACR,UAAU,GACV,eAAe,GACf,SAAS,CAAC;AAQd,MAAM,WAAW,UAAU,CAAC,OAAO,GAAG,OAAO,EAAE,MAAM,SAAS,mBAAmB,GAAG,mBAAmB;IACrG,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,iFAAiF;IACjF,SAAS,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,MAAM,EAAE,UAAU,CAAC;IACnB,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,2BAA2B;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,sBAAsB,CAAC,OAAO,GAAG,OAAO;IACvD,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;CAC9B;AAMD,MAAM,WAAW,YAAY;IAC3B;;;;;;;;;;OAUG;IACH,MAAM,CAAC,IAAI,EAAE;QACX,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEhC,oDAAoD;IACpD,OAAO,CAAC,OAAO,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;IAEhF;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,CAAC,IAAI,EAAE,uBAAuB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAEtE;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAElE;;;OAGG;IACH,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAElF;;;;;OAKG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAW7E;;;;OAIG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzC;;;;;OAKG;IACH,KAAK,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChC;AAED,wBAAgB,0BAA0B,IAAI,YAAY,CAkGzD"}
1
+ {"version":3,"file":"task-registry.d.ts","sourceRoot":"","sources":["../../../../../src/lib/server/decisioning/runtime/task-registry.ts"],"names":[],"mappings":"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"}