@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.
- package/dist/lib/index.d.mts +3 -3
- package/dist/lib/index.d.ts +3 -3
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +2 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/index.mjs +3 -1
- package/dist/lib/index.mjs.map +1 -1
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/create-adcp-server.d.mts +2 -2
- package/dist/lib/server/create-adcp-server.d.ts +2 -2
- package/dist/lib/server/create-adcp-server.d.ts.map +1 -1
- package/dist/lib/server/create-adcp-server.js +6 -1
- package/dist/lib/server/create-adcp-server.js.map +1 -1
- package/dist/lib/server/create-adcp-server.mjs +6 -1
- package/dist/lib/server/create-adcp-server.mjs.map +1 -1
- package/dist/lib/server/decisioning/index.d.mts +3 -3
- package/dist/lib/server/decisioning/index.d.ts +3 -3
- package/dist/lib/server/decisioning/index.d.ts.map +1 -1
- package/dist/lib/server/decisioning/index.js.map +1 -1
- package/dist/lib/server/decisioning/index.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/from-platform.d.mts +24 -17
- package/dist/lib/server/decisioning/runtime/from-platform.d.ts +24 -17
- package/dist/lib/server/decisioning/runtime/from-platform.d.ts.map +1 -1
- package/dist/lib/server/decisioning/runtime/from-platform.js +71 -42
- package/dist/lib/server/decisioning/runtime/from-platform.js.map +1 -1
- package/dist/lib/server/decisioning/runtime/from-platform.mjs +75 -43
- package/dist/lib/server/decisioning/runtime/from-platform.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/postgres-task-registry.d.mts +15 -21
- package/dist/lib/server/decisioning/runtime/postgres-task-registry.d.ts +15 -21
- package/dist/lib/server/decisioning/runtime/postgres-task-registry.d.ts.map +1 -1
- package/dist/lib/server/decisioning/runtime/postgres-task-registry.js +100 -30
- package/dist/lib/server/decisioning/runtime/postgres-task-registry.js.map +1 -1
- package/dist/lib/server/decisioning/runtime/postgres-task-registry.mjs +100 -30
- package/dist/lib/server/decisioning/runtime/postgres-task-registry.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/task-registry.d.mts +30 -16
- package/dist/lib/server/decisioning/runtime/task-registry.d.ts +30 -16
- package/dist/lib/server/decisioning/runtime/task-registry.d.ts.map +1 -1
- package/dist/lib/server/decisioning/runtime/task-registry.js +40 -20
- package/dist/lib/server/decisioning/runtime/task-registry.js.map +1 -1
- package/dist/lib/server/decisioning/runtime/task-registry.mjs +38 -19
- package/dist/lib/server/decisioning/runtime/task-registry.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/to-context.d.mts +2 -2
- package/dist/lib/server/decisioning/runtime/to-context.d.ts +2 -2
- package/dist/lib/server/decisioning/runtime/to-context.d.ts.map +1 -1
- package/dist/lib/server/decisioning/runtime/to-context.js +2 -2
- package/dist/lib/server/decisioning/runtime/to-context.js.map +1 -1
- package/dist/lib/server/decisioning/runtime/to-context.mjs +2 -2
- package/dist/lib/server/decisioning/runtime/to-context.mjs.map +1 -1
- package/dist/lib/server/decisioning/specialisms/creative-ad-server.d.mts +3 -2
- package/dist/lib/server/decisioning/specialisms/creative-ad-server.d.ts +3 -2
- package/dist/lib/server/decisioning/specialisms/creative-ad-server.d.ts.map +1 -1
- package/dist/lib/server/decisioning/specialisms/creative-ad-server.js.map +1 -1
- package/dist/lib/server/decisioning/specialisms/creative.d.mts +10 -10
- package/dist/lib/server/decisioning/specialisms/creative.d.ts +10 -10
- package/dist/lib/server/decisioning/specialisms/creative.d.ts.map +1 -1
- package/dist/lib/server/decisioning/specialisms/creative.js.map +1 -1
- package/dist/lib/server/index.d.mts +1 -1
- package/dist/lib/server/index.d.ts +1 -1
- package/dist/lib/server/index.d.ts.map +1 -1
- package/dist/lib/server/index.js.map +1 -1
- package/dist/lib/server/index.mjs.map +1 -1
- package/dist/lib/testing/storyboard/default-invariants.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/default-invariants.js +11 -2
- package/dist/lib/testing/storyboard/default-invariants.js.map +1 -1
- package/dist/lib/testing/storyboard/default-invariants.mjs +11 -2
- package/dist/lib/testing/storyboard/default-invariants.mjs.map +1 -1
- package/dist/lib/testing/storyboard/index.d.mts +1 -1
- package/dist/lib/testing/storyboard/index.d.ts +1 -1
- package/dist/lib/testing/storyboard/index.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/index.js.map +1 -1
- package/dist/lib/testing/storyboard/index.mjs.map +1 -1
- package/dist/lib/testing/storyboard/loader.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/loader.js +14 -0
- package/dist/lib/testing/storyboard/loader.js.map +1 -1
- package/dist/lib/testing/storyboard/loader.mjs +14 -0
- package/dist/lib/testing/storyboard/loader.mjs.map +1 -1
- package/dist/lib/testing/storyboard/phase-condition.d.mts +41 -0
- package/dist/lib/testing/storyboard/phase-condition.d.ts +42 -0
- package/dist/lib/testing/storyboard/phase-condition.d.ts.map +1 -0
- package/dist/lib/testing/storyboard/phase-condition.js +215 -0
- package/dist/lib/testing/storyboard/phase-condition.js.map +1 -0
- package/dist/lib/testing/storyboard/phase-condition.mjs +189 -0
- package/dist/lib/testing/storyboard/phase-condition.mjs.map +1 -0
- package/dist/lib/testing/storyboard/request-builder.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/request-builder.js +15 -2
- package/dist/lib/testing/storyboard/request-builder.js.map +1 -1
- package/dist/lib/testing/storyboard/request-builder.mjs +15 -2
- package/dist/lib/testing/storyboard/request-builder.mjs.map +1 -1
- package/dist/lib/testing/storyboard/runner.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/runner.js +57 -23
- package/dist/lib/testing/storyboard/runner.js.map +1 -1
- package/dist/lib/testing/storyboard/runner.mjs +57 -23
- package/dist/lib/testing/storyboard/runner.mjs.map +1 -1
- package/dist/lib/testing/storyboard/task-map.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/task-map.js +6 -2
- package/dist/lib/testing/storyboard/task-map.js.map +1 -1
- package/dist/lib/testing/storyboard/task-map.mjs +6 -2
- package/dist/lib/testing/storyboard/task-map.mjs.map +1 -1
- package/dist/lib/testing/storyboard/types.d.mts +5 -4
- package/dist/lib/testing/storyboard/types.d.ts +5 -4
- package/dist/lib/testing/storyboard/types.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/types.js.map +1 -1
- package/dist/lib/testing/storyboard/types.mjs.map +1 -1
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/testing/storyboard/webhook-assertions.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/webhook-assertions.js +60 -4
- package/dist/lib/testing/storyboard/webhook-assertions.js.map +1 -1
- package/dist/lib/testing/storyboard/webhook-assertions.mjs +60 -4
- package/dist/lib/testing/storyboard/webhook-assertions.mjs.map +1 -1
- package/dist/lib/testing/storyboard/webhook-receiver.d.mts +15 -0
- package/dist/lib/testing/storyboard/webhook-receiver.d.ts +15 -0
- package/dist/lib/testing/storyboard/webhook-receiver.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/webhook-receiver.js +57 -6
- package/dist/lib/testing/storyboard/webhook-receiver.js.map +1 -1
- package/dist/lib/testing/storyboard/webhook-receiver.mjs +57 -6
- package/dist/lib/testing/storyboard/webhook-receiver.mjs.map +1 -1
- package/dist/lib/testing/types.d.mts +2 -0
- package/dist/lib/testing/types.d.ts +2 -0
- package/dist/lib/testing/types.d.ts.map +1 -1
- package/dist/lib/testing/types.js.map +1 -1
- package/dist/lib/types/core.generated.d.mts +5 -2
- package/dist/lib/types/core.generated.d.ts +5 -2
- package/dist/lib/types/core.generated.d.ts.map +1 -1
- package/dist/lib/types/core.generated.js.map +1 -1
- package/dist/lib/types/index.d.mts +1 -1
- package/dist/lib/types/index.d.ts +1 -1
- package/dist/lib/types/index.d.ts.map +1 -1
- package/dist/lib/types/index.js.map +1 -1
- package/dist/lib/types/index.mjs.map +1 -1
- package/dist/lib/types/list-transformers.d.ts +9 -5
- package/dist/lib/types/schemas.generated.d.ts +21 -298
- package/dist/lib/types/schemas.generated.d.ts.map +1 -1
- package/dist/lib/types/schemas.generated.js +5 -2
- package/dist/lib/types/schemas.generated.js.map +1 -1
- package/dist/lib/types/schemas.generated.mjs +4 -2
- package/dist/lib/types/schemas.generated.mjs.map +1 -1
- package/dist/lib/types/server-payload-aliases.d.mts +2 -1
- package/dist/lib/types/server-payload-aliases.d.ts +2 -1
- package/dist/lib/types/server-payload-aliases.d.ts.map +1 -1
- package/dist/lib/types/server-payload-aliases.js.map +1 -1
- package/dist/lib/types/tools.generated.d.mts +5 -2
- package/dist/lib/types/tools.generated.d.ts +5 -2
- package/dist/lib/types/tools.generated.d.ts.map +1 -1
- package/dist/lib/types/tools.generated.js.map +1 -1
- package/dist/lib/utils/build-creative-return-builders.d.mts +5 -1
- package/dist/lib/utils/build-creative-return-builders.d.ts +5 -1
- package/dist/lib/utils/build-creative-return-builders.d.ts.map +1 -1
- package/dist/lib/utils/build-creative-return-builders.js +9 -3
- package/dist/lib/utils/build-creative-return-builders.js.map +1 -1
- package/dist/lib/utils/build-creative-return-builders.mjs +7 -2
- package/dist/lib/utils/build-creative-return-builders.mjs.map +1 -1
- package/dist/lib/utils/response-unwrapper.js +1 -1
- package/dist/lib/utils/response-unwrapper.js.map +1 -1
- package/dist/lib/utils/response-unwrapper.mjs +1 -1
- package/dist/lib/utils/response-unwrapper.mjs.map +1 -1
- package/dist/lib/validation/schema-loader.d.ts.map +1 -1
- package/dist/lib/validation/schema-loader.js +16 -7
- package/dist/lib/validation/schema-loader.js.map +1 -1
- package/dist/lib/validation/schema-loader.mjs +16 -7
- package/dist/lib/validation/schema-loader.mjs.map +1 -1
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.js.map +1 -1
- package/dist/lib/version.mjs +3 -3
- package/dist/lib/version.mjs.map +1 -1
- package/docs/llms.txt +2 -2
- package/docs/migration-5.x-to-6.x.md +3 -2
- package/docs/migration-task-registry-scoping.md +61 -0
- package/examples/hello_seller_adapter_guaranteed.ts +1 -1
- package/package.json +4 -4
- package/skills/build-decisioning-creative-template/SKILL.md +2 -2
- package/skills/build-decisioning-platform/SKILL.md +4 -2
- 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
|
-
|
|
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
|
|
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
|
|
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 = $
|
|
137
|
-
WHERE task_id = $1 AND
|
|
138
|
-
|
|
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 = $
|
|
149
|
-
WHERE task_id = $1 AND
|
|
150
|
-
|
|
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 = $
|
|
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
|
|
161
|
-
|
|
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(
|
|
233
|
+
if (backgrounds.get(backgroundKey) === composed) backgrounds.delete(backgroundKey);
|
|
168
234
|
},
|
|
169
235
|
() => {
|
|
170
|
-
if (backgrounds.get(
|
|
236
|
+
if (backgrounds.get(backgroundKey) === composed) backgrounds.delete(backgroundKey);
|
|
171
237
|
}
|
|
172
238
|
);
|
|
173
|
-
backgrounds.set(
|
|
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":"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","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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
1
|
+
{"version":3,"file":"task-registry.d.ts","sourceRoot":"","sources":["../../../../../src/lib/server/decisioning/runtime/task-registry.ts"],"names":[],"mappings":"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"}
|