@hasna/todos 0.15.6 → 0.15.7
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/cli/cloud-router.d.ts.map +1 -1
- package/dist/cli/index.js +3011 -1325
- package/dist/contracts.js +213 -3
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1950 -26
- package/dist/mcp/index.js +2073 -371
- package/dist/mcp.js +7 -3
- package/dist/project-registration/authority.d.ts +45 -0
- package/dist/project-registration/authority.d.ts.map +1 -0
- package/dist/project-registration/backend.d.ts +83 -0
- package/dist/project-registration/backend.d.ts.map +1 -0
- package/dist/project-registration/http.d.ts +24 -0
- package/dist/project-registration/http.d.ts.map +1 -0
- package/dist/project-registration/index.d.ts +8 -0
- package/dist/project-registration/index.d.ts.map +1 -0
- package/dist/project-registration/postgres.d.ts +30 -0
- package/dist/project-registration/postgres.d.ts.map +1 -0
- package/dist/project-registration/schema.d.ts +3 -0
- package/dist/project-registration/schema.d.ts.map +1 -0
- package/dist/project-registration/sqlite.d.ts +17 -0
- package/dist/project-registration/sqlite.d.ts.map +1 -0
- package/dist/project-registration/types.d.ts +155 -0
- package/dist/project-registration/types.d.ts.map +1 -0
- package/dist/project-registration.d.ts +2 -0
- package/dist/project-registration.d.ts.map +1 -0
- package/dist/project-registration.js +17689 -0
- package/dist/registry.d.ts +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +220 -3
- package/dist/release-provenance.json +5 -5
- package/dist/server/cloud.d.ts +3 -0
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +4374 -2672
- package/dist/server/v1.d.ts +2 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage.js +206 -0
- package/package.json +7 -3
package/dist/mcp/index.js
CHANGED
|
@@ -228,6 +228,206 @@ var init_types = __esm(() => {
|
|
|
228
228
|
};
|
|
229
229
|
});
|
|
230
230
|
|
|
231
|
+
// src/project-registration/schema.ts
|
|
232
|
+
function sqliteTodosProjectRegistrationSchemaSql() {
|
|
233
|
+
return `
|
|
234
|
+
CREATE TABLE IF NOT EXISTS todos_project_registration_receipts (
|
|
235
|
+
receipt_id TEXT PRIMARY KEY,
|
|
236
|
+
authority TEXT NOT NULL CHECK(authority = 'todos'),
|
|
237
|
+
route TEXT NOT NULL,
|
|
238
|
+
package_version TEXT NOT NULL,
|
|
239
|
+
authority_id TEXT NOT NULL,
|
|
240
|
+
tenant_id TEXT NOT NULL,
|
|
241
|
+
corpus_id TEXT NOT NULL,
|
|
242
|
+
operation_id TEXT NOT NULL,
|
|
243
|
+
step_id TEXT NOT NULL,
|
|
244
|
+
resource_kind TEXT NOT NULL CHECK(resource_kind IN ('project', 'task_list')),
|
|
245
|
+
direction TEXT NOT NULL CHECK(direction IN ('forward', 'inverse')),
|
|
246
|
+
target_selector TEXT NOT NULL,
|
|
247
|
+
idempotency_key TEXT NOT NULL,
|
|
248
|
+
request_digest TEXT NOT NULL,
|
|
249
|
+
precondition_digest TEXT NOT NULL,
|
|
250
|
+
normalized_call_digest TEXT NOT NULL,
|
|
251
|
+
outcome TEXT NOT NULL CHECK(outcome IN (
|
|
252
|
+
'accepted', 'duplicate_of_accepted', 'terminal_nonacceptance'
|
|
253
|
+
)),
|
|
254
|
+
reason TEXT,
|
|
255
|
+
target_id TEXT,
|
|
256
|
+
result_revision TEXT,
|
|
257
|
+
result_digest TEXT,
|
|
258
|
+
duplicate_of_receipt_id TEXT,
|
|
259
|
+
accepted_receipt_id TEXT,
|
|
260
|
+
created_by_operation INTEGER NOT NULL CHECK(created_by_operation IN (0, 1)),
|
|
261
|
+
created_at TEXT NOT NULL
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
CREATE INDEX IF NOT EXISTS idx_todos_project_registration_receipts_lookup
|
|
265
|
+
ON todos_project_registration_receipts (
|
|
266
|
+
authority_id, tenant_id, corpus_id, operation_id, step_id,
|
|
267
|
+
resource_kind, direction, idempotency_key
|
|
268
|
+
);
|
|
269
|
+
CREATE INDEX IF NOT EXISTS idx_todos_project_registration_receipts_step
|
|
270
|
+
ON todos_project_registration_receipts (
|
|
271
|
+
authority_id, tenant_id, corpus_id, operation_id, step_id,
|
|
272
|
+
resource_kind, direction, outcome
|
|
273
|
+
);
|
|
274
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_todos_project_registration_receipts_accepted_step
|
|
275
|
+
ON todos_project_registration_receipts (
|
|
276
|
+
authority_id, tenant_id, corpus_id, operation_id, step_id,
|
|
277
|
+
resource_kind, direction
|
|
278
|
+
)
|
|
279
|
+
WHERE outcome = 'accepted';
|
|
280
|
+
CREATE INDEX IF NOT EXISTS idx_todos_project_registration_receipts_target
|
|
281
|
+
ON todos_project_registration_receipts (
|
|
282
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_id
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
CREATE TABLE IF NOT EXISTS todos_project_registration_bindings (
|
|
286
|
+
authority_id TEXT NOT NULL,
|
|
287
|
+
tenant_id TEXT NOT NULL,
|
|
288
|
+
corpus_id TEXT NOT NULL,
|
|
289
|
+
resource_kind TEXT NOT NULL CHECK(resource_kind IN ('project', 'task_list')),
|
|
290
|
+
target_selector TEXT NOT NULL,
|
|
291
|
+
operation_id TEXT NOT NULL,
|
|
292
|
+
step_id TEXT NOT NULL,
|
|
293
|
+
direction TEXT NOT NULL CHECK(direction = 'forward'),
|
|
294
|
+
idempotency_key TEXT NOT NULL,
|
|
295
|
+
request_digest TEXT NOT NULL,
|
|
296
|
+
precondition_digest TEXT NOT NULL,
|
|
297
|
+
normalized_call_digest TEXT NOT NULL,
|
|
298
|
+
state TEXT NOT NULL CHECK(state IN (
|
|
299
|
+
'pending', 'accepted', 'terminal_nonacceptance', 'removed'
|
|
300
|
+
)),
|
|
301
|
+
target_id TEXT,
|
|
302
|
+
accepted_receipt_id TEXT,
|
|
303
|
+
result_revision TEXT,
|
|
304
|
+
result_digest TEXT,
|
|
305
|
+
removed_receipt_id TEXT,
|
|
306
|
+
created_at TEXT NOT NULL,
|
|
307
|
+
updated_at TEXT NOT NULL,
|
|
308
|
+
PRIMARY KEY(
|
|
309
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_selector
|
|
310
|
+
),
|
|
311
|
+
UNIQUE(accepted_receipt_id)
|
|
312
|
+
);
|
|
313
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_todos_project_registration_binding_target
|
|
314
|
+
ON todos_project_registration_bindings(
|
|
315
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_id
|
|
316
|
+
)
|
|
317
|
+
WHERE target_id IS NOT NULL;
|
|
318
|
+
|
|
319
|
+
CREATE TRIGGER IF NOT EXISTS todos_project_registration_receipts_immutable_update
|
|
320
|
+
BEFORE UPDATE ON todos_project_registration_receipts
|
|
321
|
+
BEGIN
|
|
322
|
+
SELECT RAISE(ABORT, 'todos project registration receipts are immutable');
|
|
323
|
+
END;
|
|
324
|
+
|
|
325
|
+
CREATE TRIGGER IF NOT EXISTS todos_project_registration_receipts_immutable_delete
|
|
326
|
+
BEFORE DELETE ON todos_project_registration_receipts
|
|
327
|
+
BEGIN
|
|
328
|
+
SELECT RAISE(ABORT, 'todos project registration receipts are immutable');
|
|
329
|
+
END;
|
|
330
|
+
`;
|
|
331
|
+
}
|
|
332
|
+
function postgresTodosProjectRegistrationSchemaSql() {
|
|
333
|
+
return [
|
|
334
|
+
`CREATE TABLE IF NOT EXISTS todos_project_registration_receipts (
|
|
335
|
+
receipt_id text PRIMARY KEY,
|
|
336
|
+
authority text NOT NULL CHECK(authority = 'todos'),
|
|
337
|
+
route text NOT NULL,
|
|
338
|
+
package_version text NOT NULL,
|
|
339
|
+
authority_id text NOT NULL,
|
|
340
|
+
tenant_id text NOT NULL,
|
|
341
|
+
corpus_id text NOT NULL,
|
|
342
|
+
operation_id text NOT NULL,
|
|
343
|
+
step_id text NOT NULL,
|
|
344
|
+
resource_kind text NOT NULL CHECK(resource_kind IN ('project', 'task_list')),
|
|
345
|
+
direction text NOT NULL CHECK(direction IN ('forward', 'inverse')),
|
|
346
|
+
target_selector text NOT NULL,
|
|
347
|
+
idempotency_key text NOT NULL,
|
|
348
|
+
request_digest text NOT NULL,
|
|
349
|
+
precondition_digest text NOT NULL,
|
|
350
|
+
normalized_call_digest text NOT NULL,
|
|
351
|
+
outcome text NOT NULL CHECK(outcome IN (
|
|
352
|
+
'accepted', 'duplicate_of_accepted', 'terminal_nonacceptance'
|
|
353
|
+
)),
|
|
354
|
+
reason text,
|
|
355
|
+
target_id text,
|
|
356
|
+
result_revision text,
|
|
357
|
+
result_digest text,
|
|
358
|
+
duplicate_of_receipt_id text,
|
|
359
|
+
accepted_receipt_id text,
|
|
360
|
+
created_by_operation boolean NOT NULL,
|
|
361
|
+
created_at timestamptz NOT NULL
|
|
362
|
+
)`,
|
|
363
|
+
`CREATE INDEX IF NOT EXISTS todos_project_registration_receipts_lookup_idx
|
|
364
|
+
ON todos_project_registration_receipts (
|
|
365
|
+
authority_id, tenant_id, corpus_id, operation_id, step_id,
|
|
366
|
+
resource_kind, direction, idempotency_key
|
|
367
|
+
)`,
|
|
368
|
+
`CREATE INDEX IF NOT EXISTS todos_project_registration_receipts_step_idx
|
|
369
|
+
ON todos_project_registration_receipts (
|
|
370
|
+
authority_id, tenant_id, corpus_id, operation_id, step_id,
|
|
371
|
+
resource_kind, direction, outcome
|
|
372
|
+
)`,
|
|
373
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS todos_project_registration_receipts_accepted_step_uidx
|
|
374
|
+
ON todos_project_registration_receipts (
|
|
375
|
+
authority_id, tenant_id, corpus_id, operation_id, step_id,
|
|
376
|
+
resource_kind, direction
|
|
377
|
+
)
|
|
378
|
+
WHERE outcome = 'accepted'`,
|
|
379
|
+
`CREATE INDEX IF NOT EXISTS todos_project_registration_receipts_target_idx
|
|
380
|
+
ON todos_project_registration_receipts (
|
|
381
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_id
|
|
382
|
+
)`,
|
|
383
|
+
`CREATE TABLE IF NOT EXISTS todos_project_registration_bindings (
|
|
384
|
+
authority_id text NOT NULL,
|
|
385
|
+
tenant_id text NOT NULL,
|
|
386
|
+
corpus_id text NOT NULL,
|
|
387
|
+
resource_kind text NOT NULL CHECK(resource_kind IN ('project', 'task_list')),
|
|
388
|
+
target_selector text NOT NULL,
|
|
389
|
+
operation_id text NOT NULL,
|
|
390
|
+
step_id text NOT NULL,
|
|
391
|
+
direction text NOT NULL CHECK(direction = 'forward'),
|
|
392
|
+
idempotency_key text NOT NULL,
|
|
393
|
+
request_digest text NOT NULL,
|
|
394
|
+
precondition_digest text NOT NULL,
|
|
395
|
+
normalized_call_digest text NOT NULL,
|
|
396
|
+
state text NOT NULL CHECK(state IN (
|
|
397
|
+
'pending', 'accepted', 'terminal_nonacceptance', 'removed'
|
|
398
|
+
)),
|
|
399
|
+
target_id text,
|
|
400
|
+
accepted_receipt_id text UNIQUE,
|
|
401
|
+
result_revision text,
|
|
402
|
+
result_digest text,
|
|
403
|
+
removed_receipt_id text,
|
|
404
|
+
created_at timestamptz NOT NULL,
|
|
405
|
+
updated_at timestamptz NOT NULL,
|
|
406
|
+
PRIMARY KEY(
|
|
407
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_selector
|
|
408
|
+
)
|
|
409
|
+
)`,
|
|
410
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS todos_project_registration_binding_target_uidx
|
|
411
|
+
ON todos_project_registration_bindings(
|
|
412
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_id
|
|
413
|
+
)
|
|
414
|
+
WHERE target_id IS NOT NULL`,
|
|
415
|
+
`CREATE OR REPLACE FUNCTION todos_project_registration_receipts_immutable()
|
|
416
|
+
RETURNS trigger
|
|
417
|
+
LANGUAGE plpgsql
|
|
418
|
+
AS $$
|
|
419
|
+
BEGIN
|
|
420
|
+
RAISE EXCEPTION 'todos project registration receipts are immutable';
|
|
421
|
+
END;
|
|
422
|
+
$$`,
|
|
423
|
+
`DROP TRIGGER IF EXISTS todos_project_registration_receipts_immutable
|
|
424
|
+
ON todos_project_registration_receipts`,
|
|
425
|
+
`CREATE TRIGGER todos_project_registration_receipts_immutable
|
|
426
|
+
BEFORE UPDATE OR DELETE ON todos_project_registration_receipts
|
|
427
|
+
FOR EACH ROW EXECUTE FUNCTION todos_project_registration_receipts_immutable()`
|
|
428
|
+
];
|
|
429
|
+
}
|
|
430
|
+
|
|
231
431
|
// src/db/migrations.ts
|
|
232
432
|
var MIGRATIONS;
|
|
233
433
|
var init_migrations = __esm(() => {
|
|
@@ -1833,6 +2033,11 @@ var init_migrations = __esm(() => {
|
|
|
1833
2033
|
INSERT OR IGNORE INTO _migrations (id) VALUES (68);
|
|
1834
2034
|
COMMIT;
|
|
1835
2035
|
PRAGMA foreign_keys = ON;
|
|
2036
|
+
`,
|
|
2037
|
+
`BEGIN;
|
|
2038
|
+
${sqliteTodosProjectRegistrationSchemaSql()}
|
|
2039
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (69);
|
|
2040
|
+
COMMIT;
|
|
1836
2041
|
`
|
|
1837
2042
|
];
|
|
1838
2043
|
});
|
|
@@ -3035,6 +3240,7 @@ function ensureSchema(db) {
|
|
|
3035
3240
|
)`);
|
|
3036
3241
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_api_keys_prefix ON api_keys(prefix)");
|
|
3037
3242
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_api_keys_active ON api_keys(revoked_at, expires_at)");
|
|
3243
|
+
db.exec(sqliteTodosProjectRegistrationSchemaSql());
|
|
3038
3244
|
ensureTable("pr_groups", `
|
|
3039
3245
|
CREATE TABLE pr_groups (
|
|
3040
3246
|
schema_version INTEGER NOT NULL DEFAULT 1,
|
|
@@ -20684,6 +20890,16 @@ async function cloudListProjects(client) {
|
|
|
20684
20890
|
const envelope = res.raw;
|
|
20685
20891
|
return Array.isArray(envelope?.projects) ? envelope.projects : res.items;
|
|
20686
20892
|
}
|
|
20893
|
+
function unwrapProject(raw) {
|
|
20894
|
+
if (raw && typeof raw === "object" && "project" in raw) {
|
|
20895
|
+
return raw.project;
|
|
20896
|
+
}
|
|
20897
|
+
return raw;
|
|
20898
|
+
}
|
|
20899
|
+
async function cloudGetProjectById(client, id) {
|
|
20900
|
+
const raw = await client.get("projects", id);
|
|
20901
|
+
return raw == null ? null : unwrapProject(raw);
|
|
20902
|
+
}
|
|
20687
20903
|
function cloudProjectSlug(value) {
|
|
20688
20904
|
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
20689
20905
|
}
|
|
@@ -20790,29 +21006,63 @@ async function cloudListTaskLists(client, projectId) {
|
|
|
20790
21006
|
return envelope.taskLists;
|
|
20791
21007
|
return Array.isArray(raw) ? raw : [];
|
|
20792
21008
|
}
|
|
21009
|
+
function unwrapTaskList(raw) {
|
|
21010
|
+
if (raw && typeof raw === "object" && "task_list" in raw) {
|
|
21011
|
+
return raw.task_list;
|
|
21012
|
+
}
|
|
21013
|
+
return raw;
|
|
21014
|
+
}
|
|
21015
|
+
async function cloudGetTaskList(client, id) {
|
|
21016
|
+
const raw = await client.get("task-lists", id);
|
|
21017
|
+
return raw == null ? null : unwrapTaskList(raw);
|
|
21018
|
+
}
|
|
21019
|
+
function resolveTaskListFromCandidates(lists, input) {
|
|
21020
|
+
const normalizedIdRef = input.toLowerCase();
|
|
21021
|
+
const matchGroups = [
|
|
21022
|
+
lists.filter((list) => list.id.toLowerCase() === normalizedIdRef),
|
|
21023
|
+
lists.filter((list) => list.slug === input),
|
|
21024
|
+
lists.filter((list) => list.id.toLowerCase().startsWith(normalizedIdRef))
|
|
21025
|
+
];
|
|
21026
|
+
for (const matches of matchGroups) {
|
|
21027
|
+
if (matches.length === 1)
|
|
21028
|
+
return matches[0].id;
|
|
21029
|
+
if (matches.length > 1) {
|
|
21030
|
+
throw new Error(`Task list reference is ambiguous: "${input}"`);
|
|
21031
|
+
}
|
|
21032
|
+
}
|
|
21033
|
+
return null;
|
|
21034
|
+
}
|
|
21035
|
+
async function legacyProjectTaskLists(client, projectId) {
|
|
21036
|
+
const project = await cloudGetProjectById(client, projectId);
|
|
21037
|
+
if (!project?.task_list_id)
|
|
21038
|
+
return [];
|
|
21039
|
+
return (await cloudListTaskLists(client)).filter((list) => list.project_id == null && list.slug === project.task_list_id);
|
|
21040
|
+
}
|
|
20793
21041
|
async function cloudResolveTaskListRef(client, ref, projectId) {
|
|
20794
21042
|
const input = ref.trim();
|
|
20795
21043
|
const normalizedIdRef = input.toLowerCase();
|
|
20796
21044
|
if (UUID_RE.test(input) && !projectId)
|
|
20797
21045
|
return normalizedIdRef;
|
|
20798
|
-
|
|
20799
|
-
|
|
20800
|
-
|
|
20801
|
-
|
|
20802
|
-
|
|
20803
|
-
|
|
20804
|
-
|
|
20805
|
-
|
|
20806
|
-
|
|
20807
|
-
|
|
20808
|
-
|
|
20809
|
-
|
|
20810
|
-
}
|
|
20811
|
-
const
|
|
20812
|
-
if (
|
|
20813
|
-
return
|
|
20814
|
-
if (
|
|
20815
|
-
|
|
21046
|
+
if (UUID_RE.test(input) && projectId) {
|
|
21047
|
+
const direct = await cloudGetTaskList(client, normalizedIdRef);
|
|
21048
|
+
if (direct?.id?.toLowerCase() === normalizedIdRef) {
|
|
21049
|
+
if (direct.project_id === projectId)
|
|
21050
|
+
return direct.id;
|
|
21051
|
+
if (direct.project_id == null) {
|
|
21052
|
+
const project = await cloudGetProjectById(client, projectId);
|
|
21053
|
+
if (project?.task_list_id === direct.slug)
|
|
21054
|
+
return direct.id;
|
|
21055
|
+
}
|
|
21056
|
+
throw new Error(`Task list not found: "${input}"`);
|
|
21057
|
+
}
|
|
21058
|
+
}
|
|
21059
|
+
const scopedMatch = resolveTaskListFromCandidates(await cloudListTaskLists(client, projectId), input);
|
|
21060
|
+
if (scopedMatch)
|
|
21061
|
+
return scopedMatch;
|
|
21062
|
+
if (projectId) {
|
|
21063
|
+
const legacyMatch = resolveTaskListFromCandidates(await legacyProjectTaskLists(client, projectId), input);
|
|
21064
|
+
if (legacyMatch)
|
|
21065
|
+
return legacyMatch;
|
|
20816
21066
|
}
|
|
20817
21067
|
throw new Error(`Task list not found: "${input}"`);
|
|
20818
21068
|
}
|
|
@@ -34926,7 +35176,7 @@ var package_default;
|
|
|
34926
35176
|
var init_package = __esm(() => {
|
|
34927
35177
|
package_default = {
|
|
34928
35178
|
name: "@hasna/todos",
|
|
34929
|
-
version: "0.15.
|
|
35179
|
+
version: "0.15.7",
|
|
34930
35180
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
34931
35181
|
type: "module",
|
|
34932
35182
|
main: "dist/index.js",
|
|
@@ -34964,6 +35214,10 @@ var init_package = __esm(() => {
|
|
|
34964
35214
|
"./testing": {
|
|
34965
35215
|
types: "./dist/testing.d.ts",
|
|
34966
35216
|
import: "./dist/testing.js"
|
|
35217
|
+
},
|
|
35218
|
+
"./project-registration": {
|
|
35219
|
+
types: "./dist/project-registration.d.ts",
|
|
35220
|
+
import: "./dist/project-registration.js"
|
|
34967
35221
|
}
|
|
34968
35222
|
},
|
|
34969
35223
|
workspaces: [
|
|
@@ -34976,8 +35230,8 @@ var init_package = __esm(() => {
|
|
|
34976
35230
|
"README.md"
|
|
34977
35231
|
],
|
|
34978
35232
|
scripts: {
|
|
34979
|
-
build: "rm -rf dist dashboard/dist && cd dashboard && bun install --frozen-lockfile && bun run build && cd .. && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && tsc --emitDeclarationOnly --outDir dist",
|
|
34980
|
-
"build:server": "rm -rf dist && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*'",
|
|
35233
|
+
build: "rm -rf dist dashboard/dist && cd dashboard && bun install --frozen-lockfile && bun run build && cd .. && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts src/project-registration.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && tsc --emitDeclarationOnly --outDir dist",
|
|
35234
|
+
"build:server": "rm -rf dist && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts src/project-registration.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*'",
|
|
34981
35235
|
migrate: "bun run src/server/index.ts migrate",
|
|
34982
35236
|
"backfill:comment-redaction": "bun run src/server/index.ts redact-comments",
|
|
34983
35237
|
"generate:sdk": "bun run scripts/generate-sdk.ts",
|
|
@@ -47723,6 +47977,1433 @@ var init_postgres = __esm(() => {
|
|
|
47723
47977
|
init_types3();
|
|
47724
47978
|
});
|
|
47725
47979
|
|
|
47980
|
+
// src/project-registration/types.ts
|
|
47981
|
+
var TODOS_PROJECT_REGISTRATION_ROUTE = "todos.project-registration.v1", TODOS_PROJECT_REGISTRATION_CALLER_ROUTE = "projects.full-registration.v1", TodosProjectRegistrationError;
|
|
47982
|
+
var init_types4 = __esm(() => {
|
|
47983
|
+
TodosProjectRegistrationError = class TodosProjectRegistrationError extends Error {
|
|
47984
|
+
code;
|
|
47985
|
+
details;
|
|
47986
|
+
constructor(code, message, details = {}) {
|
|
47987
|
+
super(message);
|
|
47988
|
+
this.code = code;
|
|
47989
|
+
this.details = details;
|
|
47990
|
+
this.name = "TodosProjectRegistrationError";
|
|
47991
|
+
}
|
|
47992
|
+
};
|
|
47993
|
+
});
|
|
47994
|
+
|
|
47995
|
+
// src/project-registration/postgres.ts
|
|
47996
|
+
function safeIdentifier(value, field) {
|
|
47997
|
+
if (!/^[a-z_][a-z0-9_]*$/.test(value)) {
|
|
47998
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field} must be a safe PostgreSQL identifier`);
|
|
47999
|
+
}
|
|
48000
|
+
return value;
|
|
48001
|
+
}
|
|
48002
|
+
function normalizeTimestamp2(value) {
|
|
48003
|
+
return value instanceof Date ? value.toISOString() : new Date(String(value)).toISOString();
|
|
48004
|
+
}
|
|
48005
|
+
function parsePayload3(value) {
|
|
48006
|
+
if (typeof value === "string")
|
|
48007
|
+
return JSON.parse(value);
|
|
48008
|
+
return value;
|
|
48009
|
+
}
|
|
48010
|
+
function receiptFromRow(row) {
|
|
48011
|
+
return {
|
|
48012
|
+
...row,
|
|
48013
|
+
authority: "todos",
|
|
48014
|
+
created_by_operation: Boolean(row["created_by_operation"]),
|
|
48015
|
+
created_at: normalizeTimestamp2(row["created_at"])
|
|
48016
|
+
};
|
|
48017
|
+
}
|
|
48018
|
+
function bindingFromRow(row) {
|
|
48019
|
+
return {
|
|
48020
|
+
...row,
|
|
48021
|
+
created_at: normalizeTimestamp2(row["created_at"]),
|
|
48022
|
+
updated_at: normalizeTimestamp2(row["updated_at"])
|
|
48023
|
+
};
|
|
48024
|
+
}
|
|
48025
|
+
|
|
48026
|
+
class PostgresTodosProjectRegistrationTransaction {
|
|
48027
|
+
client;
|
|
48028
|
+
service;
|
|
48029
|
+
tableName;
|
|
48030
|
+
storage;
|
|
48031
|
+
constructor(client, service, tableName, cursorTableName) {
|
|
48032
|
+
this.client = client;
|
|
48033
|
+
this.service = service;
|
|
48034
|
+
this.tableName = tableName;
|
|
48035
|
+
this.storage = createPostgresTodosStorageAdapter({
|
|
48036
|
+
client,
|
|
48037
|
+
service,
|
|
48038
|
+
tableName,
|
|
48039
|
+
cursorTableName
|
|
48040
|
+
});
|
|
48041
|
+
}
|
|
48042
|
+
async lockStep(identity) {
|
|
48043
|
+
const key = [
|
|
48044
|
+
identity.authority_id,
|
|
48045
|
+
identity.tenant_id,
|
|
48046
|
+
identity.corpus_id,
|
|
48047
|
+
identity.operation_id,
|
|
48048
|
+
identity.step_id,
|
|
48049
|
+
identity.resource_kind,
|
|
48050
|
+
identity.direction
|
|
48051
|
+
].join("\x1F");
|
|
48052
|
+
await this.client.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [key]);
|
|
48053
|
+
}
|
|
48054
|
+
async getReceiptForLookup(identity) {
|
|
48055
|
+
const result = await this.client.query(`
|
|
48056
|
+
SELECT * FROM todos_project_registration_receipts
|
|
48057
|
+
WHERE authority_id = $1 AND tenant_id = $2 AND corpus_id = $3
|
|
48058
|
+
AND operation_id = $4 AND step_id = $5 AND resource_kind = $6
|
|
48059
|
+
AND direction = $7 AND idempotency_key = $8 AND target_selector = $9
|
|
48060
|
+
ORDER BY CASE outcome
|
|
48061
|
+
WHEN 'terminal_nonacceptance' THEN 0
|
|
48062
|
+
WHEN 'duplicate_of_accepted' THEN 1
|
|
48063
|
+
ELSE 2
|
|
48064
|
+
END, created_at DESC, receipt_id DESC
|
|
48065
|
+
LIMIT 1
|
|
48066
|
+
`, [
|
|
48067
|
+
identity.authority_id,
|
|
48068
|
+
identity.tenant_id,
|
|
48069
|
+
identity.corpus_id,
|
|
48070
|
+
identity.operation_id,
|
|
48071
|
+
identity.step_id,
|
|
48072
|
+
identity.resource_kind,
|
|
48073
|
+
identity.direction,
|
|
48074
|
+
identity.idempotency_key,
|
|
48075
|
+
identity.target_selector
|
|
48076
|
+
]);
|
|
48077
|
+
return result.rows[0] ? receiptFromRow(result.rows[0]) : null;
|
|
48078
|
+
}
|
|
48079
|
+
async getReceiptById(receiptId) {
|
|
48080
|
+
const result = await this.client.query("SELECT * FROM todos_project_registration_receipts WHERE receipt_id = $1 LIMIT 1", [receiptId]);
|
|
48081
|
+
return result.rows[0] ? receiptFromRow(result.rows[0]) : null;
|
|
48082
|
+
}
|
|
48083
|
+
async getAcceptedReceiptForStep(identity) {
|
|
48084
|
+
const result = await this.client.query(`
|
|
48085
|
+
SELECT * FROM todos_project_registration_receipts
|
|
48086
|
+
WHERE authority_id = $1 AND tenant_id = $2 AND corpus_id = $3
|
|
48087
|
+
AND operation_id = $4 AND step_id = $5 AND resource_kind = $6
|
|
48088
|
+
AND direction = $7 AND outcome = 'accepted'
|
|
48089
|
+
ORDER BY created_at ASC, receipt_id ASC
|
|
48090
|
+
LIMIT 1
|
|
48091
|
+
FOR UPDATE
|
|
48092
|
+
`, [
|
|
48093
|
+
identity.authority_id,
|
|
48094
|
+
identity.tenant_id,
|
|
48095
|
+
identity.corpus_id,
|
|
48096
|
+
identity.operation_id,
|
|
48097
|
+
identity.step_id,
|
|
48098
|
+
identity.resource_kind,
|
|
48099
|
+
identity.direction
|
|
48100
|
+
]);
|
|
48101
|
+
return result.rows[0] ? receiptFromRow(result.rows[0]) : null;
|
|
48102
|
+
}
|
|
48103
|
+
async insertReceipt(receipt) {
|
|
48104
|
+
const result = await this.client.query(`
|
|
48105
|
+
INSERT INTO todos_project_registration_receipts (
|
|
48106
|
+
receipt_id, authority, route, package_version, authority_id, tenant_id,
|
|
48107
|
+
corpus_id, operation_id, step_id, resource_kind, direction,
|
|
48108
|
+
target_selector, idempotency_key, request_digest, precondition_digest,
|
|
48109
|
+
normalized_call_digest, outcome, reason, target_id, result_revision,
|
|
48110
|
+
result_digest, duplicate_of_receipt_id, accepted_receipt_id,
|
|
48111
|
+
created_by_operation, created_at
|
|
48112
|
+
) VALUES (
|
|
48113
|
+
$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,
|
|
48114
|
+
$19,$20,$21,$22,$23,$24,$25
|
|
48115
|
+
)
|
|
48116
|
+
ON CONFLICT (receipt_id) DO NOTHING
|
|
48117
|
+
RETURNING receipt_id
|
|
48118
|
+
`, [
|
|
48119
|
+
receipt.receipt_id,
|
|
48120
|
+
receipt.authority,
|
|
48121
|
+
receipt.route,
|
|
48122
|
+
receipt.package_version,
|
|
48123
|
+
receipt.authority_id,
|
|
48124
|
+
receipt.tenant_id,
|
|
48125
|
+
receipt.corpus_id,
|
|
48126
|
+
receipt.operation_id,
|
|
48127
|
+
receipt.step_id,
|
|
48128
|
+
receipt.resource_kind,
|
|
48129
|
+
receipt.direction,
|
|
48130
|
+
receipt.target_selector,
|
|
48131
|
+
receipt.idempotency_key,
|
|
48132
|
+
receipt.request_digest,
|
|
48133
|
+
receipt.precondition_digest,
|
|
48134
|
+
receipt.normalized_call_digest,
|
|
48135
|
+
receipt.outcome,
|
|
48136
|
+
receipt.reason,
|
|
48137
|
+
receipt.target_id,
|
|
48138
|
+
receipt.result_revision,
|
|
48139
|
+
receipt.result_digest,
|
|
48140
|
+
receipt.duplicate_of_receipt_id,
|
|
48141
|
+
receipt.accepted_receipt_id,
|
|
48142
|
+
receipt.created_by_operation,
|
|
48143
|
+
receipt.created_at
|
|
48144
|
+
]);
|
|
48145
|
+
return result.rows.length === 1;
|
|
48146
|
+
}
|
|
48147
|
+
async getBinding(scope, resourceKind, targetSelector) {
|
|
48148
|
+
const result = await this.client.query(`
|
|
48149
|
+
SELECT * FROM todos_project_registration_bindings
|
|
48150
|
+
WHERE authority_id = $1 AND tenant_id = $2 AND corpus_id = $3
|
|
48151
|
+
AND resource_kind = $4 AND target_selector = $5
|
|
48152
|
+
LIMIT 1
|
|
48153
|
+
FOR UPDATE
|
|
48154
|
+
`, [
|
|
48155
|
+
scope.authority_id,
|
|
48156
|
+
scope.tenant_id,
|
|
48157
|
+
scope.corpus_id,
|
|
48158
|
+
resourceKind,
|
|
48159
|
+
targetSelector
|
|
48160
|
+
]);
|
|
48161
|
+
return result.rows[0] ? bindingFromRow(result.rows[0]) : null;
|
|
48162
|
+
}
|
|
48163
|
+
async claimBinding(binding) {
|
|
48164
|
+
const result = await this.client.query(`
|
|
48165
|
+
INSERT INTO todos_project_registration_bindings (
|
|
48166
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_selector,
|
|
48167
|
+
operation_id, step_id, direction, idempotency_key, request_digest,
|
|
48168
|
+
precondition_digest, normalized_call_digest, state, target_id,
|
|
48169
|
+
accepted_receipt_id, result_revision, result_digest, removed_receipt_id,
|
|
48170
|
+
created_at, updated_at
|
|
48171
|
+
) VALUES (
|
|
48172
|
+
$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,
|
|
48173
|
+
$18,$19,$20
|
|
48174
|
+
)
|
|
48175
|
+
ON CONFLICT (
|
|
48176
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_selector
|
|
48177
|
+
) DO NOTHING
|
|
48178
|
+
RETURNING target_selector
|
|
48179
|
+
`, [
|
|
48180
|
+
binding.authority_id,
|
|
48181
|
+
binding.tenant_id,
|
|
48182
|
+
binding.corpus_id,
|
|
48183
|
+
binding.resource_kind,
|
|
48184
|
+
binding.target_selector,
|
|
48185
|
+
binding.operation_id,
|
|
48186
|
+
binding.step_id,
|
|
48187
|
+
binding.direction,
|
|
48188
|
+
binding.idempotency_key,
|
|
48189
|
+
binding.request_digest,
|
|
48190
|
+
binding.precondition_digest,
|
|
48191
|
+
binding.normalized_call_digest,
|
|
48192
|
+
binding.state,
|
|
48193
|
+
binding.target_id,
|
|
48194
|
+
binding.accepted_receipt_id,
|
|
48195
|
+
binding.result_revision,
|
|
48196
|
+
binding.result_digest,
|
|
48197
|
+
binding.removed_receipt_id,
|
|
48198
|
+
binding.created_at,
|
|
48199
|
+
binding.updated_at
|
|
48200
|
+
]);
|
|
48201
|
+
return result.rows.length === 1;
|
|
48202
|
+
}
|
|
48203
|
+
async setBindingAccepted(scope, resourceKind, targetSelector, update) {
|
|
48204
|
+
const result = await this.client.query(`
|
|
48205
|
+
UPDATE todos_project_registration_bindings
|
|
48206
|
+
SET state = 'accepted', target_id = $1, accepted_receipt_id = $2,
|
|
48207
|
+
result_revision = $3, result_digest = $4, updated_at = $5
|
|
48208
|
+
WHERE authority_id = $6 AND tenant_id = $7 AND corpus_id = $8
|
|
48209
|
+
AND resource_kind = $9 AND target_selector = $10 AND state = 'pending'
|
|
48210
|
+
RETURNING target_selector
|
|
48211
|
+
`, [
|
|
48212
|
+
update.target_id,
|
|
48213
|
+
update.accepted_receipt_id,
|
|
48214
|
+
update.result_revision,
|
|
48215
|
+
update.result_digest,
|
|
48216
|
+
update.updated_at,
|
|
48217
|
+
scope.authority_id,
|
|
48218
|
+
scope.tenant_id,
|
|
48219
|
+
scope.corpus_id,
|
|
48220
|
+
resourceKind,
|
|
48221
|
+
targetSelector
|
|
48222
|
+
]);
|
|
48223
|
+
if (result.rows.length !== 1) {
|
|
48224
|
+
throw new Error("Todos project registration binding was not pending at acceptance");
|
|
48225
|
+
}
|
|
48226
|
+
}
|
|
48227
|
+
async setBindingTerminal(scope, resourceKind, targetSelector, updatedAt) {
|
|
48228
|
+
await this.client.query(`
|
|
48229
|
+
UPDATE todos_project_registration_bindings
|
|
48230
|
+
SET state = 'terminal_nonacceptance', updated_at = $1
|
|
48231
|
+
WHERE authority_id = $2 AND tenant_id = $3 AND corpus_id = $4
|
|
48232
|
+
AND resource_kind = $5 AND target_selector = $6 AND state = 'pending'
|
|
48233
|
+
`, [
|
|
48234
|
+
updatedAt,
|
|
48235
|
+
scope.authority_id,
|
|
48236
|
+
scope.tenant_id,
|
|
48237
|
+
scope.corpus_id,
|
|
48238
|
+
resourceKind,
|
|
48239
|
+
targetSelector
|
|
48240
|
+
]);
|
|
48241
|
+
}
|
|
48242
|
+
async setBindingRemoved(scope, resourceKind, targetSelector, removedReceiptId, updatedAt) {
|
|
48243
|
+
const result = await this.client.query(`
|
|
48244
|
+
UPDATE todos_project_registration_bindings
|
|
48245
|
+
SET state = 'removed', removed_receipt_id = $1, updated_at = $2
|
|
48246
|
+
WHERE authority_id = $3 AND tenant_id = $4 AND corpus_id = $5
|
|
48247
|
+
AND resource_kind = $6 AND target_selector = $7 AND state = 'accepted'
|
|
48248
|
+
RETURNING target_selector
|
|
48249
|
+
`, [
|
|
48250
|
+
removedReceiptId,
|
|
48251
|
+
updatedAt,
|
|
48252
|
+
scope.authority_id,
|
|
48253
|
+
scope.tenant_id,
|
|
48254
|
+
scope.corpus_id,
|
|
48255
|
+
resourceKind,
|
|
48256
|
+
targetSelector
|
|
48257
|
+
]);
|
|
48258
|
+
if (result.rows.length !== 1) {
|
|
48259
|
+
throw new Error("Todos project registration binding was not accepted at removal");
|
|
48260
|
+
}
|
|
48261
|
+
}
|
|
48262
|
+
async findProjectConflict(path, taskListSlug) {
|
|
48263
|
+
const result = await this.client.query(`
|
|
48264
|
+
SELECT payload FROM ${this.tableName}
|
|
48265
|
+
WHERE service = $1 AND object_type = 'projects' AND deleted_at IS NULL
|
|
48266
|
+
AND (payload->>'path' = $2 OR payload->>'task_list_id' = $3)
|
|
48267
|
+
ORDER BY payload->>'created_at' ASC, object_id ASC
|
|
48268
|
+
LIMIT 1
|
|
48269
|
+
`, [this.service, path, taskListSlug]);
|
|
48270
|
+
return result.rows[0] ? parsePayload3(result.rows[0].payload) : null;
|
|
48271
|
+
}
|
|
48272
|
+
async findTaskListConflict(projectId, slug) {
|
|
48273
|
+
const result = await this.client.query(`
|
|
48274
|
+
SELECT payload FROM ${this.tableName}
|
|
48275
|
+
WHERE service = $1 AND object_type = 'task_lists' AND deleted_at IS NULL
|
|
48276
|
+
AND payload->>'project_id' = $2 AND payload->>'slug' = $3
|
|
48277
|
+
ORDER BY payload->>'created_at' ASC, object_id ASC
|
|
48278
|
+
LIMIT 1
|
|
48279
|
+
`, [this.service, projectId, slug]);
|
|
48280
|
+
return result.rows[0] ? parsePayload3(result.rows[0].payload) : null;
|
|
48281
|
+
}
|
|
48282
|
+
async createProject(input) {
|
|
48283
|
+
return await this.storage.projects.create(input);
|
|
48284
|
+
}
|
|
48285
|
+
async createTaskList(input) {
|
|
48286
|
+
return await this.storage.taskLists.create(input);
|
|
48287
|
+
}
|
|
48288
|
+
async getProject(id) {
|
|
48289
|
+
return await this.storage.projects.get(id);
|
|
48290
|
+
}
|
|
48291
|
+
async getTaskList(id) {
|
|
48292
|
+
return await this.storage.taskLists.get(id);
|
|
48293
|
+
}
|
|
48294
|
+
async lockCompensationWrites() {
|
|
48295
|
+
await this.client.query(`LOCK TABLE ${this.tableName} IN SHARE ROW EXCLUSIVE MODE`);
|
|
48296
|
+
}
|
|
48297
|
+
async hasDependents(resourceKind, targetId) {
|
|
48298
|
+
const referencePredicate = resourceKind === "project" ? `(
|
|
48299
|
+
payload->>'project_id' = $2
|
|
48300
|
+
OR payload->>'active_project_id' = $2
|
|
48301
|
+
OR payload->>'assigned_from_project' = $2
|
|
48302
|
+
OR payload->>'external_project_id' = $2
|
|
48303
|
+
)` : "payload->>'task_list_id' = $2";
|
|
48304
|
+
const result = await this.client.query(`
|
|
48305
|
+
SELECT EXISTS (
|
|
48306
|
+
SELECT 1 FROM ${this.tableName}
|
|
48307
|
+
WHERE service = $1 AND deleted_at IS NULL
|
|
48308
|
+
AND ${referencePredicate}
|
|
48309
|
+
LIMIT 1
|
|
48310
|
+
) AS exists
|
|
48311
|
+
`, [this.service, targetId]);
|
|
48312
|
+
return result.rows[0]?.exists === true;
|
|
48313
|
+
}
|
|
48314
|
+
async deleteProject(id) {
|
|
48315
|
+
return await this.storage.projects.delete(id);
|
|
48316
|
+
}
|
|
48317
|
+
async deleteTaskList(id) {
|
|
48318
|
+
return await this.storage.taskLists.delete(id);
|
|
48319
|
+
}
|
|
48320
|
+
}
|
|
48321
|
+
|
|
48322
|
+
class PostgresTodosProjectRegistrationBackend {
|
|
48323
|
+
client;
|
|
48324
|
+
kind = "postgresql";
|
|
48325
|
+
service;
|
|
48326
|
+
tableName;
|
|
48327
|
+
cursorTableName;
|
|
48328
|
+
schemaReady = null;
|
|
48329
|
+
constructor(client, options = {}) {
|
|
48330
|
+
this.client = client;
|
|
48331
|
+
this.service = options.service ?? "todos";
|
|
48332
|
+
this.tableName = safeIdentifier(options.tableName ?? "todos_sync_records", "tableName");
|
|
48333
|
+
this.cursorTableName = safeIdentifier(options.cursorTableName ?? "todos_sync_cursors", "cursorTableName");
|
|
48334
|
+
}
|
|
48335
|
+
async ensureSchema() {
|
|
48336
|
+
this.schemaReady ??= (async () => {
|
|
48337
|
+
for (const statement of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
|
|
48338
|
+
await this.client.query(statement);
|
|
48339
|
+
}
|
|
48340
|
+
for (const statement of postgresTodosProjectRegistrationSchemaSql()) {
|
|
48341
|
+
await this.client.query(statement);
|
|
48342
|
+
}
|
|
48343
|
+
})();
|
|
48344
|
+
await this.schemaReady;
|
|
48345
|
+
}
|
|
48346
|
+
async transaction(fn) {
|
|
48347
|
+
await this.ensureSchema();
|
|
48348
|
+
if (typeof this.client.transaction !== "function") {
|
|
48349
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE", "PostgreSQL project registration requires an authoritative transaction");
|
|
48350
|
+
}
|
|
48351
|
+
return this.client.transaction((transaction) => fn(new PostgresTodosProjectRegistrationTransaction(transaction, this.service, this.tableName, this.cursorTableName)));
|
|
48352
|
+
}
|
|
48353
|
+
async direct() {
|
|
48354
|
+
await this.ensureSchema();
|
|
48355
|
+
return new PostgresTodosProjectRegistrationTransaction(this.client, this.service, this.tableName, this.cursorTableName);
|
|
48356
|
+
}
|
|
48357
|
+
async getReceiptForLookup(identity) {
|
|
48358
|
+
return (await this.direct()).getReceiptForLookup(identity);
|
|
48359
|
+
}
|
|
48360
|
+
async getReceiptById(receiptId) {
|
|
48361
|
+
return (await this.direct()).getReceiptById(receiptId);
|
|
48362
|
+
}
|
|
48363
|
+
async getBinding(scope, resourceKind, targetSelector) {
|
|
48364
|
+
return (await this.direct()).getBinding(scope, resourceKind, targetSelector);
|
|
48365
|
+
}
|
|
48366
|
+
async getProject(id) {
|
|
48367
|
+
return (await this.direct()).getProject(id);
|
|
48368
|
+
}
|
|
48369
|
+
async getTaskList(id) {
|
|
48370
|
+
return (await this.direct()).getTaskList(id);
|
|
48371
|
+
}
|
|
48372
|
+
}
|
|
48373
|
+
var init_postgres2 = __esm(() => {
|
|
48374
|
+
init_postgres_adapter();
|
|
48375
|
+
init_postgres_sync();
|
|
48376
|
+
init_types4();
|
|
48377
|
+
});
|
|
48378
|
+
|
|
48379
|
+
// src/project-registration/sqlite.ts
|
|
48380
|
+
var sqliteTransactionTails, PROJECT_REFERENCE_COLUMNS, TASK_LIST_REFERENCE_COLUMNS;
|
|
48381
|
+
var init_sqlite = __esm(() => {
|
|
48382
|
+
init_local_sqlite();
|
|
48383
|
+
sqliteTransactionTails = new WeakMap;
|
|
48384
|
+
PROJECT_REFERENCE_COLUMNS = new Set([
|
|
48385
|
+
"project_id",
|
|
48386
|
+
"active_project_id",
|
|
48387
|
+
"assigned_from_project",
|
|
48388
|
+
"external_project_id"
|
|
48389
|
+
]);
|
|
48390
|
+
TASK_LIST_REFERENCE_COLUMNS = new Set(["task_list_id"]);
|
|
48391
|
+
});
|
|
48392
|
+
|
|
48393
|
+
// src/project-registration/authority.ts
|
|
48394
|
+
import { createHash as createHash14 } from "crypto";
|
|
48395
|
+
function canonicalProjectRegistrationJson(value) {
|
|
48396
|
+
return JSON.stringify(canonicalize2(value));
|
|
48397
|
+
}
|
|
48398
|
+
function canonicalize2(value) {
|
|
48399
|
+
if (Array.isArray(value))
|
|
48400
|
+
return value.map(canonicalize2);
|
|
48401
|
+
if (!value || typeof value !== "object")
|
|
48402
|
+
return value;
|
|
48403
|
+
const out = {};
|
|
48404
|
+
for (const key of Object.keys(value).sort()) {
|
|
48405
|
+
const entry2 = value[key];
|
|
48406
|
+
if (entry2 !== undefined)
|
|
48407
|
+
out[key] = canonicalize2(entry2);
|
|
48408
|
+
}
|
|
48409
|
+
return out;
|
|
48410
|
+
}
|
|
48411
|
+
function digestProjectRegistrationValue(value) {
|
|
48412
|
+
return createHash14("sha256").update(canonicalProjectRegistrationJson(value)).digest("hex");
|
|
48413
|
+
}
|
|
48414
|
+
function deriveTodosProjectRegistrationIdempotencyKey(input) {
|
|
48415
|
+
return `prk_${digestProjectRegistrationValue({
|
|
48416
|
+
route: TODOS_PROJECT_REGISTRATION_CALLER_ROUTE,
|
|
48417
|
+
...input
|
|
48418
|
+
}).slice(0, 48)}`;
|
|
48419
|
+
}
|
|
48420
|
+
function responseBytes(value) {
|
|
48421
|
+
return Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
48422
|
+
}
|
|
48423
|
+
function assertBounds(bounds) {
|
|
48424
|
+
if (!Number.isSafeInteger(bounds.response_byte_limit) || bounds.response_byte_limit <= 0) {
|
|
48425
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "response_byte_limit must be a positive integer");
|
|
48426
|
+
}
|
|
48427
|
+
if (!Number.isSafeInteger(bounds.time_budget_ms) || bounds.time_budget_ms <= 0) {
|
|
48428
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "time_budget_ms must be a positive integer");
|
|
48429
|
+
}
|
|
48430
|
+
}
|
|
48431
|
+
function assertResourceKind(value) {
|
|
48432
|
+
if (value !== "project" && value !== "task_list") {
|
|
48433
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "resource_kind must be project or task_list");
|
|
48434
|
+
}
|
|
48435
|
+
}
|
|
48436
|
+
function assertDirection(value) {
|
|
48437
|
+
if (value !== "forward" && value !== "inverse") {
|
|
48438
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "direction must be forward or inverse");
|
|
48439
|
+
}
|
|
48440
|
+
}
|
|
48441
|
+
function assertWithinBounds(value, bounds, startedAt) {
|
|
48442
|
+
const bytes = responseBytes(value);
|
|
48443
|
+
if (bytes > bounds.response_byte_limit) {
|
|
48444
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_RESPONSE_TOO_LARGE", `registration response requires ${bytes} bytes but the bound is ${bounds.response_byte_limit}`, { response_bytes: bytes, response_byte_limit: bounds.response_byte_limit });
|
|
48445
|
+
}
|
|
48446
|
+
const elapsed = Date.now() - startedAt;
|
|
48447
|
+
if (elapsed > bounds.time_budget_ms) {
|
|
48448
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_TIME_BUDGET_EXCEEDED", `registration call took ${elapsed}ms but the bound is ${bounds.time_budget_ms}ms`, { elapsed_ms: elapsed, time_budget_ms: bounds.time_budget_ms });
|
|
48449
|
+
}
|
|
48450
|
+
return { response_bytes: bytes, elapsed_ms: elapsed };
|
|
48451
|
+
}
|
|
48452
|
+
function withResponseControl(payload, bounds, startedAt) {
|
|
48453
|
+
const envelope = {
|
|
48454
|
+
...payload,
|
|
48455
|
+
response_control: {
|
|
48456
|
+
response_byte_limit: bounds.response_byte_limit,
|
|
48457
|
+
time_budget_ms: bounds.time_budget_ms,
|
|
48458
|
+
response_bytes: 0,
|
|
48459
|
+
elapsed_ms: 0,
|
|
48460
|
+
complete: true,
|
|
48461
|
+
truncated: false
|
|
48462
|
+
}
|
|
48463
|
+
};
|
|
48464
|
+
for (let attempt = 0;attempt < 8; attempt += 1) {
|
|
48465
|
+
const measured = assertWithinBounds(envelope, bounds, startedAt);
|
|
48466
|
+
const stable3 = envelope.response_control.response_bytes === measured.response_bytes && envelope.response_control.elapsed_ms === measured.elapsed_ms;
|
|
48467
|
+
envelope.response_control = {
|
|
48468
|
+
response_byte_limit: bounds.response_byte_limit,
|
|
48469
|
+
time_budget_ms: bounds.time_budget_ms,
|
|
48470
|
+
response_bytes: measured.response_bytes,
|
|
48471
|
+
elapsed_ms: measured.elapsed_ms,
|
|
48472
|
+
complete: true,
|
|
48473
|
+
truncated: false
|
|
48474
|
+
};
|
|
48475
|
+
if (stable3)
|
|
48476
|
+
break;
|
|
48477
|
+
}
|
|
48478
|
+
const finalMeasurement = assertWithinBounds(envelope, bounds, startedAt);
|
|
48479
|
+
envelope.response_control.response_bytes = finalMeasurement.response_bytes;
|
|
48480
|
+
envelope.response_control.elapsed_ms = finalMeasurement.elapsed_ms;
|
|
48481
|
+
return envelope;
|
|
48482
|
+
}
|
|
48483
|
+
function requireString(value, field, options = {}) {
|
|
48484
|
+
const min = options.min ?? 1;
|
|
48485
|
+
const max = options.max ?? 512;
|
|
48486
|
+
if (typeof value !== "string" || value.length < min || value.length > max || /[\u0000-\u001f]/.test(value) || options.pattern && !options.pattern.test(value)) {
|
|
48487
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field} is not a valid bounded registration identifier`);
|
|
48488
|
+
}
|
|
48489
|
+
return value;
|
|
48490
|
+
}
|
|
48491
|
+
function exactKeys(value, expected, field) {
|
|
48492
|
+
const actual = Object.keys(value).sort();
|
|
48493
|
+
const wanted = [...expected].sort();
|
|
48494
|
+
if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) {
|
|
48495
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field} must contain exactly: ${wanted.join(", ")}`);
|
|
48496
|
+
}
|
|
48497
|
+
}
|
|
48498
|
+
function publicReceipt(row) {
|
|
48499
|
+
const {
|
|
48500
|
+
target_selector: _targetSelector,
|
|
48501
|
+
normalized_call_digest: _normalizedCallDigest,
|
|
48502
|
+
...receipt
|
|
48503
|
+
} = row;
|
|
48504
|
+
return receipt;
|
|
48505
|
+
}
|
|
48506
|
+
function projectRegistrationPath(projectId) {
|
|
48507
|
+
return `hasna-project://${encodeURIComponent(projectId)}`;
|
|
48508
|
+
}
|
|
48509
|
+
function taskListSlug(projectSlug) {
|
|
48510
|
+
const slug = normalizeSlug(projectSlug);
|
|
48511
|
+
if (!slug || slug !== projectSlug) {
|
|
48512
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "project_slug must be canonical kebab-case");
|
|
48513
|
+
}
|
|
48514
|
+
return `todos-${slug}`;
|
|
48515
|
+
}
|
|
48516
|
+
function deterministicTaskPrefix(projectSlug) {
|
|
48517
|
+
const letters = projectSlug.replace(/[^a-z0-9]/gi, "").toUpperCase();
|
|
48518
|
+
return (letters.slice(0, 3) || "PRJ").padEnd(3, "X");
|
|
48519
|
+
}
|
|
48520
|
+
function projectRecord(project) {
|
|
48521
|
+
return {
|
|
48522
|
+
target_id: project.id,
|
|
48523
|
+
revision: project.updated_at,
|
|
48524
|
+
digest: digestProjectRegistrationValue({
|
|
48525
|
+
id: project.id,
|
|
48526
|
+
name: project.name,
|
|
48527
|
+
path: project.path,
|
|
48528
|
+
description: project.description,
|
|
48529
|
+
task_list_id: project.task_list_id,
|
|
48530
|
+
task_prefix: project.task_prefix,
|
|
48531
|
+
task_counter: project.task_counter,
|
|
48532
|
+
created_at: project.created_at,
|
|
48533
|
+
updated_at: project.updated_at
|
|
48534
|
+
})
|
|
48535
|
+
};
|
|
48536
|
+
}
|
|
48537
|
+
function taskListRecord(taskList) {
|
|
48538
|
+
return {
|
|
48539
|
+
target_id: taskList.id,
|
|
48540
|
+
revision: taskList.updated_at,
|
|
48541
|
+
digest: digestProjectRegistrationValue({
|
|
48542
|
+
id: taskList.id,
|
|
48543
|
+
project_id: taskList.project_id,
|
|
48544
|
+
slug: taskList.slug,
|
|
48545
|
+
name: taskList.name,
|
|
48546
|
+
description: taskList.description,
|
|
48547
|
+
metadata: taskList.metadata,
|
|
48548
|
+
created_at: taskList.created_at,
|
|
48549
|
+
updated_at: taskList.updated_at
|
|
48550
|
+
})
|
|
48551
|
+
};
|
|
48552
|
+
}
|
|
48553
|
+
function receiptId(input) {
|
|
48554
|
+
return `tpr_${digestProjectRegistrationValue(input).slice(0, 40)}`;
|
|
48555
|
+
}
|
|
48556
|
+
function capabilityMatches(request, capability) {
|
|
48557
|
+
return request.authority_route === capability.route && request.package_version === capability.package_version && request.authority_id === capability.authority_id && request.tenant_id === capability.tenant_id && request.corpus_id === capability.corpus_id;
|
|
48558
|
+
}
|
|
48559
|
+
function authorityScope(capability) {
|
|
48560
|
+
return {
|
|
48561
|
+
authority_id: capability.authority_id,
|
|
48562
|
+
tenant_id: capability.tenant_id,
|
|
48563
|
+
corpus_id: capability.corpus_id
|
|
48564
|
+
};
|
|
48565
|
+
}
|
|
48566
|
+
function assertCapabilityRequest(request, capability) {
|
|
48567
|
+
if (!capabilityMatches(request, capability)) {
|
|
48568
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH", "registration request does not match this authority capability identity");
|
|
48569
|
+
}
|
|
48570
|
+
}
|
|
48571
|
+
function normalizedCallDigest(request) {
|
|
48572
|
+
return digestProjectRegistrationValue({
|
|
48573
|
+
authority_route: request.authority_route,
|
|
48574
|
+
package_version: request.package_version,
|
|
48575
|
+
authority_id: request.authority_id,
|
|
48576
|
+
tenant_id: request.tenant_id,
|
|
48577
|
+
corpus_id: request.corpus_id,
|
|
48578
|
+
operation_id: request.operation_id,
|
|
48579
|
+
step_id: request.step_id,
|
|
48580
|
+
resource_kind: request.resource_kind,
|
|
48581
|
+
direction: request.direction,
|
|
48582
|
+
target_selector: request.target_selector,
|
|
48583
|
+
idempotency_key: request.idempotency_key,
|
|
48584
|
+
request_digest: request.request_digest,
|
|
48585
|
+
precondition_digest: request.precondition_digest,
|
|
48586
|
+
project_id: request.project_id,
|
|
48587
|
+
project_slug: request.project_slug,
|
|
48588
|
+
project_name: request.project_name,
|
|
48589
|
+
desired: request.desired,
|
|
48590
|
+
accepted_receipt_id: request.accepted_receipt?.receipt_id ?? null
|
|
48591
|
+
});
|
|
48592
|
+
}
|
|
48593
|
+
function assertCommonRequest(request, capability) {
|
|
48594
|
+
assertBounds(request);
|
|
48595
|
+
assertResourceKind(request.resource_kind);
|
|
48596
|
+
assertDirection(request.direction);
|
|
48597
|
+
assertCapabilityRequest(request, capability);
|
|
48598
|
+
requireString(request.operation_id, "operation_id", {
|
|
48599
|
+
min: 8,
|
|
48600
|
+
max: 128,
|
|
48601
|
+
pattern: OPERATION_PATTERN
|
|
48602
|
+
});
|
|
48603
|
+
requireString(request.step_id, "step_id", {
|
|
48604
|
+
min: 3,
|
|
48605
|
+
max: 128,
|
|
48606
|
+
pattern: STEP_PATTERN
|
|
48607
|
+
});
|
|
48608
|
+
requireString(request.target_selector, "target_selector", { max: 512 });
|
|
48609
|
+
requireString(request.project_id, "project_id", {
|
|
48610
|
+
min: 16,
|
|
48611
|
+
max: 128,
|
|
48612
|
+
pattern: WORKSPACE_ID_PATTERN
|
|
48613
|
+
});
|
|
48614
|
+
requireString(request.project_name, "project_name", { max: 256 });
|
|
48615
|
+
requireString(request.project_slug, "project_slug", { max: 128 });
|
|
48616
|
+
requireString(request.request_digest, "request_digest", {
|
|
48617
|
+
min: 64,
|
|
48618
|
+
max: 64,
|
|
48619
|
+
pattern: SHA256_PATTERN
|
|
48620
|
+
});
|
|
48621
|
+
requireString(request.precondition_digest, "precondition_digest", {
|
|
48622
|
+
min: 64,
|
|
48623
|
+
max: 64,
|
|
48624
|
+
pattern: SHA256_PATTERN
|
|
48625
|
+
});
|
|
48626
|
+
requireString(request.idempotency_key, "idempotency_key", {
|
|
48627
|
+
min: 52,
|
|
48628
|
+
max: 52,
|
|
48629
|
+
pattern: IDEMPOTENCY_PATTERN
|
|
48630
|
+
});
|
|
48631
|
+
if (!request.desired || typeof request.desired !== "object" || Array.isArray(request.desired)) {
|
|
48632
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "desired must be a JSON object");
|
|
48633
|
+
}
|
|
48634
|
+
const expectedKey = deriveTodosProjectRegistrationIdempotencyKey({
|
|
48635
|
+
operation_id: request.operation_id,
|
|
48636
|
+
step_id: request.step_id,
|
|
48637
|
+
direction: request.direction,
|
|
48638
|
+
target_selector: request.target_selector,
|
|
48639
|
+
request_digest: request.request_digest,
|
|
48640
|
+
precondition_digest: request.precondition_digest
|
|
48641
|
+
});
|
|
48642
|
+
if (request.idempotency_key !== expectedKey) {
|
|
48643
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_IDEMPOTENCY_MISMATCH", "idempotency_key does not match the deterministic operation/step/direction payload", { expected: expectedKey });
|
|
48644
|
+
}
|
|
48645
|
+
taskListSlug(request.project_slug);
|
|
48646
|
+
}
|
|
48647
|
+
function assertForwardRequest(request, capability) {
|
|
48648
|
+
assertCommonRequest(request, capability);
|
|
48649
|
+
if (request.direction !== "forward") {
|
|
48650
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "create requires direction=forward");
|
|
48651
|
+
}
|
|
48652
|
+
const expectedRequestDigest = digestProjectRegistrationValue(request.desired);
|
|
48653
|
+
const expectedPreconditionDigest = digestProjectRegistrationValue({
|
|
48654
|
+
target_selector: request.target_selector,
|
|
48655
|
+
expected: "absent"
|
|
48656
|
+
});
|
|
48657
|
+
if (request.request_digest !== expectedRequestDigest || request.precondition_digest !== expectedPreconditionDigest) {
|
|
48658
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "request_digest or precondition_digest does not match normalized forward semantics", {
|
|
48659
|
+
expected_request_digest: expectedRequestDigest,
|
|
48660
|
+
expected_precondition_digest: expectedPreconditionDigest
|
|
48661
|
+
});
|
|
48662
|
+
}
|
|
48663
|
+
if (request.resource_kind === "project") {
|
|
48664
|
+
exactKeys(request.desired, ["source_project_id", "source_project_slug", "name"], "project desired");
|
|
48665
|
+
if (request.desired["source_project_id"] !== request.project_id || request.desired["source_project_slug"] !== request.project_slug || request.desired["name"] !== request.project_name || request.target_selector !== request.project_id) {
|
|
48666
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "project desired state and target selector must match the complete Projects identity");
|
|
48667
|
+
}
|
|
48668
|
+
return;
|
|
48669
|
+
}
|
|
48670
|
+
if (request.resource_kind === "task_list") {
|
|
48671
|
+
exactKeys(request.desired, ["todos_project_id", "source_project_id", "name"], "task-list desired");
|
|
48672
|
+
const todosProjectId = request.desired["todos_project_id"];
|
|
48673
|
+
if (typeof todosProjectId !== "string" || !UUID_PATTERN.test(todosProjectId)) {
|
|
48674
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED", "task-list create requires the exact full Todos project UUID");
|
|
48675
|
+
}
|
|
48676
|
+
if (request.target_selector !== `${todosProjectId}:default` || request.desired["source_project_id"] !== request.project_id || request.desired["name"] !== request.project_name) {
|
|
48677
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "task-list desired state must bind the exact Todos project id and Projects identity");
|
|
48678
|
+
}
|
|
48679
|
+
return;
|
|
48680
|
+
}
|
|
48681
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "unsupported registration resource kind");
|
|
48682
|
+
}
|
|
48683
|
+
function assertInverseRequest(request, capability) {
|
|
48684
|
+
assertCommonRequest(request, capability);
|
|
48685
|
+
if (request.direction !== "inverse") {
|
|
48686
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "compensate requires direction=inverse");
|
|
48687
|
+
}
|
|
48688
|
+
const accepted = request.accepted_receipt;
|
|
48689
|
+
if (!accepted || accepted.authority !== "todos" || accepted.route !== capability.route || accepted.package_version !== capability.package_version || accepted.authority_id !== capability.authority_id || accepted.tenant_id !== capability.tenant_id || accepted.corpus_id !== capability.corpus_id || accepted.operation_id !== request.operation_id || accepted.step_id !== request.step_id || accepted.resource_kind !== request.resource_kind || accepted.direction !== "forward" || accepted.outcome !== "accepted" || !accepted.created_by_operation || !accepted.target_id || !accepted.result_revision || !accepted.result_digest) {
|
|
48690
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "inverse requires the complete accepted forward receipt created by this operation");
|
|
48691
|
+
}
|
|
48692
|
+
exactKeys(request.desired, ["accepted_receipt_id", "target_id"], "inverse desired");
|
|
48693
|
+
const expectedDesired = {
|
|
48694
|
+
accepted_receipt_id: accepted.receipt_id,
|
|
48695
|
+
target_id: accepted.target_id
|
|
48696
|
+
};
|
|
48697
|
+
const expectedPrecondition = {
|
|
48698
|
+
expected_revision: accepted.result_revision,
|
|
48699
|
+
expected_digest: accepted.result_digest
|
|
48700
|
+
};
|
|
48701
|
+
const expectedRequestDigest = digestProjectRegistrationValue(expectedDesired);
|
|
48702
|
+
const expectedPreconditionDigest = digestProjectRegistrationValue(expectedPrecondition);
|
|
48703
|
+
if (canonicalProjectRegistrationJson(request.desired) !== canonicalProjectRegistrationJson(expectedDesired) || request.request_digest !== expectedRequestDigest || request.precondition_digest !== expectedPreconditionDigest || request.target_selector !== accepted.target_id) {
|
|
48704
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "inverse request does not match the accepted receipt and exact readback precondition");
|
|
48705
|
+
}
|
|
48706
|
+
return accepted;
|
|
48707
|
+
}
|
|
48708
|
+
function makeReceipt(input, createdAt2) {
|
|
48709
|
+
return {
|
|
48710
|
+
...input,
|
|
48711
|
+
receipt_id: receiptId(input),
|
|
48712
|
+
created_at: createdAt2
|
|
48713
|
+
};
|
|
48714
|
+
}
|
|
48715
|
+
function receiptBase(request, callDigest, capability) {
|
|
48716
|
+
return {
|
|
48717
|
+
authority: "todos",
|
|
48718
|
+
route: capability.route,
|
|
48719
|
+
package_version: capability.package_version,
|
|
48720
|
+
authority_id: capability.authority_id,
|
|
48721
|
+
tenant_id: capability.tenant_id,
|
|
48722
|
+
corpus_id: capability.corpus_id,
|
|
48723
|
+
operation_id: request.operation_id,
|
|
48724
|
+
step_id: request.step_id,
|
|
48725
|
+
resource_kind: request.resource_kind,
|
|
48726
|
+
direction: request.direction,
|
|
48727
|
+
target_selector: request.target_selector,
|
|
48728
|
+
idempotency_key: request.idempotency_key,
|
|
48729
|
+
request_digest: request.request_digest,
|
|
48730
|
+
precondition_digest: request.precondition_digest,
|
|
48731
|
+
normalized_call_digest: callDigest
|
|
48732
|
+
};
|
|
48733
|
+
}
|
|
48734
|
+
function makeAcceptedReceipt(request, callDigest, capability, record, createdAt2) {
|
|
48735
|
+
return makeReceipt({
|
|
48736
|
+
...receiptBase(request, callDigest, capability),
|
|
48737
|
+
outcome: "accepted",
|
|
48738
|
+
reason: null,
|
|
48739
|
+
target_id: record.target_id,
|
|
48740
|
+
result_revision: record.revision,
|
|
48741
|
+
result_digest: record.digest,
|
|
48742
|
+
duplicate_of_receipt_id: null,
|
|
48743
|
+
accepted_receipt_id: request.direction === "inverse" ? request.accepted_receipt.receipt_id : null,
|
|
48744
|
+
created_by_operation: true
|
|
48745
|
+
}, createdAt2);
|
|
48746
|
+
}
|
|
48747
|
+
function makeDuplicateReceipt(request, callDigest, capability, accepted, createdAt2) {
|
|
48748
|
+
return makeReceipt({
|
|
48749
|
+
...receiptBase(request, callDigest, capability),
|
|
48750
|
+
outcome: "duplicate_of_accepted",
|
|
48751
|
+
reason: null,
|
|
48752
|
+
target_id: accepted.target_id,
|
|
48753
|
+
result_revision: accepted.result_revision,
|
|
48754
|
+
result_digest: accepted.result_digest,
|
|
48755
|
+
duplicate_of_receipt_id: accepted.receipt_id,
|
|
48756
|
+
accepted_receipt_id: null,
|
|
48757
|
+
created_by_operation: false
|
|
48758
|
+
}, createdAt2);
|
|
48759
|
+
}
|
|
48760
|
+
function makeTerminalReceipt(request, callDigest, capability, reason, createdAt2, options = {}) {
|
|
48761
|
+
return makeReceipt({
|
|
48762
|
+
...receiptBase(request, callDigest, capability),
|
|
48763
|
+
outcome: "terminal_nonacceptance",
|
|
48764
|
+
reason,
|
|
48765
|
+
target_id: options.targetId ?? null,
|
|
48766
|
+
result_revision: null,
|
|
48767
|
+
result_digest: null,
|
|
48768
|
+
duplicate_of_receipt_id: null,
|
|
48769
|
+
accepted_receipt_id: options.acceptedReceiptId ?? null,
|
|
48770
|
+
created_by_operation: false
|
|
48771
|
+
}, createdAt2);
|
|
48772
|
+
}
|
|
48773
|
+
async function insertDeterministicReceipt(transaction, receipt) {
|
|
48774
|
+
if (await transaction.insertReceipt(receipt))
|
|
48775
|
+
return receipt;
|
|
48776
|
+
const existing = await transaction.getReceiptById(receipt.receipt_id);
|
|
48777
|
+
const { created_at: _existingCreatedAt, ...existingContent } = existing ?? {};
|
|
48778
|
+
const { created_at: _receiptCreatedAt, ...receiptContent } = receipt;
|
|
48779
|
+
if (!existing || canonicalProjectRegistrationJson(existingContent) !== canonicalProjectRegistrationJson(receiptContent)) {
|
|
48780
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "deterministic receipt id is occupied by different immutable content", { receipt_id: receipt.receipt_id });
|
|
48781
|
+
}
|
|
48782
|
+
return existing;
|
|
48783
|
+
}
|
|
48784
|
+
function bindingFor(request, callDigest, timestamp4, capability) {
|
|
48785
|
+
return {
|
|
48786
|
+
...authorityScope(capability),
|
|
48787
|
+
resource_kind: request.resource_kind,
|
|
48788
|
+
target_selector: request.target_selector,
|
|
48789
|
+
operation_id: request.operation_id,
|
|
48790
|
+
step_id: request.step_id,
|
|
48791
|
+
direction: "forward",
|
|
48792
|
+
idempotency_key: request.idempotency_key,
|
|
48793
|
+
request_digest: request.request_digest,
|
|
48794
|
+
precondition_digest: request.precondition_digest,
|
|
48795
|
+
normalized_call_digest: callDigest,
|
|
48796
|
+
state: "pending",
|
|
48797
|
+
target_id: null,
|
|
48798
|
+
accepted_receipt_id: null,
|
|
48799
|
+
result_revision: null,
|
|
48800
|
+
result_digest: null,
|
|
48801
|
+
removed_receipt_id: null,
|
|
48802
|
+
created_at: timestamp4,
|
|
48803
|
+
updated_at: timestamp4
|
|
48804
|
+
};
|
|
48805
|
+
}
|
|
48806
|
+
|
|
48807
|
+
class PackageOwnedTodosProjectRegistrationAuthority {
|
|
48808
|
+
backend;
|
|
48809
|
+
authority = "todos";
|
|
48810
|
+
capabilityValue;
|
|
48811
|
+
now;
|
|
48812
|
+
faultInjector;
|
|
48813
|
+
constructor(backend, options = {}) {
|
|
48814
|
+
this.backend = backend;
|
|
48815
|
+
this.capabilityValue = {
|
|
48816
|
+
authority: "todos",
|
|
48817
|
+
route: TODOS_PROJECT_REGISTRATION_ROUTE,
|
|
48818
|
+
package_version: options.packageVersion ?? getPackageVersion(import.meta.url),
|
|
48819
|
+
authority_id: options.authorityId ?? "todos",
|
|
48820
|
+
tenant_id: options.tenantId ?? backend.kind,
|
|
48821
|
+
corpus_id: options.corpusId ?? `todos:${backend.kind}`,
|
|
48822
|
+
supported_resources: ["project", "task_list"],
|
|
48823
|
+
conditional_create: true,
|
|
48824
|
+
immutable_receipts: true,
|
|
48825
|
+
exact_terminal_lookup: true,
|
|
48826
|
+
exact_readback: true,
|
|
48827
|
+
conditional_inverse: true,
|
|
48828
|
+
ambiguous_outcome_reconciliation: true
|
|
48829
|
+
};
|
|
48830
|
+
this.now = options.now ?? (() => new Date().toISOString());
|
|
48831
|
+
this.faultInjector = options.faultInjector;
|
|
48832
|
+
}
|
|
48833
|
+
async capability() {
|
|
48834
|
+
return {
|
|
48835
|
+
...this.capabilityValue,
|
|
48836
|
+
supported_resources: [...this.capabilityValue.supported_resources]
|
|
48837
|
+
};
|
|
48838
|
+
}
|
|
48839
|
+
async fault(point, request) {
|
|
48840
|
+
if (!this.faultInjector)
|
|
48841
|
+
return;
|
|
48842
|
+
try {
|
|
48843
|
+
await this.faultInjector(point, {
|
|
48844
|
+
operation_id: request.operation_id,
|
|
48845
|
+
step_id: request.step_id,
|
|
48846
|
+
resource_kind: request.resource_kind,
|
|
48847
|
+
direction: request.direction
|
|
48848
|
+
});
|
|
48849
|
+
} catch (cause) {
|
|
48850
|
+
throw new WriteBoundaryError(point, cause);
|
|
48851
|
+
}
|
|
48852
|
+
}
|
|
48853
|
+
async afterCommit(request) {
|
|
48854
|
+
await this.faultInjector?.("after_commit", {
|
|
48855
|
+
operation_id: request.operation_id,
|
|
48856
|
+
step_id: request.step_id,
|
|
48857
|
+
resource_kind: request.resource_kind,
|
|
48858
|
+
direction: request.direction
|
|
48859
|
+
});
|
|
48860
|
+
}
|
|
48861
|
+
async duplicateFor(transaction, request, callDigest, accepted) {
|
|
48862
|
+
const duplicate = makeDuplicateReceipt(request, callDigest, this.capabilityValue, accepted, this.now());
|
|
48863
|
+
return insertDeterministicReceipt(transaction, duplicate);
|
|
48864
|
+
}
|
|
48865
|
+
async terminalFor(transaction, request, callDigest, reason, options = {}) {
|
|
48866
|
+
return insertDeterministicReceipt(transaction, makeTerminalReceipt(request, callDigest, this.capabilityValue, reason, this.now(), options));
|
|
48867
|
+
}
|
|
48868
|
+
async existingForwardResolution(transaction, request, callDigest) {
|
|
48869
|
+
const exact = await transaction.getReceiptForLookup({
|
|
48870
|
+
...authorityScope(this.capabilityValue),
|
|
48871
|
+
operation_id: request.operation_id,
|
|
48872
|
+
step_id: request.step_id,
|
|
48873
|
+
resource_kind: request.resource_kind,
|
|
48874
|
+
direction: request.direction,
|
|
48875
|
+
idempotency_key: request.idempotency_key,
|
|
48876
|
+
target_selector: request.target_selector
|
|
48877
|
+
});
|
|
48878
|
+
if (exact) {
|
|
48879
|
+
if (exact.outcome === "terminal_nonacceptance")
|
|
48880
|
+
return exact;
|
|
48881
|
+
const accepted2 = exact.outcome === "accepted" ? exact : await transaction.getReceiptById(exact.duplicate_of_receipt_id);
|
|
48882
|
+
if (!accepted2) {
|
|
48883
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "duplicate receipt points to a missing accepted receipt");
|
|
48884
|
+
}
|
|
48885
|
+
if (accepted2.normalized_call_digest !== callDigest) {
|
|
48886
|
+
return this.terminalFor(transaction, request, callDigest, "operation_step_semantics_changed", { targetId: accepted2.target_id });
|
|
48887
|
+
}
|
|
48888
|
+
return this.duplicateFor(transaction, request, callDigest, accepted2);
|
|
48889
|
+
}
|
|
48890
|
+
const accepted = await transaction.getAcceptedReceiptForStep({
|
|
48891
|
+
...authorityScope(this.capabilityValue),
|
|
48892
|
+
operation_id: request.operation_id,
|
|
48893
|
+
step_id: request.step_id,
|
|
48894
|
+
resource_kind: request.resource_kind,
|
|
48895
|
+
direction: "forward"
|
|
48896
|
+
});
|
|
48897
|
+
if (!accepted)
|
|
48898
|
+
return null;
|
|
48899
|
+
if (accepted.normalized_call_digest === callDigest) {
|
|
48900
|
+
return this.duplicateFor(transaction, request, callDigest, accepted);
|
|
48901
|
+
}
|
|
48902
|
+
return this.terminalFor(transaction, request, callDigest, "operation_step_semantics_changed", { targetId: accepted.target_id });
|
|
48903
|
+
}
|
|
48904
|
+
async createObject(transaction, request) {
|
|
48905
|
+
if (request.resource_kind === "project") {
|
|
48906
|
+
const path = projectRegistrationPath(request.project_id);
|
|
48907
|
+
const slug2 = taskListSlug(request.project_slug);
|
|
48908
|
+
const conflict2 = await transaction.findProjectConflict(path, slug2);
|
|
48909
|
+
if (conflict2) {
|
|
48910
|
+
return this.terminalFor(transaction, request, normalizedCallDigest(request), "target_already_exists", { targetId: conflict2.id });
|
|
48911
|
+
}
|
|
48912
|
+
await this.fault("before_object_write", request);
|
|
48913
|
+
const project = await transaction.createProject({
|
|
48914
|
+
name: request.project_name,
|
|
48915
|
+
path,
|
|
48916
|
+
description: `Registered from Projects workspace ${request.project_id}`,
|
|
48917
|
+
task_list_id: slug2,
|
|
48918
|
+
task_prefix: deterministicTaskPrefix(request.project_slug)
|
|
48919
|
+
});
|
|
48920
|
+
await this.fault("after_object_write", request);
|
|
48921
|
+
return projectRecord(project);
|
|
48922
|
+
}
|
|
48923
|
+
const todosProjectId = String(request.desired["todos_project_id"]);
|
|
48924
|
+
const sourceBinding = await transaction.getBinding(authorityScope(this.capabilityValue), "project", request.project_id);
|
|
48925
|
+
if (!sourceBinding || sourceBinding.state !== "accepted" || sourceBinding.target_id !== todosProjectId) {
|
|
48926
|
+
return this.terminalFor(transaction, request, normalizedCallDigest(request), "exact_parent_registration_missing", { targetId: todosProjectId });
|
|
48927
|
+
}
|
|
48928
|
+
const parent = await transaction.getProject(todosProjectId);
|
|
48929
|
+
if (!parent) {
|
|
48930
|
+
return this.terminalFor(transaction, request, normalizedCallDigest(request), "exact_parent_project_missing", { targetId: todosProjectId });
|
|
48931
|
+
}
|
|
48932
|
+
const slug = taskListSlug(request.project_slug);
|
|
48933
|
+
const conflict = await transaction.findTaskListConflict(todosProjectId, slug);
|
|
48934
|
+
if (conflict) {
|
|
48935
|
+
return this.terminalFor(transaction, request, normalizedCallDigest(request), "target_already_exists", { targetId: conflict.id });
|
|
48936
|
+
}
|
|
48937
|
+
await this.fault("before_object_write", request);
|
|
48938
|
+
const taskList = await transaction.createTaskList({
|
|
48939
|
+
name: request.project_name,
|
|
48940
|
+
slug,
|
|
48941
|
+
project_id: todosProjectId,
|
|
48942
|
+
metadata: {
|
|
48943
|
+
source_project_id: request.project_id,
|
|
48944
|
+
registration_authority: "todos"
|
|
48945
|
+
}
|
|
48946
|
+
});
|
|
48947
|
+
await this.fault("after_object_write", request);
|
|
48948
|
+
if (taskList.project_id !== todosProjectId) {
|
|
48949
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "task-list create did not preserve the exact full Todos project id");
|
|
48950
|
+
}
|
|
48951
|
+
return taskListRecord(taskList);
|
|
48952
|
+
}
|
|
48953
|
+
async create(request) {
|
|
48954
|
+
const startedAt = Date.now();
|
|
48955
|
+
assertForwardRequest(request, this.capabilityValue);
|
|
48956
|
+
const callDigest = normalizedCallDigest(request);
|
|
48957
|
+
try {
|
|
48958
|
+
const row = await this.backend.transaction(async (transaction) => {
|
|
48959
|
+
await transaction.lockStep({
|
|
48960
|
+
...authorityScope(this.capabilityValue),
|
|
48961
|
+
operation_id: request.operation_id,
|
|
48962
|
+
step_id: request.step_id,
|
|
48963
|
+
resource_kind: request.resource_kind,
|
|
48964
|
+
direction: request.direction
|
|
48965
|
+
});
|
|
48966
|
+
const resolved = await this.existingForwardResolution(transaction, request, callDigest);
|
|
48967
|
+
if (resolved)
|
|
48968
|
+
return resolved;
|
|
48969
|
+
const timestamp4 = this.now();
|
|
48970
|
+
const claimed = await transaction.claimBinding(bindingFor(request, callDigest, timestamp4, this.capabilityValue));
|
|
48971
|
+
if (!claimed) {
|
|
48972
|
+
const binding = await transaction.getBinding(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector);
|
|
48973
|
+
if (binding?.state === "accepted" && binding.normalized_call_digest === callDigest && binding.accepted_receipt_id) {
|
|
48974
|
+
const accepted2 = await transaction.getReceiptById(binding.accepted_receipt_id);
|
|
48975
|
+
if (accepted2) {
|
|
48976
|
+
return this.duplicateFor(transaction, request, callDigest, accepted2);
|
|
48977
|
+
}
|
|
48978
|
+
}
|
|
48979
|
+
return this.terminalFor(transaction, request, callDigest, binding?.state === "removed" ? "target_registration_was_removed" : "target_already_registered", { targetId: binding?.target_id ?? null });
|
|
48980
|
+
}
|
|
48981
|
+
const recordOrTerminal = await this.createObject(transaction, request);
|
|
48982
|
+
if ("outcome" in recordOrTerminal) {
|
|
48983
|
+
await transaction.setBindingTerminal(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector, this.now());
|
|
48984
|
+
return recordOrTerminal;
|
|
48985
|
+
}
|
|
48986
|
+
const accepted = makeAcceptedReceipt(request, callDigest, this.capabilityValue, recordOrTerminal, this.now());
|
|
48987
|
+
await this.fault("before_receipt_write", request);
|
|
48988
|
+
const stored = await insertDeterministicReceipt(transaction, accepted);
|
|
48989
|
+
await this.fault("after_receipt_write", request);
|
|
48990
|
+
await transaction.setBindingAccepted(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector, {
|
|
48991
|
+
target_id: recordOrTerminal.target_id,
|
|
48992
|
+
accepted_receipt_id: stored.receipt_id,
|
|
48993
|
+
result_revision: recordOrTerminal.revision,
|
|
48994
|
+
result_digest: recordOrTerminal.digest,
|
|
48995
|
+
updated_at: this.now()
|
|
48996
|
+
});
|
|
48997
|
+
return stored;
|
|
48998
|
+
});
|
|
48999
|
+
await this.afterCommit(request);
|
|
49000
|
+
const receipt = publicReceipt(row);
|
|
49001
|
+
assertWithinBounds(receipt, request, startedAt);
|
|
49002
|
+
return receipt;
|
|
49003
|
+
} catch (error) {
|
|
49004
|
+
if (!(error instanceof WriteBoundaryError))
|
|
49005
|
+
throw error;
|
|
49006
|
+
const terminal = await this.recordWriteFailure(request, callDigest, error.point);
|
|
49007
|
+
const receipt = publicReceipt(terminal);
|
|
49008
|
+
assertWithinBounds(receipt, request, startedAt);
|
|
49009
|
+
return receipt;
|
|
49010
|
+
}
|
|
49011
|
+
}
|
|
49012
|
+
async recordWriteFailure(request, callDigest, point) {
|
|
49013
|
+
return this.backend.transaction(async (transaction) => {
|
|
49014
|
+
await transaction.lockStep({
|
|
49015
|
+
...authorityScope(this.capabilityValue),
|
|
49016
|
+
operation_id: request.operation_id,
|
|
49017
|
+
step_id: request.step_id,
|
|
49018
|
+
resource_kind: request.resource_kind,
|
|
49019
|
+
direction: request.direction
|
|
49020
|
+
});
|
|
49021
|
+
const exact = await transaction.getReceiptForLookup({
|
|
49022
|
+
...authorityScope(this.capabilityValue),
|
|
49023
|
+
operation_id: request.operation_id,
|
|
49024
|
+
step_id: request.step_id,
|
|
49025
|
+
resource_kind: request.resource_kind,
|
|
49026
|
+
direction: request.direction,
|
|
49027
|
+
idempotency_key: request.idempotency_key,
|
|
49028
|
+
target_selector: request.target_selector
|
|
49029
|
+
});
|
|
49030
|
+
if (exact)
|
|
49031
|
+
return exact;
|
|
49032
|
+
const accepted = await transaction.getAcceptedReceiptForStep({
|
|
49033
|
+
...authorityScope(this.capabilityValue),
|
|
49034
|
+
operation_id: request.operation_id,
|
|
49035
|
+
step_id: request.step_id,
|
|
49036
|
+
resource_kind: request.resource_kind,
|
|
49037
|
+
direction: request.direction
|
|
49038
|
+
});
|
|
49039
|
+
if (accepted) {
|
|
49040
|
+
return accepted.normalized_call_digest === callDigest ? this.duplicateFor(transaction, request, callDigest, accepted) : this.terminalFor(transaction, request, callDigest, "operation_step_semantics_changed", { targetId: accepted.target_id });
|
|
49041
|
+
}
|
|
49042
|
+
const timestamp4 = this.now();
|
|
49043
|
+
const claimed = await transaction.claimBinding(bindingFor(request, callDigest, timestamp4, this.capabilityValue));
|
|
49044
|
+
const terminal = await this.terminalFor(transaction, request, callDigest, `write_failed:${point}`);
|
|
49045
|
+
if (claimed) {
|
|
49046
|
+
await transaction.setBindingTerminal(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector, this.now());
|
|
49047
|
+
}
|
|
49048
|
+
return terminal;
|
|
49049
|
+
});
|
|
49050
|
+
}
|
|
49051
|
+
async readExact(request) {
|
|
49052
|
+
const startedAt = Date.now();
|
|
49053
|
+
assertBounds(request);
|
|
49054
|
+
assertResourceKind(request.resource_kind);
|
|
49055
|
+
if (!UUID_PATTERN.test(request.target_id)) {
|
|
49056
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED", "exact readback requires a complete Todos object UUID");
|
|
49057
|
+
}
|
|
49058
|
+
const record = request.resource_kind === "project" ? await this.backend.getProject(request.target_id).then((value) => value ? projectRecord(value) : null) : await this.backend.getTaskList(request.target_id).then((value) => value ? taskListRecord(value) : null);
|
|
49059
|
+
if (!record) {
|
|
49060
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_RECORD_NOT_FOUND", `registered ${request.resource_kind} was not found by exact id`, { target_id: request.target_id });
|
|
49061
|
+
}
|
|
49062
|
+
assertWithinBounds(record, request, startedAt);
|
|
49063
|
+
return record;
|
|
49064
|
+
}
|
|
49065
|
+
async lookupReceipt(request) {
|
|
49066
|
+
const startedAt = Date.now();
|
|
49067
|
+
assertBounds(request);
|
|
49068
|
+
assertResourceKind(request.resource_kind);
|
|
49069
|
+
assertDirection(request.direction);
|
|
49070
|
+
if (request.max_items !== 1) {
|
|
49071
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "max_items must be exactly 1 for terminal receipt lookup");
|
|
49072
|
+
}
|
|
49073
|
+
if (request.authority !== "todos" || request.authority_route !== this.capabilityValue.route || request.package_version !== this.capabilityValue.package_version || request.authority_id !== this.capabilityValue.authority_id || request.tenant_id !== this.capabilityValue.tenant_id || request.corpus_id !== this.capabilityValue.corpus_id) {
|
|
49074
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH", "receipt lookup does not match this authority capability identity");
|
|
49075
|
+
}
|
|
49076
|
+
requireString(request.operation_id, "operation_id", {
|
|
49077
|
+
min: 8,
|
|
49078
|
+
max: 128,
|
|
49079
|
+
pattern: OPERATION_PATTERN
|
|
49080
|
+
});
|
|
49081
|
+
requireString(request.step_id, "step_id", {
|
|
49082
|
+
min: 3,
|
|
49083
|
+
max: 128,
|
|
49084
|
+
pattern: STEP_PATTERN
|
|
49085
|
+
});
|
|
49086
|
+
requireString(request.target_selector, "target_selector", { max: 512 });
|
|
49087
|
+
requireString(request.idempotency_key, "idempotency_key", {
|
|
49088
|
+
min: 52,
|
|
49089
|
+
max: 52,
|
|
49090
|
+
pattern: IDEMPOTENCY_PATTERN
|
|
49091
|
+
});
|
|
49092
|
+
if (request.target_id !== undefined && !UUID_PATTERN.test(request.target_id)) {
|
|
49093
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED", "receipt lookup target_id must be a complete Todos object UUID");
|
|
49094
|
+
}
|
|
49095
|
+
const receipt = await this.backend.getReceiptForLookup({
|
|
49096
|
+
...authorityScope(this.capabilityValue),
|
|
49097
|
+
operation_id: request.operation_id,
|
|
49098
|
+
step_id: request.step_id,
|
|
49099
|
+
resource_kind: request.resource_kind,
|
|
49100
|
+
direction: request.direction,
|
|
49101
|
+
idempotency_key: request.idempotency_key,
|
|
49102
|
+
target_selector: request.target_selector
|
|
49103
|
+
});
|
|
49104
|
+
if (!receipt || request.target_id && receipt.target_id !== request.target_id) {
|
|
49105
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND", "no exact terminal receipt matched the bounded lookup");
|
|
49106
|
+
}
|
|
49107
|
+
return withResponseControl({ receipt: publicReceipt(receipt) }, request, startedAt);
|
|
49108
|
+
}
|
|
49109
|
+
async storedAcceptedReceipt(request, supplied) {
|
|
49110
|
+
const stored = await this.backend.getReceiptById(supplied.receipt_id);
|
|
49111
|
+
if (!stored || stored.outcome !== "accepted" || !stored.created_by_operation || canonicalProjectRegistrationJson(publicReceipt(stored)) !== canonicalProjectRegistrationJson(supplied)) {
|
|
49112
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND", "accepted receipt is not an exact immutable receipt owned by this authority", { receipt_id: supplied.receipt_id });
|
|
49113
|
+
}
|
|
49114
|
+
if (stored.operation_id !== request.operation_id || stored.step_id !== request.step_id || stored.resource_kind !== request.resource_kind || stored.target_id !== supplied.target_id) {
|
|
49115
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND", "accepted receipt does not own this exact operation step and target");
|
|
49116
|
+
}
|
|
49117
|
+
return stored;
|
|
49118
|
+
}
|
|
49119
|
+
async compensate(request) {
|
|
49120
|
+
const startedAt = Date.now();
|
|
49121
|
+
const suppliedAccepted = assertInverseRequest(request, this.capabilityValue);
|
|
49122
|
+
const accepted = await this.storedAcceptedReceipt(request, suppliedAccepted);
|
|
49123
|
+
const callDigest = normalizedCallDigest(request);
|
|
49124
|
+
try {
|
|
49125
|
+
const row = await this.backend.transaction(async (transaction) => {
|
|
49126
|
+
await transaction.lockStep({
|
|
49127
|
+
...authorityScope(this.capabilityValue),
|
|
49128
|
+
operation_id: request.operation_id,
|
|
49129
|
+
step_id: request.step_id,
|
|
49130
|
+
resource_kind: request.resource_kind,
|
|
49131
|
+
direction: request.direction
|
|
49132
|
+
});
|
|
49133
|
+
const exact = await transaction.getReceiptForLookup({
|
|
49134
|
+
...authorityScope(this.capabilityValue),
|
|
49135
|
+
operation_id: request.operation_id,
|
|
49136
|
+
step_id: request.step_id,
|
|
49137
|
+
resource_kind: request.resource_kind,
|
|
49138
|
+
direction: "inverse",
|
|
49139
|
+
idempotency_key: request.idempotency_key,
|
|
49140
|
+
target_selector: request.target_selector
|
|
49141
|
+
});
|
|
49142
|
+
if (exact)
|
|
49143
|
+
return exact;
|
|
49144
|
+
const storedAccepted = await transaction.getReceiptById(accepted.receipt_id);
|
|
49145
|
+
if (!storedAccepted || storedAccepted.outcome !== "accepted" || !storedAccepted.created_by_operation) {
|
|
49146
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND", "accepted receipt disappeared before conditional inverse");
|
|
49147
|
+
}
|
|
49148
|
+
const binding = await transaction.getBinding(authorityScope(this.capabilityValue), accepted.resource_kind, accepted.target_selector);
|
|
49149
|
+
if (!binding || binding.state !== "accepted" || binding.accepted_receipt_id !== accepted.receipt_id || binding.target_id !== accepted.target_id) {
|
|
49150
|
+
return this.terminalFor(transaction, request, callDigest, "target_not_owned_by_receipt", {
|
|
49151
|
+
targetId: accepted.target_id,
|
|
49152
|
+
acceptedReceiptId: accepted.receipt_id
|
|
49153
|
+
});
|
|
49154
|
+
}
|
|
49155
|
+
await transaction.lockCompensationWrites();
|
|
49156
|
+
const object = request.resource_kind === "project" ? await transaction.getProject(accepted.target_id) : await transaction.getTaskList(accepted.target_id);
|
|
49157
|
+
if (!object) {
|
|
49158
|
+
return this.terminalFor(transaction, request, callDigest, "target_missing_before_inverse", {
|
|
49159
|
+
targetId: accepted.target_id,
|
|
49160
|
+
acceptedReceiptId: accepted.receipt_id
|
|
49161
|
+
});
|
|
49162
|
+
}
|
|
49163
|
+
const current = request.resource_kind === "project" ? projectRecord(object) : taskListRecord(object);
|
|
49164
|
+
if (current.revision !== accepted.result_revision || current.digest !== accepted.result_digest) {
|
|
49165
|
+
return this.terminalFor(transaction, request, callDigest, "target_drifted", {
|
|
49166
|
+
targetId: accepted.target_id,
|
|
49167
|
+
acceptedReceiptId: accepted.receipt_id
|
|
49168
|
+
});
|
|
49169
|
+
}
|
|
49170
|
+
if (await transaction.hasDependents(request.resource_kind, accepted.target_id)) {
|
|
49171
|
+
return this.terminalFor(transaction, request, callDigest, "target_has_dependents", {
|
|
49172
|
+
targetId: accepted.target_id,
|
|
49173
|
+
acceptedReceiptId: accepted.receipt_id
|
|
49174
|
+
});
|
|
49175
|
+
}
|
|
49176
|
+
await this.fault("before_object_write", request);
|
|
49177
|
+
const deleted = request.resource_kind === "project" ? await transaction.deleteProject(accepted.target_id) : await transaction.deleteTaskList(accepted.target_id);
|
|
49178
|
+
if (!deleted) {
|
|
49179
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "conditional inverse could not delete the exact accepted target");
|
|
49180
|
+
}
|
|
49181
|
+
await this.fault("after_object_write", request);
|
|
49182
|
+
const inverseRecord = {
|
|
49183
|
+
target_id: accepted.target_id,
|
|
49184
|
+
revision: "absent",
|
|
49185
|
+
digest: digestProjectRegistrationValue({
|
|
49186
|
+
target_id: accepted.target_id,
|
|
49187
|
+
accepted_receipt_id: accepted.receipt_id,
|
|
49188
|
+
absent: true
|
|
49189
|
+
})
|
|
49190
|
+
};
|
|
49191
|
+
const inverse = makeAcceptedReceipt(request, callDigest, this.capabilityValue, inverseRecord, this.now());
|
|
49192
|
+
await this.fault("before_receipt_write", request);
|
|
49193
|
+
const stored = await insertDeterministicReceipt(transaction, inverse);
|
|
49194
|
+
await this.fault("after_receipt_write", request);
|
|
49195
|
+
await transaction.setBindingRemoved(authorityScope(this.capabilityValue), accepted.resource_kind, accepted.target_selector, stored.receipt_id, this.now());
|
|
49196
|
+
return stored;
|
|
49197
|
+
});
|
|
49198
|
+
await this.afterCommit(request);
|
|
49199
|
+
const receipt = publicReceipt(row);
|
|
49200
|
+
assertWithinBounds(receipt, request, startedAt);
|
|
49201
|
+
return receipt;
|
|
49202
|
+
} catch (error) {
|
|
49203
|
+
if (!(error instanceof WriteBoundaryError))
|
|
49204
|
+
throw error;
|
|
49205
|
+
const terminal = await this.backend.transaction(async (transaction) => {
|
|
49206
|
+
await transaction.lockStep({
|
|
49207
|
+
...authorityScope(this.capabilityValue),
|
|
49208
|
+
operation_id: request.operation_id,
|
|
49209
|
+
step_id: request.step_id,
|
|
49210
|
+
resource_kind: request.resource_kind,
|
|
49211
|
+
direction: request.direction
|
|
49212
|
+
});
|
|
49213
|
+
return this.terminalFor(transaction, request, callDigest, `write_failed:${error.point}`, {
|
|
49214
|
+
targetId: accepted.target_id,
|
|
49215
|
+
acceptedReceiptId: accepted.receipt_id
|
|
49216
|
+
});
|
|
49217
|
+
});
|
|
49218
|
+
const receipt = publicReceipt(terminal);
|
|
49219
|
+
assertWithinBounds(receipt, request, startedAt);
|
|
49220
|
+
return receipt;
|
|
49221
|
+
}
|
|
49222
|
+
}
|
|
49223
|
+
async verifyInverse(request) {
|
|
49224
|
+
const startedAt = Date.now();
|
|
49225
|
+
const accepted = assertInverseRequest(request, this.capabilityValue);
|
|
49226
|
+
await this.storedAcceptedReceipt(request, accepted);
|
|
49227
|
+
const receipt = await this.backend.getReceiptForLookup({
|
|
49228
|
+
...authorityScope(this.capabilityValue),
|
|
49229
|
+
operation_id: request.operation_id,
|
|
49230
|
+
step_id: request.step_id,
|
|
49231
|
+
resource_kind: request.resource_kind,
|
|
49232
|
+
direction: "inverse",
|
|
49233
|
+
idempotency_key: request.idempotency_key,
|
|
49234
|
+
target_selector: request.target_selector
|
|
49235
|
+
});
|
|
49236
|
+
if (!receipt || receipt.outcome !== "accepted" || receipt.accepted_receipt_id !== accepted.receipt_id || receipt.result_revision !== "absent" || !receipt.result_digest) {
|
|
49237
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND", "accepted conditional inverse receipt was not found");
|
|
49238
|
+
}
|
|
49239
|
+
const object = request.resource_kind === "project" ? await this.backend.getProject(accepted.target_id) : await this.backend.getTaskList(accepted.target_id);
|
|
49240
|
+
if (object) {
|
|
49241
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "inverse verification found the accepted target still present");
|
|
49242
|
+
}
|
|
49243
|
+
const verification = {
|
|
49244
|
+
target_id: accepted.target_id,
|
|
49245
|
+
accepted_receipt_id: accepted.receipt_id,
|
|
49246
|
+
absent: true,
|
|
49247
|
+
digest: digestProjectRegistrationValue({
|
|
49248
|
+
target_id: accepted.target_id,
|
|
49249
|
+
accepted_receipt_id: accepted.receipt_id,
|
|
49250
|
+
absent: true
|
|
49251
|
+
})
|
|
49252
|
+
};
|
|
49253
|
+
assertWithinBounds(verification, request, startedAt);
|
|
49254
|
+
if (verification.digest !== receipt.result_digest) {
|
|
49255
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "inverse verification digest does not match the immutable receipt");
|
|
49256
|
+
}
|
|
49257
|
+
return verification;
|
|
49258
|
+
}
|
|
49259
|
+
}
|
|
49260
|
+
function createPostgresTodosProjectRegistrationAuthority(client, options = {}) {
|
|
49261
|
+
const {
|
|
49262
|
+
service,
|
|
49263
|
+
tableName,
|
|
49264
|
+
cursorTableName,
|
|
49265
|
+
...authorityOptions
|
|
49266
|
+
} = options;
|
|
49267
|
+
return new PackageOwnedTodosProjectRegistrationAuthority(new PostgresTodosProjectRegistrationBackend(client, {
|
|
49268
|
+
service,
|
|
49269
|
+
tableName,
|
|
49270
|
+
cursorTableName
|
|
49271
|
+
}), authorityOptions);
|
|
49272
|
+
}
|
|
49273
|
+
var UUID_PATTERN, WORKSPACE_ID_PATTERN, OPERATION_PATTERN, STEP_PATTERN, SHA256_PATTERN, IDEMPOTENCY_PATTERN, WriteBoundaryError;
|
|
49274
|
+
var init_authority = __esm(() => {
|
|
49275
|
+
init_package_version();
|
|
49276
|
+
init_postgres2();
|
|
49277
|
+
init_sqlite();
|
|
49278
|
+
init_types4();
|
|
49279
|
+
UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
49280
|
+
WORKSPACE_ID_PATTERN = /^wks_[A-Za-z0-9][A-Za-z0-9_-]{11,}$/;
|
|
49281
|
+
OPERATION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
|
|
49282
|
+
STEP_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$/;
|
|
49283
|
+
SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
49284
|
+
IDEMPOTENCY_PATTERN = /^prk_[0-9a-f]{48}$/;
|
|
49285
|
+
WriteBoundaryError = class WriteBoundaryError extends Error {
|
|
49286
|
+
point;
|
|
49287
|
+
cause;
|
|
49288
|
+
constructor(point, cause) {
|
|
49289
|
+
super(`Todos project registration failed at ${point}`);
|
|
49290
|
+
this.point = point;
|
|
49291
|
+
this.cause = cause;
|
|
49292
|
+
}
|
|
49293
|
+
};
|
|
49294
|
+
});
|
|
49295
|
+
|
|
49296
|
+
// src/project-registration/http.ts
|
|
49297
|
+
function json(body, status = 200) {
|
|
49298
|
+
return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS });
|
|
49299
|
+
}
|
|
49300
|
+
function errorStatus(error) {
|
|
49301
|
+
switch (error.code) {
|
|
49302
|
+
case "TODOS_PROJECT_REGISTRATION_INVALID_INPUT":
|
|
49303
|
+
case "TODOS_PROJECT_REGISTRATION_INVALID_BOUNDS":
|
|
49304
|
+
case "TODOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH":
|
|
49305
|
+
case "TODOS_PROJECT_REGISTRATION_DIGEST_MISMATCH":
|
|
49306
|
+
case "TODOS_PROJECT_REGISTRATION_IDEMPOTENCY_MISMATCH":
|
|
49307
|
+
case "TODOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED":
|
|
49308
|
+
return 400;
|
|
49309
|
+
case "TODOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND":
|
|
49310
|
+
case "TODOS_PROJECT_REGISTRATION_RECORD_NOT_FOUND":
|
|
49311
|
+
case "TODOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND":
|
|
49312
|
+
return 404;
|
|
49313
|
+
case "TODOS_PROJECT_REGISTRATION_RESPONSE_TOO_LARGE":
|
|
49314
|
+
return 413;
|
|
49315
|
+
case "TODOS_PROJECT_REGISTRATION_TIME_BUDGET_EXCEEDED":
|
|
49316
|
+
return 408;
|
|
49317
|
+
case "TODOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE":
|
|
49318
|
+
return 503;
|
|
49319
|
+
default:
|
|
49320
|
+
return 409;
|
|
49321
|
+
}
|
|
49322
|
+
}
|
|
49323
|
+
async function readJson(req) {
|
|
49324
|
+
try {
|
|
49325
|
+
const value = await req.json();
|
|
49326
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
49327
|
+
} catch {
|
|
49328
|
+
return null;
|
|
49329
|
+
}
|
|
49330
|
+
}
|
|
49331
|
+
async function handleTodosProjectRegistrationHttpRequest(req, url, authority, basePath = "/v1/project-registration") {
|
|
49332
|
+
const path = url.pathname;
|
|
49333
|
+
if (path !== basePath && !path.startsWith(`${basePath}/`))
|
|
49334
|
+
return null;
|
|
49335
|
+
const action = path.slice(basePath.length).split("/").filter(Boolean).join("/");
|
|
49336
|
+
const method = req.method.toUpperCase();
|
|
49337
|
+
try {
|
|
49338
|
+
if ((action === "" || action === "capability") && method === "GET") {
|
|
49339
|
+
return json({ capability: await authority.capability() });
|
|
49340
|
+
}
|
|
49341
|
+
if (method !== "POST")
|
|
49342
|
+
return json({ error: "method not allowed" }, 405);
|
|
49343
|
+
const body = await readJson(req);
|
|
49344
|
+
if (!body) {
|
|
49345
|
+
return json({
|
|
49346
|
+
error: "invalid JSON body",
|
|
49347
|
+
code: "TODOS_PROJECT_REGISTRATION_INVALID_INPUT"
|
|
49348
|
+
}, 400);
|
|
49349
|
+
}
|
|
49350
|
+
if (action === "create") {
|
|
49351
|
+
return json({
|
|
49352
|
+
receipt: await authority.create(body)
|
|
49353
|
+
}, 201);
|
|
49354
|
+
}
|
|
49355
|
+
if (action === "receipts/lookup") {
|
|
49356
|
+
return json(await authority.lookupReceipt(body));
|
|
49357
|
+
}
|
|
49358
|
+
if (action === "read-exact") {
|
|
49359
|
+
return json({
|
|
49360
|
+
record: await authority.readExact(body)
|
|
49361
|
+
});
|
|
49362
|
+
}
|
|
49363
|
+
if (action === "compensate") {
|
|
49364
|
+
return json({
|
|
49365
|
+
receipt: await authority.compensate(body)
|
|
49366
|
+
}, 201);
|
|
49367
|
+
}
|
|
49368
|
+
if (action === "verify-inverse") {
|
|
49369
|
+
return json({
|
|
49370
|
+
verification: await authority.verifyInverse(body)
|
|
49371
|
+
});
|
|
49372
|
+
}
|
|
49373
|
+
return json({
|
|
49374
|
+
error: "unknown Todos project-registration route",
|
|
49375
|
+
code: "TODOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND"
|
|
49376
|
+
}, 404);
|
|
49377
|
+
} catch (cause) {
|
|
49378
|
+
if (cause instanceof TodosProjectRegistrationError) {
|
|
49379
|
+
return json({
|
|
49380
|
+
error: cause.message,
|
|
49381
|
+
code: cause.code,
|
|
49382
|
+
details: cause.details,
|
|
49383
|
+
authoritative: true
|
|
49384
|
+
}, errorStatus(cause));
|
|
49385
|
+
}
|
|
49386
|
+
return json({
|
|
49387
|
+
error: cause instanceof Error ? cause.message : "internal registration error",
|
|
49388
|
+
code: "TODOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE"
|
|
49389
|
+
}, 500);
|
|
49390
|
+
}
|
|
49391
|
+
}
|
|
49392
|
+
var JSON_HEADERS;
|
|
49393
|
+
var init_http2 = __esm(() => {
|
|
49394
|
+
init_types4();
|
|
49395
|
+
JSON_HEADERS = { "Content-Type": "application/json" };
|
|
49396
|
+
});
|
|
49397
|
+
|
|
49398
|
+
// src/project-registration/index.ts
|
|
49399
|
+
var init_project_registration = __esm(() => {
|
|
49400
|
+
init_authority();
|
|
49401
|
+
init_http2();
|
|
49402
|
+
init_postgres2();
|
|
49403
|
+
init_sqlite();
|
|
49404
|
+
init_types4();
|
|
49405
|
+
});
|
|
49406
|
+
|
|
47726
49407
|
// src/storage/comment-redaction-backfill.ts
|
|
47727
49408
|
function assertSafeIdentifier2(value) {
|
|
47728
49409
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
|
|
@@ -47831,6 +49512,7 @@ __export(exports_cloud, {
|
|
|
47831
49512
|
isCloudModeEnabled: () => isCloudModeEnabled,
|
|
47832
49513
|
getCloudVerifier: () => getCloudVerifier,
|
|
47833
49514
|
getCloudStorageAdapter: () => getCloudStorageAdapter,
|
|
49515
|
+
getCloudProjectRegistrationAuthority: () => getCloudProjectRegistrationAuthority,
|
|
47834
49516
|
getCloudPrGroupLedger: () => getCloudPrGroupLedger,
|
|
47835
49517
|
getApiKeyStore: () => getApiKeyStore,
|
|
47836
49518
|
ensureCloudTaskShortIdIndex: () => ensureCloudTaskShortIdIndex,
|
|
@@ -47879,6 +49561,17 @@ function getCloudPrGroupLedger() {
|
|
|
47879
49561
|
cachedPrGroupLedger = new PrGroupLedger(new PostgresPrGroupLedgerPersistence(getClient()));
|
|
47880
49562
|
return cachedPrGroupLedger;
|
|
47881
49563
|
}
|
|
49564
|
+
function getCloudProjectRegistrationAuthority() {
|
|
49565
|
+
if (cachedProjectRegistrationAuthority)
|
|
49566
|
+
return cachedProjectRegistrationAuthority;
|
|
49567
|
+
cachedProjectRegistrationAuthority = createPostgresTodosProjectRegistrationAuthority(getClient(), {
|
|
49568
|
+
service: TODOS_APP_SLUG,
|
|
49569
|
+
authorityId: TODOS_APP_SLUG,
|
|
49570
|
+
tenantId: process.env.HASNA_TODOS_TENANT_ID ?? "default",
|
|
49571
|
+
corpusId: process.env.HASNA_TODOS_CORPUS_ID ?? `${TODOS_APP_SLUG}:postgresql`
|
|
49572
|
+
});
|
|
49573
|
+
return cachedProjectRegistrationAuthority;
|
|
49574
|
+
}
|
|
47882
49575
|
function authClient() {
|
|
47883
49576
|
const client = getClient();
|
|
47884
49577
|
return {
|
|
@@ -47927,6 +49620,9 @@ async function ensureCloudSchema() {
|
|
|
47927
49620
|
for (const sql of postgresPrGroupSchemaSql()) {
|
|
47928
49621
|
await client.query(sql);
|
|
47929
49622
|
}
|
|
49623
|
+
for (const sql of postgresTodosProjectRegistrationSchemaSql()) {
|
|
49624
|
+
await client.query(sql);
|
|
49625
|
+
}
|
|
47930
49626
|
await getApiKeyStore().ensureSchema();
|
|
47931
49627
|
})();
|
|
47932
49628
|
return schemaEnsured;
|
|
@@ -47967,14 +49663,16 @@ async function closeCloud() {
|
|
|
47967
49663
|
cachedStore = null;
|
|
47968
49664
|
cachedVerifier = null;
|
|
47969
49665
|
cachedPrGroupLedger = null;
|
|
49666
|
+
cachedProjectRegistrationAuthority = null;
|
|
47970
49667
|
schemaEnsured = null;
|
|
47971
49668
|
}
|
|
47972
|
-
var TODOS_APP_SLUG = "todos", cachedClient = null, cachedAdapter = null, cachedStore = null, cachedVerifier = null, cachedPrGroupLedger = null, schemaEnsured = null;
|
|
49669
|
+
var TODOS_APP_SLUG = "todos", cachedClient = null, cachedAdapter = null, cachedStore = null, cachedVerifier = null, cachedPrGroupLedger = null, cachedProjectRegistrationAuthority = null, schemaEnsured = null;
|
|
47973
49670
|
var init_cloud = __esm(() => {
|
|
47974
49671
|
init_cloud_client();
|
|
47975
49672
|
init_postgres_adapter();
|
|
47976
49673
|
init_ledger();
|
|
47977
49674
|
init_postgres();
|
|
49675
|
+
init_project_registration();
|
|
47978
49676
|
init_postgres_sync();
|
|
47979
49677
|
init_comment_redaction_backfill();
|
|
47980
49678
|
});
|
|
@@ -48333,9 +50031,9 @@ function parseBoundedLimit(value, fallback, max) {
|
|
|
48333
50031
|
return fallback;
|
|
48334
50032
|
return Math.min(parsed, max);
|
|
48335
50033
|
}
|
|
48336
|
-
function mapTaskError(e,
|
|
50034
|
+
function mapTaskError(e, json3) {
|
|
48337
50035
|
if (e instanceof VersionConflictError) {
|
|
48338
|
-
return
|
|
50036
|
+
return json3({
|
|
48339
50037
|
error: e.message,
|
|
48340
50038
|
code: VersionConflictError.code,
|
|
48341
50039
|
expected_version: e.expectedVersion,
|
|
@@ -48343,23 +50041,23 @@ function mapTaskError(e, json2) {
|
|
|
48343
50041
|
}, 409);
|
|
48344
50042
|
}
|
|
48345
50043
|
if (e instanceof TaskNotFoundError) {
|
|
48346
|
-
return
|
|
50044
|
+
return json3({ error: e.message, code: TaskNotFoundError.code }, 404);
|
|
48347
50045
|
}
|
|
48348
50046
|
if (e instanceof LockError) {
|
|
48349
|
-
return
|
|
50047
|
+
return json3({ error: e.message, code: LockError.code }, 409);
|
|
48350
50048
|
}
|
|
48351
50049
|
if (e instanceof CompletionGuardError) {
|
|
48352
|
-
return
|
|
50050
|
+
return json3({
|
|
48353
50051
|
error: e.message,
|
|
48354
50052
|
code: CompletionGuardError.code,
|
|
48355
50053
|
retry_after: e.retryAfterSeconds ?? null
|
|
48356
50054
|
}, 409);
|
|
48357
50055
|
}
|
|
48358
50056
|
if (e instanceof TaskNotStartableError) {
|
|
48359
|
-
return
|
|
50057
|
+
return json3({ error: e.message, code: TaskNotStartableError.code }, 409);
|
|
48360
50058
|
}
|
|
48361
50059
|
if (e instanceof Error && / is blocked by /.test(e.message)) {
|
|
48362
|
-
return
|
|
50060
|
+
return json3({ error: e.message, code: "TASK_NOT_STARTABLE" }, 409);
|
|
48363
50061
|
}
|
|
48364
50062
|
return null;
|
|
48365
50063
|
}
|
|
@@ -48444,11 +50142,11 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
48444
50142
|
}
|
|
48445
50143
|
});
|
|
48446
50144
|
}
|
|
48447
|
-
function handleHealth(_ctx,
|
|
50145
|
+
function handleHealth(_ctx, json3) {
|
|
48448
50146
|
const stats2 = getTaskStats();
|
|
48449
50147
|
const staleCount = getStaleTasks(30).length;
|
|
48450
50148
|
const overdueRecurring = getOverdueTasks().filter((t) => t.recurrence_rule && t.status === "pending").length;
|
|
48451
|
-
return
|
|
50149
|
+
return json3({
|
|
48452
50150
|
status: staleCount === 0 && overdueRecurring === 0 ? "ok" : "warn",
|
|
48453
50151
|
tasks: stats2.total,
|
|
48454
50152
|
stale: staleCount,
|
|
@@ -48456,18 +50154,18 @@ function handleHealth(_ctx, json2) {
|
|
|
48456
50154
|
timestamp: new Date().toISOString()
|
|
48457
50155
|
});
|
|
48458
50156
|
}
|
|
48459
|
-
function handleHeadlessBoundary(_ctx,
|
|
50157
|
+
function handleHeadlessBoundary(_ctx, json3) {
|
|
48460
50158
|
const { getHeadlessBoundaryManifest: getHeadlessBoundaryManifest2 } = (init_headless_boundaries(), __toCommonJS(exports_headless_boundaries));
|
|
48461
|
-
return
|
|
50159
|
+
return json3(getHeadlessBoundaryManifest2());
|
|
48462
50160
|
}
|
|
48463
|
-
function handleStats(_ctx,
|
|
50161
|
+
function handleStats(_ctx, json3) {
|
|
48464
50162
|
const stats2 = getTaskStats();
|
|
48465
50163
|
const byStatus = stats2.by_status;
|
|
48466
50164
|
const projects = listProjects();
|
|
48467
50165
|
const agents = listAgents();
|
|
48468
50166
|
const staleCount = getStaleTasks(30).length;
|
|
48469
50167
|
const overdueRecurring = getOverdueTasks().filter((t) => t.recurrence_rule && t.status === "pending").length;
|
|
48470
|
-
return
|
|
50168
|
+
return json3({
|
|
48471
50169
|
total_tasks: stats2.total,
|
|
48472
50170
|
pending: byStatus["pending"] ?? 0,
|
|
48473
50171
|
in_progress: byStatus["in_progress"] ?? 0,
|
|
@@ -48490,10 +50188,10 @@ function taskStatusQueryParam(url) {
|
|
|
48490
50188
|
return { ok: false, message: result.message };
|
|
48491
50189
|
return { ok: true, value: collapseEnumValues(result.values) };
|
|
48492
50190
|
}
|
|
48493
|
-
async function handleListTasks(_req, url, _ctx,
|
|
50191
|
+
async function handleListTasks(_req, url, _ctx, json3, taskToSummary2) {
|
|
48494
50192
|
const statusParam = taskStatusQueryParam(url);
|
|
48495
50193
|
if (!statusParam.ok)
|
|
48496
|
-
return
|
|
50194
|
+
return json3({ error: statusParam.message }, 400);
|
|
48497
50195
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
48498
50196
|
const sessionId = url.searchParams.get("session_id") || undefined;
|
|
48499
50197
|
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
@@ -48508,13 +50206,13 @@ async function handleListTasks(_req, url, _ctx, json2, taskToSummary2) {
|
|
|
48508
50206
|
limit: limitParam ? parseInt(limitParam, 10) : undefined,
|
|
48509
50207
|
offset: offsetParam ? parseInt(offsetParam, 10) : undefined
|
|
48510
50208
|
});
|
|
48511
|
-
return
|
|
50209
|
+
return json3(tasks.map((t) => taskToSummary2(t, fields)));
|
|
48512
50210
|
}
|
|
48513
|
-
async function handleCreateTask(req, ctx,
|
|
50211
|
+
async function handleCreateTask(req, ctx, json3, taskToSummary2) {
|
|
48514
50212
|
try {
|
|
48515
50213
|
const body = await req.json();
|
|
48516
50214
|
if (!body.title)
|
|
48517
|
-
return
|
|
50215
|
+
return json3({ error: "Missing 'title'" }, 400);
|
|
48518
50216
|
const createdBy = body.created_by ?? body.agent_id ?? "dashboard";
|
|
48519
50217
|
const task2 = createTask({
|
|
48520
50218
|
title: body.title,
|
|
@@ -48526,19 +50224,19 @@ async function handleCreateTask(req, ctx, json2, taskToSummary2) {
|
|
|
48526
50224
|
...body.assigned_to ? { assigned_to: body.assigned_to } : {}
|
|
48527
50225
|
});
|
|
48528
50226
|
ctx.broadcastEvent({ type: "task", task_id: task2.id, action: "created", agent_id: task2.agent_id, project_id: task2.project_id });
|
|
48529
|
-
return
|
|
50227
|
+
return json3(taskToSummary2(task2), 201);
|
|
48530
50228
|
} catch (e) {
|
|
48531
|
-
return
|
|
50229
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to create task" }, 500);
|
|
48532
50230
|
}
|
|
48533
50231
|
}
|
|
48534
|
-
async function handleUpsertTask(req, ctx,
|
|
50232
|
+
async function handleUpsertTask(req, ctx, json3, taskToSummary2) {
|
|
48535
50233
|
try {
|
|
48536
50234
|
const body = await req.json();
|
|
48537
50235
|
if (typeof body["fingerprint"] !== "string" || body["fingerprint"].trim() === "") {
|
|
48538
|
-
return
|
|
50236
|
+
return json3({ error: "Missing 'fingerprint'" }, 400);
|
|
48539
50237
|
}
|
|
48540
50238
|
if (typeof body["title"] !== "string" || body["title"].trim() === "") {
|
|
48541
|
-
return
|
|
50239
|
+
return json3({ error: "Missing 'title'" }, 400);
|
|
48542
50240
|
}
|
|
48543
50241
|
const metadata = body["metadata"] && typeof body["metadata"] === "object" && !Array.isArray(body["metadata"]) ? { ...body["metadata"] } : {};
|
|
48544
50242
|
for (const key of ["expectation_id", "expectation_fingerprint", "evidence_paths", "origin_loop_id", "origin_run_id", "expected", "observed", "acceptance"]) {
|
|
@@ -48559,9 +50257,9 @@ async function handleUpsertTask(req, ctx, json2, taskToSummary2) {
|
|
|
48559
50257
|
metadata
|
|
48560
50258
|
});
|
|
48561
50259
|
ctx.broadcastEvent({ type: "task", task_id: result.task.id, action: result.created ? "created" : "updated", agent_id: result.task.agent_id, project_id: result.task.project_id });
|
|
48562
|
-
return
|
|
50260
|
+
return json3({ created: result.created, task: taskToSummary2(result.task) }, result.created ? 201 : 200);
|
|
48563
50261
|
} catch (e) {
|
|
48564
|
-
return
|
|
50262
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to upsert task" }, 500);
|
|
48565
50263
|
}
|
|
48566
50264
|
}
|
|
48567
50265
|
function handleTasksExport(_req, url, _ctx, _json, taskToSummary2) {
|
|
@@ -48613,11 +50311,11 @@ function handleTasksExport(_req, url, _ctx, _json, taskToSummary2) {
|
|
|
48613
50311
|
}
|
|
48614
50312
|
});
|
|
48615
50313
|
}
|
|
48616
|
-
async function handleTasksBulk(req, _ctx,
|
|
50314
|
+
async function handleTasksBulk(req, _ctx, json3) {
|
|
48617
50315
|
try {
|
|
48618
50316
|
const body = await req.json();
|
|
48619
50317
|
if (!body.ids?.length || !body.action)
|
|
48620
|
-
return
|
|
50318
|
+
return json3({ error: "Missing ids or action" }, 400);
|
|
48621
50319
|
const results = [];
|
|
48622
50320
|
for (const id of body.ids) {
|
|
48623
50321
|
try {
|
|
@@ -48635,66 +50333,66 @@ async function handleTasksBulk(req, _ctx, json2) {
|
|
|
48635
50333
|
results.push({ id, success: false, error: e instanceof Error ? e.message : "Failed" });
|
|
48636
50334
|
}
|
|
48637
50335
|
}
|
|
48638
|
-
return
|
|
50336
|
+
return json3({ results, succeeded: results.filter((r) => r.success).length, failed: results.filter((r) => !r.success).length });
|
|
48639
50337
|
} catch (e) {
|
|
48640
|
-
return
|
|
50338
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
48641
50339
|
}
|
|
48642
50340
|
}
|
|
48643
|
-
function handleTasksStatus(_req, url, _ctx,
|
|
50341
|
+
function handleTasksStatus(_req, url, _ctx, json3) {
|
|
48644
50342
|
try {
|
|
48645
50343
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
48646
50344
|
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
48647
50345
|
const status = getStatus(projectId ? { project_id: projectId } : undefined, agentId);
|
|
48648
|
-
return
|
|
50346
|
+
return json3(status);
|
|
48649
50347
|
} catch (e) {
|
|
48650
|
-
return
|
|
50348
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
48651
50349
|
}
|
|
48652
50350
|
}
|
|
48653
|
-
function handleTasksNext(_req, url, _ctx,
|
|
50351
|
+
function handleTasksNext(_req, url, _ctx, json3, taskToSummary2) {
|
|
48654
50352
|
try {
|
|
48655
50353
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
48656
50354
|
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
48657
50355
|
const fields = parseFieldsParam(url);
|
|
48658
50356
|
const task2 = getNextTask(agentId, projectId ? { project_id: projectId } : undefined);
|
|
48659
|
-
return
|
|
50357
|
+
return json3({ task: task2 ? taskToSummary2(task2, fields) : null });
|
|
48660
50358
|
} catch (e) {
|
|
48661
|
-
return
|
|
50359
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
48662
50360
|
}
|
|
48663
50361
|
}
|
|
48664
|
-
function handleTasksActive(_req, url, _ctx,
|
|
50362
|
+
function handleTasksActive(_req, url, _ctx, json3) {
|
|
48665
50363
|
try {
|
|
48666
50364
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
48667
50365
|
const work = getActiveWork(projectId ? { project_id: projectId } : undefined);
|
|
48668
|
-
return
|
|
50366
|
+
return json3({ active: work, count: work.length });
|
|
48669
50367
|
} catch (e) {
|
|
48670
|
-
return
|
|
50368
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
48671
50369
|
}
|
|
48672
50370
|
}
|
|
48673
|
-
function handleTasksStale(_req, url, _ctx,
|
|
50371
|
+
function handleTasksStale(_req, url, _ctx, json3, taskToSummary2) {
|
|
48674
50372
|
try {
|
|
48675
50373
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
48676
50374
|
const minutes2 = parseInt(url.searchParams.get("minutes") || "30", 10);
|
|
48677
50375
|
const fields = parseFieldsParam(url);
|
|
48678
50376
|
const tasks = getStaleTasks(minutes2, projectId ? { project_id: projectId } : undefined);
|
|
48679
|
-
return
|
|
50377
|
+
return json3({ tasks: tasks.map((t) => taskToSummary2(t, fields)), count: tasks.length });
|
|
48680
50378
|
} catch (e) {
|
|
48681
|
-
return
|
|
50379
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
48682
50380
|
}
|
|
48683
50381
|
}
|
|
48684
|
-
function handleTasksChanged(_req, url, _ctx,
|
|
50382
|
+
function handleTasksChanged(_req, url, _ctx, json3, taskToSummary2) {
|
|
48685
50383
|
try {
|
|
48686
50384
|
const since = url.searchParams.get("since");
|
|
48687
50385
|
if (!since)
|
|
48688
|
-
return
|
|
50386
|
+
return json3({ error: "since parameter required (ISO date string)" }, 400);
|
|
48689
50387
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
48690
50388
|
const fields = parseFieldsParam(url);
|
|
48691
50389
|
const tasks = getTasksChangedSince(since, projectId ? { project_id: projectId } : undefined);
|
|
48692
|
-
return
|
|
50390
|
+
return json3({ tasks: tasks.map((t) => taskToSummary2(t, fields)), count: tasks.length, since });
|
|
48693
50391
|
} catch (e) {
|
|
48694
|
-
return
|
|
50392
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
48695
50393
|
}
|
|
48696
50394
|
}
|
|
48697
|
-
function handleTasksContext(_req, url, _ctx,
|
|
50395
|
+
function handleTasksContext(_req, url, _ctx, json3, taskToSummary2) {
|
|
48698
50396
|
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
48699
50397
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
48700
50398
|
const format = url.searchParams.get("format") || "text";
|
|
@@ -48703,7 +50401,7 @@ function handleTasksContext(_req, url, _ctx, json2, taskToSummary2) {
|
|
|
48703
50401
|
const status = getStatus(filters, agentId);
|
|
48704
50402
|
const next = getNextTask(agentId, filters);
|
|
48705
50403
|
if (format === "json") {
|
|
48706
|
-
return
|
|
50404
|
+
return json3({ status, next_task: next ? taskToSummary2(next, fields) : null });
|
|
48707
50405
|
}
|
|
48708
50406
|
const lines = [];
|
|
48709
50407
|
lines.push(`Tasks: ${status.pending} pending | ${status.in_progress} active | ${status.completed} done`);
|
|
@@ -48720,18 +50418,18 @@ function handleTasksContext(_req, url, _ctx, json2, taskToSummary2) {
|
|
|
48720
50418
|
`);
|
|
48721
50419
|
return new Response(text2, { headers: { "Content-Type": "text/plain" } });
|
|
48722
50420
|
}
|
|
48723
|
-
function handleTaskAttachments(id, _ctx,
|
|
50421
|
+
function handleTaskAttachments(id, _ctx, json3) {
|
|
48724
50422
|
const task2 = getTask(id);
|
|
48725
50423
|
if (!task2)
|
|
48726
|
-
return
|
|
50424
|
+
return json3({ error: "Task not found" }, 404);
|
|
48727
50425
|
const evidence = task2.metadata?._evidence || {};
|
|
48728
50426
|
const attachmentIds = evidence.attachments || [];
|
|
48729
|
-
return
|
|
50427
|
+
return json3({ task_id: id, short_id: task2.short_id, attachment_ids: attachmentIds, count: attachmentIds.length, files_changed: evidence.files_changed, commit_hash: evidence.commit_hash, notes: evidence.notes });
|
|
48730
50428
|
}
|
|
48731
|
-
async function handleTaskProgress(id, req, method, _ctx,
|
|
50429
|
+
async function handleTaskProgress(id, req, method, _ctx, json3, url) {
|
|
48732
50430
|
const task2 = getTask(id);
|
|
48733
50431
|
if (!task2)
|
|
48734
|
-
return
|
|
50432
|
+
return json3({ error: "Task not found" }, 404);
|
|
48735
50433
|
if (method === "GET") {
|
|
48736
50434
|
const all = listComments(id);
|
|
48737
50435
|
const progress = all.filter((c) => c.type === "progress");
|
|
@@ -48739,7 +50437,7 @@ async function handleTaskProgress(id, req, method, _ctx, json2, url) {
|
|
|
48739
50437
|
const format = url?.searchParams.get("format") || "compact";
|
|
48740
50438
|
const limit = parseBoundedLimit(url?.searchParams.get("limit") || null, 20, 200);
|
|
48741
50439
|
const progressEntries = format === "full" ? progress : progress.slice(-limit);
|
|
48742
|
-
return
|
|
50440
|
+
return json3({
|
|
48743
50441
|
task_id: id,
|
|
48744
50442
|
progress_entries: progressEntries,
|
|
48745
50443
|
latest,
|
|
@@ -48756,27 +50454,27 @@ async function handleTaskProgress(id, req, method, _ctx, json2, url) {
|
|
|
48756
50454
|
try {
|
|
48757
50455
|
const body = await req.json();
|
|
48758
50456
|
if (!body.message)
|
|
48759
|
-
return
|
|
50457
|
+
return json3({ error: "message required" }, 400);
|
|
48760
50458
|
const comment = logProgress(id, body.message, body.pct_complete, body.agent_id);
|
|
48761
|
-
return
|
|
50459
|
+
return json3(comment, 201);
|
|
48762
50460
|
} catch (e) {
|
|
48763
|
-
return
|
|
50461
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to log progress" }, 500);
|
|
48764
50462
|
}
|
|
48765
50463
|
}
|
|
48766
50464
|
return null;
|
|
48767
50465
|
}
|
|
48768
|
-
function handleGetTask(id, _ctx,
|
|
50466
|
+
function handleGetTask(id, _ctx, json3, taskToSummary2, url) {
|
|
48769
50467
|
const task2 = getTask(id);
|
|
48770
50468
|
if (!task2)
|
|
48771
|
-
return
|
|
48772
|
-
return
|
|
50469
|
+
return json3({ error: "Task not found" }, 404);
|
|
50470
|
+
return json3(taskToSummary2(task2, url ? parseFieldsParam(url) : undefined));
|
|
48773
50471
|
}
|
|
48774
|
-
async function handlePatchTask(id, req, _ctx,
|
|
50472
|
+
async function handlePatchTask(id, req, _ctx, json3, taskToSummary2) {
|
|
48775
50473
|
try {
|
|
48776
50474
|
const body = await req.json();
|
|
48777
50475
|
const task2 = getTask(id);
|
|
48778
50476
|
if (!task2)
|
|
48779
|
-
return
|
|
50477
|
+
return json3({ error: "Task not found" }, 404);
|
|
48780
50478
|
const ALLOWED = new Set(["title", "description", "status", "priority", "assigned_to", "plan_id", "task_list_id", "tags", "metadata", "due_at", "estimated_minutes", "actual_minutes", "confidence", "retry_count", "max_retries", "retry_after", "task_type"]);
|
|
48781
50479
|
const safeBody = {};
|
|
48782
50480
|
for (const [key, value] of Object.entries(body)) {
|
|
@@ -48788,85 +50486,85 @@ async function handlePatchTask(id, req, _ctx, json2, taskToSummary2) {
|
|
|
48788
50486
|
...safeBody,
|
|
48789
50487
|
version: clientVersion
|
|
48790
50488
|
});
|
|
48791
|
-
return
|
|
50489
|
+
return json3(taskToSummary2(updated));
|
|
48792
50490
|
} catch (e) {
|
|
48793
|
-
const mapped = mapTaskError(e,
|
|
50491
|
+
const mapped = mapTaskError(e, json3);
|
|
48794
50492
|
if (mapped)
|
|
48795
50493
|
return mapped;
|
|
48796
|
-
return
|
|
50494
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to update task" }, 500);
|
|
48797
50495
|
}
|
|
48798
50496
|
}
|
|
48799
|
-
function handleDeleteTask(id, _ctx,
|
|
50497
|
+
function handleDeleteTask(id, _ctx, json3) {
|
|
48800
50498
|
const deleted = deleteTask(id);
|
|
48801
50499
|
if (!deleted)
|
|
48802
|
-
return
|
|
48803
|
-
return
|
|
50500
|
+
return json3({ error: "Task not found" }, 404);
|
|
50501
|
+
return json3({ success: true });
|
|
48804
50502
|
}
|
|
48805
|
-
function handleStartTask(id, ctx,
|
|
50503
|
+
function handleStartTask(id, ctx, json3, taskToSummary2) {
|
|
48806
50504
|
try {
|
|
48807
50505
|
const task2 = startTask(id, "dashboard");
|
|
48808
50506
|
ctx.broadcastEvent({ type: "task", task_id: task2.id, action: "started", agent_id: "dashboard", project_id: task2.project_id });
|
|
48809
|
-
return
|
|
50507
|
+
return json3(taskToSummary2(task2));
|
|
48810
50508
|
} catch (e) {
|
|
48811
|
-
const mapped = mapTaskError(e,
|
|
50509
|
+
const mapped = mapTaskError(e, json3);
|
|
48812
50510
|
if (mapped)
|
|
48813
50511
|
return mapped;
|
|
48814
|
-
return
|
|
50512
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to start task" }, 500);
|
|
48815
50513
|
}
|
|
48816
50514
|
}
|
|
48817
|
-
async function handleFailTask(id, req, ctx,
|
|
50515
|
+
async function handleFailTask(id, req, ctx, json3, taskToSummary2) {
|
|
48818
50516
|
try {
|
|
48819
50517
|
const body = await req.json().catch(() => ({}));
|
|
48820
50518
|
const result = failTask(id, body.agent_id, body.reason, { retry: body.retry, error_code: body.error_code });
|
|
48821
50519
|
ctx.broadcastEvent({ type: "task", task_id: id, action: "failed", agent_id: body.agent_id || null, project_id: result.task.project_id });
|
|
48822
|
-
return
|
|
50520
|
+
return json3({ task: taskToSummary2(result.task), retry_task: result.retryTask ? taskToSummary2(result.retryTask) : null });
|
|
48823
50521
|
} catch (e) {
|
|
48824
|
-
return
|
|
50522
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to fail task" }, 500);
|
|
48825
50523
|
}
|
|
48826
50524
|
}
|
|
48827
|
-
function handleCompleteTask(id, ctx,
|
|
50525
|
+
function handleCompleteTask(id, ctx, json3, taskToSummary2) {
|
|
48828
50526
|
try {
|
|
48829
50527
|
const task2 = completeTask(id, "dashboard");
|
|
48830
50528
|
ctx.broadcastEvent({ type: "task", task_id: task2.id, action: "completed", agent_id: "dashboard", project_id: task2.project_id });
|
|
48831
|
-
return
|
|
50529
|
+
return json3(taskToSummary2(task2));
|
|
48832
50530
|
} catch (e) {
|
|
48833
|
-
const mapped = mapTaskError(e,
|
|
50531
|
+
const mapped = mapTaskError(e, json3);
|
|
48834
50532
|
if (mapped)
|
|
48835
50533
|
return mapped;
|
|
48836
|
-
return
|
|
50534
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to complete task" }, 500);
|
|
48837
50535
|
}
|
|
48838
50536
|
}
|
|
48839
|
-
function handleListProjects(url, _ctx,
|
|
50537
|
+
function handleListProjects(url, _ctx, json3) {
|
|
48840
50538
|
const pFieldsParam = url.searchParams.get("fields");
|
|
48841
50539
|
const pFields = pFieldsParam ? pFieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
48842
50540
|
const projects = listProjects();
|
|
48843
|
-
return
|
|
50541
|
+
return json3(pFields ? projects.map((p) => Object.fromEntries(pFields.map((f) => [f, p[f] ?? null]))) : projects);
|
|
48844
50542
|
}
|
|
48845
|
-
async function handleCreateProject(req, _ctx,
|
|
50543
|
+
async function handleCreateProject(req, _ctx, json3) {
|
|
48846
50544
|
try {
|
|
48847
50545
|
const body = await req.json();
|
|
48848
50546
|
if (!body.name || !body.path)
|
|
48849
|
-
return
|
|
50547
|
+
return json3({ error: "Missing name or path" }, 400);
|
|
48850
50548
|
const project = createProject({ name: body.name, path: body.path, description: body.description });
|
|
48851
|
-
return
|
|
50549
|
+
return json3(project, 201);
|
|
48852
50550
|
} catch (e) {
|
|
48853
|
-
return
|
|
50551
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to create project" }, 500);
|
|
48854
50552
|
}
|
|
48855
50553
|
}
|
|
48856
|
-
function handleDeleteProject(id, _ctx,
|
|
50554
|
+
function handleDeleteProject(id, _ctx, json3) {
|
|
48857
50555
|
const deleted = deleteProject(id);
|
|
48858
50556
|
if (!deleted)
|
|
48859
|
-
return
|
|
48860
|
-
return
|
|
50557
|
+
return json3({ error: "Project not found" }, 404);
|
|
50558
|
+
return json3({ success: true });
|
|
48861
50559
|
}
|
|
48862
|
-
async function handleAgentMe(_req, url, _ctx,
|
|
50560
|
+
async function handleAgentMe(_req, url, _ctx, json3, taskToSummary2) {
|
|
48863
50561
|
try {
|
|
48864
50562
|
const name = url.searchParams.get("name");
|
|
48865
50563
|
if (!name)
|
|
48866
|
-
return
|
|
50564
|
+
return json3({ error: "Missing name param" }, 400);
|
|
48867
50565
|
const agentResult = registerAgent({ name });
|
|
48868
50566
|
if (isAgentConflict(agentResult))
|
|
48869
|
-
return
|
|
50567
|
+
return json3({ error: agentResult.message, conflict: true }, 409);
|
|
48870
50568
|
const agent = agentResult;
|
|
48871
50569
|
const tasks = listTasks({ assigned_to: agent.name });
|
|
48872
50570
|
const agentIdTasks = listTasks({ agent_id: agent.id });
|
|
@@ -48874,7 +50572,7 @@ async function handleAgentMe(_req, url, _ctx, json2, taskToSummary2) {
|
|
|
48874
50572
|
const pending = allTasks.filter((t) => t.status === "pending");
|
|
48875
50573
|
const inProgress = allTasks.filter((t) => t.status === "in_progress");
|
|
48876
50574
|
const completed = allTasks.filter((t) => t.status === "completed");
|
|
48877
|
-
return
|
|
50575
|
+
return json3({
|
|
48878
50576
|
agent,
|
|
48879
50577
|
pending_tasks: pending.map((t) => taskToSummary2(t)),
|
|
48880
50578
|
in_progress_tasks: inProgress.map((t) => taskToSummary2(t)),
|
|
@@ -48888,132 +50586,132 @@ async function handleAgentMe(_req, url, _ctx, json2, taskToSummary2) {
|
|
|
48888
50586
|
});
|
|
48889
50587
|
} catch (e) {
|
|
48890
50588
|
if (e instanceof InvalidAgentNameError)
|
|
48891
|
-
return
|
|
48892
|
-
return
|
|
50589
|
+
return json3({ error: e.message, suggestions: e.suggestions }, 400);
|
|
50590
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to get agent profile" }, 500);
|
|
48893
50591
|
}
|
|
48894
50592
|
}
|
|
48895
|
-
function handleAgentQueue(agentId, _ctx,
|
|
50593
|
+
function handleAgentQueue(agentId, _ctx, json3, taskToSummary2) {
|
|
48896
50594
|
const aliasSet = assignedToAliasSet(getDatabase(), agentId);
|
|
48897
50595
|
const pending = listTasks({ status: "pending" });
|
|
48898
50596
|
const queue = pending.filter((t) => aliasSet.has((t.assigned_to ?? "").toLowerCase()) || t.agent_id === agentId || !t.assigned_to && !t.locked_by);
|
|
48899
50597
|
const order = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
48900
50598
|
queue.sort((a, b) => (order[a.priority] ?? 4) - (order[b.priority] ?? 4) || new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
|
|
48901
|
-
return
|
|
50599
|
+
return json3(queue.map((t) => taskToSummary2(t)));
|
|
48902
50600
|
}
|
|
48903
|
-
async function handleClaimTask(req, _ctx,
|
|
50601
|
+
async function handleClaimTask(req, _ctx, json3, taskToSummary2) {
|
|
48904
50602
|
try {
|
|
48905
50603
|
const body = await req.json();
|
|
48906
50604
|
const agentId = body.agent_id || "anonymous";
|
|
48907
50605
|
const task2 = claimNextTask(agentId, body.project_id ? { project_id: body.project_id } : undefined);
|
|
48908
|
-
return
|
|
50606
|
+
return json3({ task: task2 ? taskToSummary2(task2) : null });
|
|
48909
50607
|
} catch (e) {
|
|
48910
|
-
return
|
|
50608
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to claim" }, 500);
|
|
48911
50609
|
}
|
|
48912
50610
|
}
|
|
48913
|
-
function handleListOrgs(_ctx,
|
|
48914
|
-
return
|
|
50611
|
+
function handleListOrgs(_ctx, json3) {
|
|
50612
|
+
return json3(listOrgs());
|
|
48915
50613
|
}
|
|
48916
|
-
async function handleCreateOrg(req, _ctx,
|
|
50614
|
+
async function handleCreateOrg(req, _ctx, json3) {
|
|
48917
50615
|
try {
|
|
48918
50616
|
const body = await req.json();
|
|
48919
50617
|
if (!body.name)
|
|
48920
|
-
return
|
|
48921
|
-
return
|
|
50618
|
+
return json3({ error: "Missing name" }, 400);
|
|
50619
|
+
return json3(createOrg(body), 201);
|
|
48922
50620
|
} catch (e) {
|
|
48923
|
-
return
|
|
50621
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
48924
50622
|
}
|
|
48925
50623
|
}
|
|
48926
|
-
async function handleUpdateOrg(id, req, _ctx,
|
|
50624
|
+
async function handleUpdateOrg(id, req, _ctx, json3) {
|
|
48927
50625
|
try {
|
|
48928
50626
|
const body = await req.json();
|
|
48929
|
-
return
|
|
50627
|
+
return json3(updateOrg(id, body));
|
|
48930
50628
|
} catch (e) {
|
|
48931
|
-
return
|
|
50629
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
48932
50630
|
}
|
|
48933
50631
|
}
|
|
48934
|
-
function handleDeleteOrg(id, _ctx,
|
|
50632
|
+
function handleDeleteOrg(id, _ctx, json3) {
|
|
48935
50633
|
const deleted = deleteOrg(id);
|
|
48936
|
-
return
|
|
50634
|
+
return json3(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
|
|
48937
50635
|
}
|
|
48938
|
-
function handleOrgChart(_ctx,
|
|
48939
|
-
return
|
|
50636
|
+
function handleOrgChart(_ctx, json3) {
|
|
50637
|
+
return json3(getOrgChart());
|
|
48940
50638
|
}
|
|
48941
|
-
function handleAgentTeam(agentId, _ctx,
|
|
48942
|
-
return
|
|
50639
|
+
function handleAgentTeam(agentId, _ctx, json3) {
|
|
50640
|
+
return json3(getDirectReports(decodeURIComponent(agentId)));
|
|
48943
50641
|
}
|
|
48944
|
-
function handleListAgents(url, _ctx,
|
|
50642
|
+
function handleListAgents(url, _ctx, json3) {
|
|
48945
50643
|
const aFieldsParam = url.searchParams.get("fields");
|
|
48946
50644
|
const aFields = aFieldsParam ? aFieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
48947
50645
|
const agents = listAgents();
|
|
48948
|
-
return
|
|
50646
|
+
return json3(aFields ? agents.map((a) => Object.fromEntries(aFields.map((f) => [f, a[f] ?? null]))) : agents);
|
|
48949
50647
|
}
|
|
48950
|
-
async function handleRegisterAgent(req, _ctx,
|
|
50648
|
+
async function handleRegisterAgent(req, _ctx, json3) {
|
|
48951
50649
|
try {
|
|
48952
50650
|
const body = await req.json();
|
|
48953
50651
|
if (!body.name)
|
|
48954
|
-
return
|
|
50652
|
+
return json3({ error: "Missing name" }, 400);
|
|
48955
50653
|
const result = registerAgent({ name: body.name, description: body.description, session_id: body.session_id, working_dir: body.working_dir });
|
|
48956
50654
|
if (isAgentConflict(result))
|
|
48957
|
-
return
|
|
48958
|
-
return
|
|
50655
|
+
return json3({ error: result.message, conflict: true }, 409);
|
|
50656
|
+
return json3(result, 201);
|
|
48959
50657
|
} catch (e) {
|
|
48960
50658
|
if (e instanceof InvalidAgentNameError)
|
|
48961
|
-
return
|
|
48962
|
-
return
|
|
50659
|
+
return json3({ error: e.message, suggestions: e.suggestions }, 400);
|
|
50660
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to register agent" }, 500);
|
|
48963
50661
|
}
|
|
48964
50662
|
}
|
|
48965
|
-
async function handleUpdateAgent(id, req, _ctx,
|
|
50663
|
+
async function handleUpdateAgent(id, req, _ctx, json3) {
|
|
48966
50664
|
try {
|
|
48967
50665
|
const body = await req.json();
|
|
48968
50666
|
const agent = updateAgent(id, body);
|
|
48969
|
-
return
|
|
50667
|
+
return json3(agent);
|
|
48970
50668
|
} catch (e) {
|
|
48971
50669
|
if (e instanceof InvalidAgentNameError)
|
|
48972
|
-
return
|
|
48973
|
-
return
|
|
50670
|
+
return json3({ error: e.message, suggestions: e.suggestions }, 400);
|
|
50671
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to update agent" }, 500);
|
|
48974
50672
|
}
|
|
48975
50673
|
}
|
|
48976
|
-
function handleDeleteAgent(id, _ctx,
|
|
50674
|
+
function handleDeleteAgent(id, _ctx, json3) {
|
|
48977
50675
|
const deleted = deleteAgent(id);
|
|
48978
50676
|
if (!deleted)
|
|
48979
|
-
return
|
|
48980
|
-
return
|
|
50677
|
+
return json3({ error: "Agent not found" }, 404);
|
|
50678
|
+
return json3({ success: true });
|
|
48981
50679
|
}
|
|
48982
|
-
async function handleBulkDeleteAgents(req, _ctx,
|
|
50680
|
+
async function handleBulkDeleteAgents(req, _ctx, json3) {
|
|
48983
50681
|
try {
|
|
48984
50682
|
const body = await req.json();
|
|
48985
50683
|
if (!body.ids?.length || body.action !== "delete")
|
|
48986
|
-
return
|
|
50684
|
+
return json3({ error: "Missing ids or invalid action" }, 400);
|
|
48987
50685
|
let succeeded = 0;
|
|
48988
50686
|
for (const id of body.ids) {
|
|
48989
50687
|
if (deleteAgent(id))
|
|
48990
50688
|
succeeded++;
|
|
48991
50689
|
}
|
|
48992
|
-
return
|
|
50690
|
+
return json3({ succeeded, failed: body.ids.length - succeeded });
|
|
48993
50691
|
} catch (e) {
|
|
48994
|
-
return
|
|
50692
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
48995
50693
|
}
|
|
48996
50694
|
}
|
|
48997
|
-
async function handleBulkDeleteProjects(req, _ctx,
|
|
50695
|
+
async function handleBulkDeleteProjects(req, _ctx, json3) {
|
|
48998
50696
|
try {
|
|
48999
50697
|
const body = await req.json();
|
|
49000
50698
|
if (!body.ids?.length || body.action !== "delete")
|
|
49001
|
-
return
|
|
50699
|
+
return json3({ error: "Missing ids or invalid action" }, 400);
|
|
49002
50700
|
let succeeded = 0;
|
|
49003
50701
|
for (const id of body.ids) {
|
|
49004
50702
|
if (deleteProject(id))
|
|
49005
50703
|
succeeded++;
|
|
49006
50704
|
}
|
|
49007
|
-
return
|
|
50705
|
+
return json3({ succeeded, failed: body.ids.length - succeeded });
|
|
49008
50706
|
} catch (e) {
|
|
49009
|
-
return
|
|
50707
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
49010
50708
|
}
|
|
49011
50709
|
}
|
|
49012
|
-
function handleDoctor(_ctx,
|
|
50710
|
+
function handleDoctor(_ctx, json3) {
|
|
49013
50711
|
const { runTodosDoctor: runTodosDoctor2 } = (init_doctor(), __toCommonJS(exports_doctor));
|
|
49014
|
-
return
|
|
50712
|
+
return json3(runTodosDoctor2({ apply: false }));
|
|
49015
50713
|
}
|
|
49016
|
-
function handleReport(_req, url, _ctx,
|
|
50714
|
+
function handleReport(_req, url, _ctx, json3) {
|
|
49017
50715
|
const days = parseInt(url.searchParams.get("days") || "7", 10);
|
|
49018
50716
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
49019
50717
|
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
@@ -49029,62 +50727,62 @@ function handleReport(_req, url, _ctx, json2) {
|
|
|
49029
50727
|
byDay[day] = (byDay[day] || 0) + 1;
|
|
49030
50728
|
}
|
|
49031
50729
|
const completionRate = changed.length > 0 ? Math.round(completed.length / changed.length * 100) : 0;
|
|
49032
|
-
return
|
|
50730
|
+
return json3({ days, period_since: since, total: all.length, stats: stats2, changed: changed.length, completed: completed.length, failed: failed.length, completion_rate: completionRate, by_day: byDay });
|
|
49033
50731
|
}
|
|
49034
|
-
function handleActivity(_req, url, _ctx,
|
|
50732
|
+
function handleActivity(_req, url, _ctx, json3) {
|
|
49035
50733
|
const limit = parseInt(url.searchParams.get("limit") || "50", 10);
|
|
49036
|
-
return
|
|
50734
|
+
return json3(getRecentActivity(limit));
|
|
49037
50735
|
}
|
|
49038
|
-
function handleTaskHistory(id, _ctx,
|
|
50736
|
+
function handleTaskHistory(id, _ctx, json3, url) {
|
|
49039
50737
|
const history = getTaskHistory(id);
|
|
49040
50738
|
const format = url?.searchParams.get("format") || "compact";
|
|
49041
50739
|
const limit = parseBoundedLimit(url?.searchParams.get("limit") || null, 20, 500);
|
|
49042
|
-
return
|
|
50740
|
+
return json3(format === "full" ? history : history.slice(0, limit));
|
|
49043
50741
|
}
|
|
49044
|
-
function handleListWebhooks(_ctx,
|
|
49045
|
-
return
|
|
50742
|
+
function handleListWebhooks(_ctx, json3) {
|
|
50743
|
+
return json3(listWebhooks());
|
|
49046
50744
|
}
|
|
49047
|
-
async function handleCreateWebhook(req, _ctx,
|
|
50745
|
+
async function handleCreateWebhook(req, _ctx, json3) {
|
|
49048
50746
|
try {
|
|
49049
50747
|
const body = await req.json();
|
|
49050
50748
|
if (!body.url)
|
|
49051
|
-
return
|
|
49052
|
-
return
|
|
50749
|
+
return json3({ error: "Missing url" }, 400);
|
|
50750
|
+
return json3(createWebhook(body), 201);
|
|
49053
50751
|
} catch (e) {
|
|
49054
|
-
return
|
|
50752
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
49055
50753
|
}
|
|
49056
50754
|
}
|
|
49057
|
-
function handleDeleteWebhook(id, _ctx,
|
|
50755
|
+
function handleDeleteWebhook(id, _ctx, json3) {
|
|
49058
50756
|
const deleted = deleteWebhook(id);
|
|
49059
|
-
return
|
|
50757
|
+
return json3(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
|
|
49060
50758
|
}
|
|
49061
|
-
function handleListTemplates(_ctx,
|
|
49062
|
-
return
|
|
50759
|
+
function handleListTemplates(_ctx, json3) {
|
|
50760
|
+
return json3(listTemplates());
|
|
49063
50761
|
}
|
|
49064
|
-
async function handleCreateTemplate(req, _ctx,
|
|
50762
|
+
async function handleCreateTemplate(req, _ctx, json3) {
|
|
49065
50763
|
try {
|
|
49066
50764
|
const body = await req.json();
|
|
49067
50765
|
if (!body.name || !body.title_pattern)
|
|
49068
|
-
return
|
|
49069
|
-
return
|
|
50766
|
+
return json3({ error: "Missing name or title_pattern" }, 400);
|
|
50767
|
+
return json3(createTemplate(body), 201);
|
|
49070
50768
|
} catch (e) {
|
|
49071
|
-
return
|
|
50769
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
49072
50770
|
}
|
|
49073
50771
|
}
|
|
49074
|
-
function handleDeleteTemplate(id, _ctx,
|
|
50772
|
+
function handleDeleteTemplate(id, _ctx, json3) {
|
|
49075
50773
|
const deleted = deleteTemplate(id);
|
|
49076
|
-
return
|
|
50774
|
+
return json3(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
|
|
49077
50775
|
}
|
|
49078
|
-
function handleListPlans(url, _ctx,
|
|
50776
|
+
function handleListPlans(url, _ctx, json3) {
|
|
49079
50777
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
49080
50778
|
const plans = listPlans(projectId);
|
|
49081
|
-
return
|
|
50779
|
+
return json3(plans);
|
|
49082
50780
|
}
|
|
49083
|
-
async function handleCreatePlan(req, _ctx,
|
|
50781
|
+
async function handleCreatePlan(req, _ctx, json3) {
|
|
49084
50782
|
try {
|
|
49085
50783
|
const body = await req.json();
|
|
49086
50784
|
if (!body.name)
|
|
49087
|
-
return
|
|
50785
|
+
return json3({ error: "Missing 'name'" }, 400);
|
|
49088
50786
|
const plan = createPlan({
|
|
49089
50787
|
name: body.name,
|
|
49090
50788
|
slug: body.slug,
|
|
@@ -49094,49 +50792,49 @@ async function handleCreatePlan(req, _ctx, json2) {
|
|
|
49094
50792
|
agent_id: body.agent_id,
|
|
49095
50793
|
status: body.status
|
|
49096
50794
|
});
|
|
49097
|
-
return
|
|
50795
|
+
return json3(plan, 201);
|
|
49098
50796
|
} catch (e) {
|
|
49099
|
-
return
|
|
50797
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to create plan" }, 500);
|
|
49100
50798
|
}
|
|
49101
50799
|
}
|
|
49102
|
-
async function handleBulkDeletePlans(req, _ctx,
|
|
50800
|
+
async function handleBulkDeletePlans(req, _ctx, json3) {
|
|
49103
50801
|
try {
|
|
49104
50802
|
const body = await req.json();
|
|
49105
50803
|
if (!body.ids?.length || body.action !== "delete")
|
|
49106
|
-
return
|
|
50804
|
+
return json3({ error: "Missing ids or invalid action" }, 400);
|
|
49107
50805
|
let succeeded = 0;
|
|
49108
50806
|
for (const id of body.ids) {
|
|
49109
50807
|
if (deletePlan(id))
|
|
49110
50808
|
succeeded++;
|
|
49111
50809
|
}
|
|
49112
|
-
return
|
|
50810
|
+
return json3({ succeeded, failed: body.ids.length - succeeded });
|
|
49113
50811
|
} catch (e) {
|
|
49114
|
-
return
|
|
50812
|
+
return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
|
|
49115
50813
|
}
|
|
49116
50814
|
}
|
|
49117
|
-
function handleGetPlan(id, _ctx,
|
|
50815
|
+
function handleGetPlan(id, _ctx, json3, taskToSummary2) {
|
|
49118
50816
|
const plan = getPlan(id);
|
|
49119
50817
|
if (!plan)
|
|
49120
|
-
return
|
|
50818
|
+
return json3({ error: "Plan not found" }, 404);
|
|
49121
50819
|
const tasks = listTasks({ plan_id: id });
|
|
49122
|
-
return
|
|
50820
|
+
return json3({ ...plan, tasks: tasks.map((t) => taskToSummary2(t)) });
|
|
49123
50821
|
}
|
|
49124
|
-
async function handleUpdatePlan(id, req, _ctx,
|
|
50822
|
+
async function handleUpdatePlan(id, req, _ctx, json3) {
|
|
49125
50823
|
try {
|
|
49126
50824
|
const body = await req.json();
|
|
49127
50825
|
const plan = updatePlan(id, body);
|
|
49128
|
-
return
|
|
50826
|
+
return json3(plan);
|
|
49129
50827
|
} catch (e) {
|
|
49130
|
-
return
|
|
50828
|
+
return json3({ error: e instanceof Error ? e.message : "Failed to update plan" }, 500);
|
|
49131
50829
|
}
|
|
49132
50830
|
}
|
|
49133
|
-
function handleDeletePlan(id, _ctx,
|
|
50831
|
+
function handleDeletePlan(id, _ctx, json3) {
|
|
49134
50832
|
const deleted = deletePlan(id);
|
|
49135
50833
|
if (!deleted)
|
|
49136
|
-
return
|
|
49137
|
-
return
|
|
50834
|
+
return json3({ error: "Plan not found" }, 404);
|
|
50835
|
+
return json3({ success: true });
|
|
49138
50836
|
}
|
|
49139
|
-
function handleStaticFiles(path, method, ctx,
|
|
50837
|
+
function handleStaticFiles(path, method, ctx, json3, serveStaticFile2) {
|
|
49140
50838
|
if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
|
|
49141
50839
|
return null;
|
|
49142
50840
|
if (path !== "/") {
|
|
@@ -49144,7 +50842,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
49144
50842
|
const resolvedFile = resolve16(filePath);
|
|
49145
50843
|
const resolvedBase = resolve16(ctx.dashboardDir);
|
|
49146
50844
|
if (!resolvedFile.startsWith(resolvedBase + sep3) && resolvedFile !== resolvedBase) {
|
|
49147
|
-
return
|
|
50845
|
+
return json3({ error: "Forbidden" }, 403);
|
|
49148
50846
|
}
|
|
49149
50847
|
const res2 = serveStaticFile2(filePath);
|
|
49150
50848
|
if (res2)
|
|
@@ -50805,10 +52503,10 @@ var exports_pr_groups = {};
|
|
|
50805
52503
|
__export(exports_pr_groups, {
|
|
50806
52504
|
handlePrGroupHttpRequest: () => handlePrGroupHttpRequest
|
|
50807
52505
|
});
|
|
50808
|
-
function
|
|
50809
|
-
return new Response(JSON.stringify(body), { status, headers:
|
|
52506
|
+
function json3(body, status = 200) {
|
|
52507
|
+
return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS2 });
|
|
50810
52508
|
}
|
|
50811
|
-
function
|
|
52509
|
+
function errorStatus2(error) {
|
|
50812
52510
|
switch (error.code) {
|
|
50813
52511
|
case "PR_GROUP_INVALID_INPUT":
|
|
50814
52512
|
case "PR_GROUP_EXACT_HEAD_REQUIRED":
|
|
@@ -50822,7 +52520,7 @@ function errorStatus(error) {
|
|
|
50822
52520
|
return 409;
|
|
50823
52521
|
}
|
|
50824
52522
|
}
|
|
50825
|
-
async function
|
|
52523
|
+
async function readJson2(req) {
|
|
50826
52524
|
try {
|
|
50827
52525
|
const value = await req.json();
|
|
50828
52526
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -50840,26 +52538,26 @@ async function handlePrGroupHttpRequest(req, url, ledger, basePath, principal) {
|
|
|
50840
52538
|
const method = req.method.toUpperCase();
|
|
50841
52539
|
try {
|
|
50842
52540
|
if (!groupId && method === "POST" && action === undefined) {
|
|
50843
|
-
return
|
|
52541
|
+
return json3({ error: "unknown PR-group route", code: "PR_GROUP_NOT_FOUND" }, 404);
|
|
50844
52542
|
}
|
|
50845
52543
|
if (groupId === "admit" && !action) {
|
|
50846
52544
|
if (method !== "POST")
|
|
50847
|
-
return
|
|
50848
|
-
const body = await
|
|
52545
|
+
return json3({ error: "method not allowed" }, 405);
|
|
52546
|
+
const body = await readJson2(req);
|
|
50849
52547
|
if (!body)
|
|
50850
|
-
return
|
|
50851
|
-
return
|
|
52548
|
+
return json3({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
|
|
52549
|
+
return json3(await ledger.admit(body), 201);
|
|
50852
52550
|
}
|
|
50853
52551
|
if (!groupId)
|
|
50854
|
-
return
|
|
52552
|
+
return json3({ error: "PR group id is required", code: "PR_GROUP_INVALID_INPUT" }, 400);
|
|
50855
52553
|
if (!action && method === "GET") {
|
|
50856
|
-
return
|
|
52554
|
+
return json3({ view: await ledger.get(groupId) });
|
|
50857
52555
|
}
|
|
50858
52556
|
if (action === "events") {
|
|
50859
52557
|
if (method === "GET") {
|
|
50860
52558
|
const limit = url.searchParams.has("limit") ? Number(url.searchParams.get("limit")) : undefined;
|
|
50861
52559
|
const afterSequence = url.searchParams.has("after_sequence") ? Number(url.searchParams.get("after_sequence")) : undefined;
|
|
50862
|
-
return
|
|
52560
|
+
return json3({
|
|
50863
52561
|
history: await ledger.events(groupId, {
|
|
50864
52562
|
...limit !== undefined ? { limit } : {},
|
|
50865
52563
|
...afterSequence !== undefined ? { after_sequence: afterSequence } : {}
|
|
@@ -50867,49 +52565,49 @@ async function handlePrGroupHttpRequest(req, url, ledger, basePath, principal) {
|
|
|
50867
52565
|
});
|
|
50868
52566
|
}
|
|
50869
52567
|
if (method === "POST") {
|
|
50870
|
-
const body = await
|
|
52568
|
+
const body = await readJson2(req);
|
|
50871
52569
|
if (!body)
|
|
50872
|
-
return
|
|
50873
|
-
return
|
|
52570
|
+
return json3({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
|
|
52571
|
+
return json3(await ledger.append({
|
|
50874
52572
|
...body,
|
|
50875
52573
|
group_id: groupId,
|
|
50876
52574
|
authenticated_actor_id: principal?.actor_id ?? undefined,
|
|
50877
52575
|
authenticated_actor_run_id: principal?.actor_run_id ?? undefined
|
|
50878
52576
|
}), 201);
|
|
50879
52577
|
}
|
|
50880
|
-
return
|
|
52578
|
+
return json3({ error: "method not allowed" }, 405);
|
|
50881
52579
|
}
|
|
50882
52580
|
if (action === "recover") {
|
|
50883
52581
|
if (method !== "POST")
|
|
50884
|
-
return
|
|
50885
|
-
const body = await
|
|
52582
|
+
return json3({ error: "method not allowed" }, 405);
|
|
52583
|
+
const body = await readJson2(req);
|
|
50886
52584
|
if (!body)
|
|
50887
|
-
return
|
|
50888
|
-
return
|
|
52585
|
+
return json3({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
|
|
52586
|
+
return json3(await ledger.recover({
|
|
50889
52587
|
...body,
|
|
50890
52588
|
group_id: groupId
|
|
50891
52589
|
}), 201);
|
|
50892
52590
|
}
|
|
50893
|
-
return
|
|
52591
|
+
return json3({ error: "unknown PR-group route", code: "PR_GROUP_NOT_FOUND" }, 404);
|
|
50894
52592
|
} catch (cause) {
|
|
50895
52593
|
if (cause instanceof PrGroupLedgerError) {
|
|
50896
|
-
return
|
|
52594
|
+
return json3({
|
|
50897
52595
|
error: cause.message,
|
|
50898
52596
|
code: cause.code,
|
|
50899
52597
|
details: cause.details,
|
|
50900
52598
|
authoritative: true
|
|
50901
|
-
},
|
|
52599
|
+
}, errorStatus2(cause));
|
|
50902
52600
|
}
|
|
50903
|
-
return
|
|
52601
|
+
return json3({
|
|
50904
52602
|
error: cause instanceof Error ? cause.message : "internal PR-group error",
|
|
50905
52603
|
code: "PR_GROUP_ATOMICITY_UNAVAILABLE"
|
|
50906
52604
|
}, 500);
|
|
50907
52605
|
}
|
|
50908
52606
|
}
|
|
50909
|
-
var
|
|
52607
|
+
var JSON_HEADERS2;
|
|
50910
52608
|
var init_pr_groups = __esm(() => {
|
|
50911
52609
|
init_types3();
|
|
50912
|
-
|
|
52610
|
+
JSON_HEADERS2 = { "Content-Type": "application/json" };
|
|
50913
52611
|
});
|
|
50914
52612
|
|
|
50915
52613
|
// src/lib/comment-cursor.ts
|
|
@@ -50942,11 +52640,11 @@ __export(exports_v1, {
|
|
|
50942
52640
|
handleV1Request: () => handleV1Request,
|
|
50943
52641
|
countSnapshotRecords: () => countSnapshotRecords
|
|
50944
52642
|
});
|
|
50945
|
-
function
|
|
50946
|
-
return new Response(JSON.stringify(body), { status, headers:
|
|
52643
|
+
function json4(body, status = 200) {
|
|
52644
|
+
return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS3 });
|
|
50947
52645
|
}
|
|
50948
52646
|
function error(status, message, extra) {
|
|
50949
|
-
return
|
|
52647
|
+
return json4({ error: message, ...extra ?? {} }, status);
|
|
50950
52648
|
}
|
|
50951
52649
|
function enumQueryParam(url, name, vocabulary) {
|
|
50952
52650
|
const raw = url.searchParams.get(name);
|
|
@@ -51197,7 +52895,7 @@ function validateTemplatePatch(value) {
|
|
|
51197
52895
|
...body.plan_id === null ? { plan_id: null } : {}
|
|
51198
52896
|
} };
|
|
51199
52897
|
}
|
|
51200
|
-
async function
|
|
52898
|
+
async function readJson3(req) {
|
|
51201
52899
|
try {
|
|
51202
52900
|
const text2 = await req.text();
|
|
51203
52901
|
if (!text2)
|
|
@@ -51267,6 +52965,9 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51267
52965
|
if (path === "/v1/pr-groups" || path.startsWith("/v1/pr-groups/")) {
|
|
51268
52966
|
return handlePrGroupHttpRequest(req, url, (dependencies.getPrGroupLedger ?? getCloudPrGroupLedger)(), "/v1/pr-groups", { actor_id: principal.agent, actor_run_id: principal.kid });
|
|
51269
52967
|
}
|
|
52968
|
+
if (path === "/v1/project-registration" || path.startsWith("/v1/project-registration/")) {
|
|
52969
|
+
return handleTodosProjectRegistrationHttpRequest(req, url, (dependencies.getProjectRegistrationAuthority ?? getCloudProjectRegistrationAuthority)());
|
|
52970
|
+
}
|
|
51270
52971
|
const store = (dependencies.getStorageAdapter ?? getCloudStorageAdapter)();
|
|
51271
52972
|
const segments = path.split("/").filter(Boolean);
|
|
51272
52973
|
const resource = segments[1];
|
|
@@ -51278,7 +52979,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51278
52979
|
if (id === "exists" && !action) {
|
|
51279
52980
|
if (method !== "POST")
|
|
51280
52981
|
return error(405, `method ${method} not allowed on /v1/tasks/exists`);
|
|
51281
|
-
const body = await
|
|
52982
|
+
const body = await readJson3(req);
|
|
51282
52983
|
const ids2 = Array.isArray(body?.ids) ? Array.from(new Set(body.ids.filter((v) => typeof v === "string" && v.length > 0))) : [];
|
|
51283
52984
|
if (ids2.length === 0)
|
|
51284
52985
|
return error(400, "provide a non-empty string array `ids`");
|
|
@@ -51288,7 +52989,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51288
52989
|
const presentSet = new Set(found.map((t) => t.id));
|
|
51289
52990
|
const present = ids2.filter((i) => presentSet.has(i));
|
|
51290
52991
|
const missing = ids2.filter((i) => !presentSet.has(i));
|
|
51291
|
-
return
|
|
52992
|
+
return json4({
|
|
51292
52993
|
requested: ids2.length,
|
|
51293
52994
|
present_count: present.length,
|
|
51294
52995
|
missing_count: missing.length,
|
|
@@ -51301,7 +53002,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51301
53002
|
if (typeof store.tasks.getByFingerprint !== "function") {
|
|
51302
53003
|
return error(501, "fingerprint upsert is not supported by this storage backend");
|
|
51303
53004
|
}
|
|
51304
|
-
const body = await
|
|
53005
|
+
const body = await readJson3(req) ?? {};
|
|
51305
53006
|
const fingerprint3 = typeof body.fingerprint === "string" ? body.fingerprint.trim() : "";
|
|
51306
53007
|
if (!fingerprint3)
|
|
51307
53008
|
return error(400, "fingerprint is required");
|
|
@@ -51338,11 +53039,11 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51338
53039
|
}
|
|
51339
53040
|
if (!existing) {
|
|
51340
53041
|
const task2 = await store.tasks.create({ ...fields, title: body.title }, contextFromPrincipal(principal, body));
|
|
51341
|
-
return
|
|
53042
|
+
return json4({ task: task2, created: true }, 201);
|
|
51342
53043
|
}
|
|
51343
53044
|
try {
|
|
51344
53045
|
const task2 = await store.tasks.update(existing.id, { ...fields, version: existing.version }, contextFromPrincipal(principal, body));
|
|
51345
|
-
return
|
|
53046
|
+
return json4({ task: task2, created: false });
|
|
51346
53047
|
} catch (e) {
|
|
51347
53048
|
const msg = e.message || "";
|
|
51348
53049
|
if (msg.includes("version conflict"))
|
|
@@ -51384,15 +53085,15 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51384
53085
|
const tasks = await store.tasks.list(filter);
|
|
51385
53086
|
const { limit: _l, offset: _o, ...countFilter } = filter;
|
|
51386
53087
|
const total = await store.tasks.count(countFilter);
|
|
51387
|
-
return
|
|
53088
|
+
return json4({ tasks, count: tasks.length, total });
|
|
51388
53089
|
}
|
|
51389
53090
|
if (method === "POST") {
|
|
51390
|
-
const body = await
|
|
53091
|
+
const body = await readJson3(req);
|
|
51391
53092
|
if (!body || typeof body.title !== "string" || !body.title.trim()) {
|
|
51392
53093
|
return error(400, "title is required");
|
|
51393
53094
|
}
|
|
51394
53095
|
const task2 = await store.tasks.create(body, contextFromPrincipal(principal, body));
|
|
51395
|
-
return
|
|
53096
|
+
return json4({ task: task2 }, 201);
|
|
51396
53097
|
}
|
|
51397
53098
|
return error(405, `method ${method} not allowed on /v1/tasks`);
|
|
51398
53099
|
}
|
|
@@ -51409,7 +53110,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51409
53110
|
if (legacyPage.length > LEGACY_COMMENT_RESPONSE_LIMIT) {
|
|
51410
53111
|
return error(426, "task has too many comments for this client; upgrade @hasna/todos to use cursor pagination");
|
|
51411
53112
|
}
|
|
51412
|
-
return
|
|
53113
|
+
return json4({
|
|
51413
53114
|
comments: legacyPage,
|
|
51414
53115
|
count: legacyPage.length,
|
|
51415
53116
|
has_more: false,
|
|
@@ -51434,7 +53135,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51434
53135
|
const page = (await store.audit.getCommentsPage(id, { limit: limit + 1, ...before ? { before } : {} }, contextFromPrincipal(principal))).map(redactComment3);
|
|
51435
53136
|
const hasMore = page.length > limit;
|
|
51436
53137
|
const comments = hasMore ? page.slice(1) : page;
|
|
51437
|
-
return
|
|
53138
|
+
return json4({
|
|
51438
53139
|
comments,
|
|
51439
53140
|
count: comments.length,
|
|
51440
53141
|
has_more: hasMore,
|
|
@@ -51442,7 +53143,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51442
53143
|
});
|
|
51443
53144
|
}
|
|
51444
53145
|
if (method === "POST") {
|
|
51445
|
-
const body2 = await
|
|
53146
|
+
const body2 = await readJson3(req) ?? {};
|
|
51446
53147
|
if (typeof body2.content !== "string" || !body2.content.trim()) {
|
|
51447
53148
|
return error(400, "content is required");
|
|
51448
53149
|
}
|
|
@@ -51457,7 +53158,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51457
53158
|
type: body2.type,
|
|
51458
53159
|
progress_pct: body2.progress_pct
|
|
51459
53160
|
}, contextFromPrincipal(principal, body2));
|
|
51460
|
-
return
|
|
53161
|
+
return json4({ comment: redactComment3(comment) }, 201);
|
|
51461
53162
|
}
|
|
51462
53163
|
return error(405, `method ${method} not allowed on /v1/tasks/:id/comments`);
|
|
51463
53164
|
}
|
|
@@ -51467,19 +53168,19 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51467
53168
|
if (!await store.tasks.get(id))
|
|
51468
53169
|
return error(404, "task not found");
|
|
51469
53170
|
const history = await store.audit.getTaskHistory(id);
|
|
51470
|
-
return
|
|
53171
|
+
return json4({ history, count: history.length });
|
|
51471
53172
|
}
|
|
51472
53173
|
if (action === "lock" || action === "unlock") {
|
|
51473
53174
|
if (method !== "POST")
|
|
51474
53175
|
return error(405, `method ${method} not allowed on /v1/tasks/:id/${action}`);
|
|
51475
|
-
const body2 = await
|
|
53176
|
+
const body2 = await readJson3(req) ?? {};
|
|
51476
53177
|
if (!await store.tasks.get(id))
|
|
51477
53178
|
return error(404, "task not found");
|
|
51478
53179
|
if (action === "lock") {
|
|
51479
53180
|
if (typeof store.tasks.lock !== "function")
|
|
51480
53181
|
return error(501, "task locking is not supported by this storage backend");
|
|
51481
53182
|
const agentId3 = body2.agent_id || principal.agent || "todos-serve";
|
|
51482
|
-
return
|
|
53183
|
+
return json4({ result: await store.tasks.lock(id, agentId3) });
|
|
51483
53184
|
}
|
|
51484
53185
|
if (typeof store.tasks.unlock !== "function")
|
|
51485
53186
|
return error(501, "task unlocking is not supported by this storage backend");
|
|
@@ -51487,7 +53188,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51487
53188
|
if (!principal.scopes.includes("todos:*"))
|
|
51488
53189
|
return error(403, "force unlock requires todos:* scope");
|
|
51489
53190
|
const released2 = await store.tasks.unlock(id);
|
|
51490
|
-
return
|
|
53191
|
+
return json4({ success: released2 });
|
|
51491
53192
|
}
|
|
51492
53193
|
if (body2.agent_id && body2.agent_id !== principal.agent && !principal.scopes.includes("todos:*")) {
|
|
51493
53194
|
return error(403, "unlock agent_id must match the authenticated agent");
|
|
@@ -51496,7 +53197,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51496
53197
|
if (!agentId2)
|
|
51497
53198
|
return error(403, "unlock requires an agent-bound key or force=true");
|
|
51498
53199
|
const released = await store.tasks.unlock(id, agentId2);
|
|
51499
|
-
return
|
|
53200
|
+
return json4({ success: released });
|
|
51500
53201
|
}
|
|
51501
53202
|
if (action === "dependencies") {
|
|
51502
53203
|
if (!store.dependencies)
|
|
@@ -51505,16 +53206,16 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51505
53206
|
if (!await store.tasks.get(id))
|
|
51506
53207
|
return error(404, "task not found");
|
|
51507
53208
|
const edges = await store.dependencies.list(id);
|
|
51508
|
-
return
|
|
53209
|
+
return json4(edges);
|
|
51509
53210
|
}
|
|
51510
53211
|
if (method === "POST") {
|
|
51511
|
-
const body2 = await
|
|
53212
|
+
const body2 = await readJson3(req) ?? {};
|
|
51512
53213
|
if (typeof body2.depends_on !== "string" || !body2.depends_on.trim()) {
|
|
51513
53214
|
return error(400, "depends_on is required");
|
|
51514
53215
|
}
|
|
51515
53216
|
try {
|
|
51516
53217
|
const dependency = await store.dependencies.add(id, body2.depends_on, contextFromPrincipal(principal));
|
|
51517
|
-
return
|
|
53218
|
+
return json4({ dependency }, 201);
|
|
51518
53219
|
} catch (e) {
|
|
51519
53220
|
const msg = e.message || "";
|
|
51520
53221
|
if (msg.includes("not found"))
|
|
@@ -51528,7 +53229,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51528
53229
|
if (!subId)
|
|
51529
53230
|
return error(400, "dependency target id is required (/v1/tasks/:id/dependencies/:dep)");
|
|
51530
53231
|
const removed = await store.dependencies.remove(id, subId);
|
|
51531
|
-
return
|
|
53232
|
+
return json4({ removed });
|
|
51532
53233
|
}
|
|
51533
53234
|
return error(405, `method ${method} not allowed on /v1/tasks/:id/dependencies`);
|
|
51534
53235
|
}
|
|
@@ -51539,10 +53240,10 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51539
53240
|
if (!await store.tasks.get(id))
|
|
51540
53241
|
return error(404, "task not found");
|
|
51541
53242
|
const verifications = await store.verifications.list(id);
|
|
51542
|
-
return
|
|
53243
|
+
return json4({ verifications, count: verifications.length });
|
|
51543
53244
|
}
|
|
51544
53245
|
if (method === "POST") {
|
|
51545
|
-
const body2 = await
|
|
53246
|
+
const body2 = await readJson3(req) ?? {};
|
|
51546
53247
|
if (typeof body2.command !== "string" || !body2.command.trim()) {
|
|
51547
53248
|
return error(400, "command is required");
|
|
51548
53249
|
}
|
|
@@ -51555,7 +53256,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51555
53256
|
artifact_path: body2.artifact_path,
|
|
51556
53257
|
agent_id: body2.agent_id
|
|
51557
53258
|
}, contextFromPrincipal(principal, body2));
|
|
51558
|
-
return
|
|
53259
|
+
return json4({ verification }, 201);
|
|
51559
53260
|
} catch (e) {
|
|
51560
53261
|
const msg = e.message || "";
|
|
51561
53262
|
if (msg.includes("not found"))
|
|
@@ -51572,10 +53273,10 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51572
53273
|
if (!await store.tasks.get(id))
|
|
51573
53274
|
return error(404, "task not found");
|
|
51574
53275
|
const commits = await store.commits.list(id);
|
|
51575
|
-
return
|
|
53276
|
+
return json4({ commits, count: commits.length });
|
|
51576
53277
|
}
|
|
51577
53278
|
if (method === "POST") {
|
|
51578
|
-
const body2 = await
|
|
53279
|
+
const body2 = await readJson3(req) ?? {};
|
|
51579
53280
|
if (typeof body2.sha !== "string" || !body2.sha.trim())
|
|
51580
53281
|
return error(400, "sha is required");
|
|
51581
53282
|
try {
|
|
@@ -51586,7 +53287,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51586
53287
|
author: body2.author,
|
|
51587
53288
|
files_changed: Array.isArray(body2.files_changed) ? body2.files_changed : undefined
|
|
51588
53289
|
}, contextFromPrincipal(principal));
|
|
51589
|
-
return
|
|
53290
|
+
return json4({ commit }, 201);
|
|
51590
53291
|
} catch (e) {
|
|
51591
53292
|
const msg = e.message || "";
|
|
51592
53293
|
if (msg.includes("not found"))
|
|
@@ -51603,10 +53304,10 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51603
53304
|
if (!await store.tasks.get(id))
|
|
51604
53305
|
return error(404, "task not found");
|
|
51605
53306
|
const refs = await store.gitRefs.list(id);
|
|
51606
|
-
return
|
|
53307
|
+
return json4({ refs, count: refs.length });
|
|
51607
53308
|
}
|
|
51608
53309
|
if (method === "POST") {
|
|
51609
|
-
const body2 = await
|
|
53310
|
+
const body2 = await readJson3(req) ?? {};
|
|
51610
53311
|
const refType = body2.ref_type === "pull_request" || body2.ref_type === "branch" ? body2.ref_type : "branch";
|
|
51611
53312
|
if (typeof body2.name !== "string" || !body2.name.trim())
|
|
51612
53313
|
return error(400, "name is required");
|
|
@@ -51619,7 +53320,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51619
53320
|
provider: body2.provider,
|
|
51620
53321
|
metadata: body2.metadata
|
|
51621
53322
|
}, contextFromPrincipal(principal));
|
|
51622
|
-
return
|
|
53323
|
+
return json4({ ref }, 201);
|
|
51623
53324
|
} catch (e) {
|
|
51624
53325
|
const msg = e.message || "";
|
|
51625
53326
|
if (msg.includes("not found"))
|
|
@@ -51635,21 +53336,21 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51635
53336
|
const body = actionJson.value && typeof actionJson.value === "object" && !Array.isArray(actionJson.value) ? actionJson.value : {};
|
|
51636
53337
|
const agentId = typeof body.agent_id === "string" ? body.agent_id : principal.agent || "todos-serve";
|
|
51637
53338
|
if (action === "start" && method === "POST") {
|
|
51638
|
-
return
|
|
53339
|
+
return json4({ task: await store.tasks.start(id, agentId) });
|
|
51639
53340
|
}
|
|
51640
53341
|
if (action === "complete" && method === "POST") {
|
|
51641
53342
|
const parsed = validateTaskCompletion(actionJson.value);
|
|
51642
53343
|
if (!parsed.ok)
|
|
51643
53344
|
return error(400, parsed.message);
|
|
51644
|
-
return
|
|
53345
|
+
return json4({
|
|
51645
53346
|
task: await store.tasks.complete(id, parsed.agentId || principal.agent || "todos-serve", parsed.options, contextFromPrincipal(principal, body))
|
|
51646
53347
|
});
|
|
51647
53348
|
}
|
|
51648
53349
|
if (action === "fail" && method === "POST") {
|
|
51649
|
-
return
|
|
53350
|
+
return json4({ result: await store.tasks.fail(id, agentId, typeof body.reason === "string" ? body.reason : "failed", {}) });
|
|
51650
53351
|
}
|
|
51651
53352
|
if (action === "claim" && method === "POST") {
|
|
51652
|
-
return
|
|
53353
|
+
return json4({ task: await store.tasks.claimNext(agentId, {}) });
|
|
51653
53354
|
}
|
|
51654
53355
|
return error(404, `unknown task action: ${action}`);
|
|
51655
53356
|
}
|
|
@@ -51672,10 +53373,10 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51672
53373
|
throw e;
|
|
51673
53374
|
}
|
|
51674
53375
|
}
|
|
51675
|
-
return task2 ?
|
|
53376
|
+
return task2 ? json4({ task: task2 }) : error(404, "task not found");
|
|
51676
53377
|
}
|
|
51677
53378
|
if (method === "PATCH" || method === "PUT") {
|
|
51678
|
-
const body = await
|
|
53379
|
+
const body = await readJson3(req);
|
|
51679
53380
|
if (!body)
|
|
51680
53381
|
return error(400, "invalid JSON body");
|
|
51681
53382
|
const current = await store.tasks.get(id);
|
|
@@ -51687,7 +53388,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51687
53388
|
};
|
|
51688
53389
|
try {
|
|
51689
53390
|
const task2 = await store.tasks.update(id, patch);
|
|
51690
|
-
return task2 ?
|
|
53391
|
+
return task2 ? json4({ task: task2 }) : error(404, "task not found");
|
|
51691
53392
|
} catch (e) {
|
|
51692
53393
|
const msg = e.message || "";
|
|
51693
53394
|
if (msg.includes("version conflict"))
|
|
@@ -51697,7 +53398,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51697
53398
|
}
|
|
51698
53399
|
if (method === "DELETE") {
|
|
51699
53400
|
await store.tasks.delete(id, contextFromPrincipal(principal));
|
|
51700
|
-
return
|
|
53401
|
+
return json4({ deleted: true, id });
|
|
51701
53402
|
}
|
|
51702
53403
|
return error(405, `method ${method} not allowed on /v1/tasks/:id`);
|
|
51703
53404
|
}
|
|
@@ -51705,24 +53406,24 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51705
53406
|
if (!id) {
|
|
51706
53407
|
if (method === "GET") {
|
|
51707
53408
|
const projects = await store.projects.list();
|
|
51708
|
-
return
|
|
53409
|
+
return json4({ projects, count: projects.length });
|
|
51709
53410
|
}
|
|
51710
53411
|
if (method === "POST") {
|
|
51711
|
-
const body = await
|
|
53412
|
+
const body = await readJson3(req);
|
|
51712
53413
|
if (!body)
|
|
51713
53414
|
return error(400, "invalid JSON body");
|
|
51714
53415
|
const validated = validateProjectCreate(body);
|
|
51715
53416
|
if (!validated.ok)
|
|
51716
53417
|
return error(400, validated.message);
|
|
51717
53418
|
const project = await store.projects.create(validated.input, contextFromPrincipal(principal));
|
|
51718
|
-
return
|
|
53419
|
+
return json4({ project }, 201);
|
|
51719
53420
|
}
|
|
51720
53421
|
return error(405, `method ${method} not allowed on /v1/projects`);
|
|
51721
53422
|
}
|
|
51722
53423
|
if (action === "rename") {
|
|
51723
53424
|
if (method !== "POST")
|
|
51724
53425
|
return error(405, `method ${method} not allowed on /v1/projects/:id/rename`);
|
|
51725
|
-
const body = await
|
|
53426
|
+
const body = await readJson3(req);
|
|
51726
53427
|
if (!body || typeof body.new_slug !== "string" || !body.new_slug.trim() || !normalizeSlug(body.new_slug)) {
|
|
51727
53428
|
return error(400, "new_slug must be a non-empty string");
|
|
51728
53429
|
}
|
|
@@ -51732,14 +53433,14 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51732
53433
|
const unknownField = Object.keys(body).find((key) => !["new_slug", "name"].includes(key));
|
|
51733
53434
|
if (unknownField)
|
|
51734
53435
|
return error(400, `unknown project rename field: ${unknownField}`);
|
|
51735
|
-
return
|
|
53436
|
+
return json4(await store.projects.rename(id, body, contextFromPrincipal(principal)));
|
|
51736
53437
|
}
|
|
51737
53438
|
if (method === "GET") {
|
|
51738
53439
|
const project = await store.projects.get(id);
|
|
51739
|
-
return project ?
|
|
53440
|
+
return project ? json4({ project }) : error(404, "project not found");
|
|
51740
53441
|
}
|
|
51741
53442
|
if (method === "PATCH" || method === "PUT") {
|
|
51742
|
-
const body = await
|
|
53443
|
+
const body = await readJson3(req);
|
|
51743
53444
|
if (!body)
|
|
51744
53445
|
return error(400, "invalid JSON body");
|
|
51745
53446
|
const validated = validateProjectPatch(body);
|
|
@@ -51748,21 +53449,21 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51748
53449
|
if (!await store.projects.get(id))
|
|
51749
53450
|
return error(404, "project not found");
|
|
51750
53451
|
const project = await store.projects.update(id, validated.patch);
|
|
51751
|
-
return
|
|
53452
|
+
return json4({ project });
|
|
51752
53453
|
}
|
|
51753
53454
|
if (method === "DELETE") {
|
|
51754
53455
|
await store.projects.delete(id, contextFromPrincipal(principal));
|
|
51755
|
-
return
|
|
53456
|
+
return json4({ deleted: true, id });
|
|
51756
53457
|
}
|
|
51757
53458
|
return error(405, `method ${method} not allowed on /v1/projects/:id`);
|
|
51758
53459
|
}
|
|
51759
53460
|
if (resource === "plans") {
|
|
51760
53461
|
if (!id && method === "GET") {
|
|
51761
53462
|
const plans = await store.plans.list(url.searchParams.get("project_id") ?? undefined);
|
|
51762
|
-
return
|
|
53463
|
+
return json4({ plans, count: plans.length });
|
|
51763
53464
|
}
|
|
51764
53465
|
if (!id && method === "POST") {
|
|
51765
|
-
const body = await
|
|
53466
|
+
const body = await readJson3(req);
|
|
51766
53467
|
const validated = validatePlanCreate(body);
|
|
51767
53468
|
if (!validated.ok)
|
|
51768
53469
|
return error(400, validated.message);
|
|
@@ -51777,14 +53478,14 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51777
53478
|
}
|
|
51778
53479
|
}
|
|
51779
53480
|
const plan = await store.plans.create(validated.input, contextFromPrincipal(principal, validated.input));
|
|
51780
|
-
return
|
|
53481
|
+
return json4({ plan }, 201);
|
|
51781
53482
|
}
|
|
51782
53483
|
if (id && method === "GET") {
|
|
51783
53484
|
const plan = await store.plans.get(id);
|
|
51784
|
-
return plan ?
|
|
53485
|
+
return plan ? json4({ plan }) : error(404, "plan not found");
|
|
51785
53486
|
}
|
|
51786
53487
|
if (id && (method === "PATCH" || method === "PUT")) {
|
|
51787
|
-
const body = await
|
|
53488
|
+
const body = await readJson3(req);
|
|
51788
53489
|
if (!body || Object.keys(body).length === 0)
|
|
51789
53490
|
return error(400, "plan patch is required");
|
|
51790
53491
|
const allowed = new Set(["name", "slug", "description", "status", "task_list_id", "agent_id"]);
|
|
@@ -51821,12 +53522,12 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51821
53522
|
}
|
|
51822
53523
|
}
|
|
51823
53524
|
const plan = await store.plans.update(id, body);
|
|
51824
|
-
return
|
|
53525
|
+
return json4({ plan });
|
|
51825
53526
|
}
|
|
51826
53527
|
if (id && method === "DELETE") {
|
|
51827
53528
|
if (!await store.plans.delete(id, contextFromPrincipal(principal)))
|
|
51828
53529
|
return error(404, "plan not found");
|
|
51829
|
-
return
|
|
53530
|
+
return json4({ deleted: true, id });
|
|
51830
53531
|
}
|
|
51831
53532
|
if (id)
|
|
51832
53533
|
return error(405, `method ${method} not allowed on /v1/plans/:id`);
|
|
@@ -51835,50 +53536,50 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51835
53536
|
if (!id && method === "GET") {
|
|
51836
53537
|
const projectId = url.searchParams.get("project_id");
|
|
51837
53538
|
const templates = (await store.templates.list()).filter((template) => projectId === null || template.project_id === projectId);
|
|
51838
|
-
return
|
|
53539
|
+
return json4({ templates, count: templates.length });
|
|
51839
53540
|
}
|
|
51840
53541
|
if (!id && method === "POST") {
|
|
51841
|
-
const body = await
|
|
53542
|
+
const body = await readJson3(req);
|
|
51842
53543
|
const validated = validateTemplateCreate(body);
|
|
51843
53544
|
if (!validated.ok)
|
|
51844
53545
|
return error(400, validated.message);
|
|
51845
53546
|
const template = await store.templates.create(validated.input, contextFromPrincipal(principal));
|
|
51846
|
-
return
|
|
53547
|
+
return json4({ template: await store.templates.getWithTasks(template.id) }, 201);
|
|
51847
53548
|
}
|
|
51848
53549
|
if (!id)
|
|
51849
53550
|
return error(405, `method ${method} not allowed on /v1/templates`);
|
|
51850
53551
|
if (method === "GET") {
|
|
51851
53552
|
const template = await store.templates.getWithTasks(id);
|
|
51852
|
-
return template ?
|
|
53553
|
+
return template ? json4({ template }) : error(404, "template not found");
|
|
51853
53554
|
}
|
|
51854
53555
|
if (method === "PATCH" || method === "PUT") {
|
|
51855
|
-
const body = await
|
|
53556
|
+
const body = await readJson3(req);
|
|
51856
53557
|
const validated = validateTemplatePatch(body);
|
|
51857
53558
|
if (!validated.ok)
|
|
51858
53559
|
return error(400, validated.message);
|
|
51859
53560
|
const template = await store.templates.update(id, validated.patch, contextFromPrincipal(principal));
|
|
51860
|
-
return template ?
|
|
53561
|
+
return template ? json4({ template: await store.templates.getWithTasks(id) }) : error(404, "template not found");
|
|
51861
53562
|
}
|
|
51862
53563
|
if (method === "DELETE") {
|
|
51863
53564
|
const deleted = await store.templates.delete(id, contextFromPrincipal(principal));
|
|
51864
|
-
return deleted ?
|
|
53565
|
+
return deleted ? json4({ deleted: true, id }) : error(404, "template not found");
|
|
51865
53566
|
}
|
|
51866
53567
|
return error(405, `method ${method} not allowed on /v1/templates/:id`);
|
|
51867
53568
|
}
|
|
51868
53569
|
if (resource === "agents") {
|
|
51869
53570
|
if (!id && method === "GET") {
|
|
51870
53571
|
const agents = await store.agents.list();
|
|
51871
|
-
return
|
|
53572
|
+
return json4({ agents, count: agents.length });
|
|
51872
53573
|
}
|
|
51873
53574
|
if (!id && method === "POST") {
|
|
51874
|
-
const body = await
|
|
53575
|
+
const body = await readJson3(req);
|
|
51875
53576
|
if (!body || typeof body.name !== "string" || !body.name.trim())
|
|
51876
53577
|
return error(400, "name is required");
|
|
51877
53578
|
const result = await store.agents.register(body, contextFromPrincipal(principal));
|
|
51878
53579
|
if (result && typeof result === "object" && "conflict" in result) {
|
|
51879
53580
|
return error(409, result.message ?? "agent name conflict", { conflict: true });
|
|
51880
53581
|
}
|
|
51881
|
-
return
|
|
53582
|
+
return json4({ agent: result }, 201);
|
|
51882
53583
|
}
|
|
51883
53584
|
if (id && action === "heartbeat") {
|
|
51884
53585
|
if (method !== "POST")
|
|
@@ -51887,7 +53588,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51887
53588
|
return error(501, "agent heartbeat is not supported by this storage backend");
|
|
51888
53589
|
}
|
|
51889
53590
|
const agent = await store.agents.heartbeat(id, contextFromPrincipal(principal));
|
|
51890
|
-
return agent ?
|
|
53591
|
+
return agent ? json4({ agent }) : error(404, "agent not found");
|
|
51891
53592
|
}
|
|
51892
53593
|
if (id && action === "release") {
|
|
51893
53594
|
if (method !== "POST")
|
|
@@ -51895,18 +53596,18 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51895
53596
|
if (typeof store.agents.release !== "function") {
|
|
51896
53597
|
return error(501, "agent release is not supported by this storage backend");
|
|
51897
53598
|
}
|
|
51898
|
-
const body = await
|
|
53599
|
+
const body = await readJson3(req) ?? {};
|
|
51899
53600
|
const result = await store.agents.release(id, body.session_id, contextFromPrincipal(principal));
|
|
51900
53601
|
if (!result)
|
|
51901
53602
|
return error(404, "agent not found");
|
|
51902
53603
|
if (!result.released) {
|
|
51903
53604
|
return error(409, "release denied: session_id does not match agent's current session", { released: false });
|
|
51904
53605
|
}
|
|
51905
|
-
return
|
|
53606
|
+
return json4({ agent: result.agent, released: true });
|
|
51906
53607
|
}
|
|
51907
53608
|
if (id && method === "GET") {
|
|
51908
53609
|
const agent = await store.agents.get(id);
|
|
51909
|
-
return agent ?
|
|
53610
|
+
return agent ? json4({ agent }) : error(404, "agent not found");
|
|
51910
53611
|
}
|
|
51911
53612
|
}
|
|
51912
53613
|
if (resource === "activity" && !id) {
|
|
@@ -51915,16 +53616,16 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51915
53616
|
const limitParam = url.searchParams.get("limit");
|
|
51916
53617
|
const limit = limitParam ? Math.max(1, Math.min(1e4, Number(limitParam) || 50)) : 50;
|
|
51917
53618
|
const activity = await store.audit.getRecentActivity(limit);
|
|
51918
|
-
return
|
|
53619
|
+
return json4({ activity, count: activity.length });
|
|
51919
53620
|
}
|
|
51920
53621
|
if (resource === "task-lists") {
|
|
51921
53622
|
if (!id && method === "GET") {
|
|
51922
53623
|
const projectId = url.searchParams.get("project_id") ?? undefined;
|
|
51923
53624
|
const taskLists = await store.taskLists.list(projectId);
|
|
51924
|
-
return
|
|
53625
|
+
return json4({ task_lists: taskLists, count: taskLists.length });
|
|
51925
53626
|
}
|
|
51926
53627
|
if (!id && method === "POST") {
|
|
51927
|
-
const body = await
|
|
53628
|
+
const body = await readJson3(req);
|
|
51928
53629
|
if (!body || typeof body.name !== "string" || !body.name.trim())
|
|
51929
53630
|
return error(400, "name is required");
|
|
51930
53631
|
const unknownField = Object.keys(body).find((key) => !["name", "slug", "project_id", "description", "metadata"].includes(key));
|
|
@@ -51943,14 +53644,14 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51943
53644
|
return error(400, "task-list slug must be non-empty kebab-case");
|
|
51944
53645
|
}
|
|
51945
53646
|
const taskList = await store.taskLists.create(body, contextFromPrincipal(principal));
|
|
51946
|
-
return
|
|
53647
|
+
return json4({ task_list: taskList }, 201);
|
|
51947
53648
|
}
|
|
51948
53649
|
if (id && method === "GET") {
|
|
51949
53650
|
const taskList = await store.taskLists.get(id);
|
|
51950
|
-
return taskList ?
|
|
53651
|
+
return taskList ? json4({ task_list: taskList }) : error(404, "task list not found");
|
|
51951
53652
|
}
|
|
51952
53653
|
if (id && (method === "PATCH" || method === "PUT")) {
|
|
51953
|
-
const body = await
|
|
53654
|
+
const body = await readJson3(req);
|
|
51954
53655
|
if (!body)
|
|
51955
53656
|
return error(400, "invalid JSON body");
|
|
51956
53657
|
const unknownField = Object.keys(body).find((key) => !["slug", "name", "description", "metadata"].includes(key));
|
|
@@ -51970,11 +53671,11 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51970
53671
|
if (!await store.taskLists.get(id))
|
|
51971
53672
|
return error(404, "task list not found");
|
|
51972
53673
|
const taskList = await store.taskLists.update(id, body);
|
|
51973
|
-
return
|
|
53674
|
+
return json4({ task_list: taskList });
|
|
51974
53675
|
}
|
|
51975
53676
|
if (id && method === "DELETE") {
|
|
51976
53677
|
const deleted = await store.taskLists.delete(id, contextFromPrincipal(principal));
|
|
51977
|
-
return deleted ?
|
|
53678
|
+
return deleted ? json4({ deleted: true, id }) : error(404, "task list not found");
|
|
51978
53679
|
}
|
|
51979
53680
|
return error(405, `method ${method} not allowed on /v1/task-lists${id ? "/:id" : ""}`);
|
|
51980
53681
|
}
|
|
@@ -51985,7 +53686,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51985
53686
|
return error(501, "dependency edge listing is not supported by this storage backend");
|
|
51986
53687
|
}
|
|
51987
53688
|
const dependencies2 = await store.dependencies.listAll();
|
|
51988
|
-
return
|
|
53689
|
+
return json4({ dependencies: dependencies2, count: dependencies2.length });
|
|
51989
53690
|
}
|
|
51990
53691
|
if (resource === "commits" && id) {
|
|
51991
53692
|
if (method !== "GET")
|
|
@@ -51993,7 +53694,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51993
53694
|
if (!store.commits)
|
|
51994
53695
|
return error(501, "commit links are not supported by this storage backend");
|
|
51995
53696
|
const commit = await store.commits.find(id);
|
|
51996
|
-
return
|
|
53697
|
+
return json4({ commit: commit ?? null });
|
|
51997
53698
|
}
|
|
51998
53699
|
if (resource === "refs" && id) {
|
|
51999
53700
|
if (method !== "GET")
|
|
@@ -52007,7 +53708,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
52007
53708
|
return error(400, "ref path segment has invalid percent encoding");
|
|
52008
53709
|
}
|
|
52009
53710
|
const refs = await store.gitRefs.find(decodedRef);
|
|
52010
|
-
return
|
|
53711
|
+
return json4({ refs, count: refs.length });
|
|
52011
53712
|
}
|
|
52012
53713
|
if (resource === "next" && !id) {
|
|
52013
53714
|
if (method !== "GET")
|
|
@@ -52019,7 +53720,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
52019
53720
|
...url.searchParams.get("plan_id") ? { plan_id: url.searchParams.get("plan_id") } : {}
|
|
52020
53721
|
};
|
|
52021
53722
|
const task2 = await store.tasks.getNext(agent, filters);
|
|
52022
|
-
return
|
|
53723
|
+
return json4({ task: task2 ?? null });
|
|
52023
53724
|
}
|
|
52024
53725
|
if (resource === "stats" && method === "GET") {
|
|
52025
53726
|
const [tasks, tasksAll, projects] = await Promise.all([
|
|
@@ -52027,7 +53728,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
52027
53728
|
store.tasks.count({ include_subtasks: true }),
|
|
52028
53729
|
store.projects.list()
|
|
52029
53730
|
]);
|
|
52030
|
-
return
|
|
53731
|
+
return json4({ tasks, tasks_all: tasksAll, subtasks: tasksAll - tasks, projects: projects.length });
|
|
52031
53732
|
}
|
|
52032
53733
|
if (resource === "integrity" && !id) {
|
|
52033
53734
|
if (method !== "GET")
|
|
@@ -52036,7 +53737,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
52036
53737
|
return error(501, "referential-integrity reporting is not supported by this storage backend");
|
|
52037
53738
|
}
|
|
52038
53739
|
const integrity = await store.integrity.report();
|
|
52039
|
-
return
|
|
53740
|
+
return json4({ integrity });
|
|
52040
53741
|
}
|
|
52041
53742
|
if (resource === "import") {
|
|
52042
53743
|
if (method !== "POST")
|
|
@@ -52044,7 +53745,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
52044
53745
|
if (typeof store.sync.importSnapshot !== "function") {
|
|
52045
53746
|
return error(501, "snapshot import is not supported by this storage backend");
|
|
52046
53747
|
}
|
|
52047
|
-
const raw = await
|
|
53748
|
+
const raw = await readJson3(req);
|
|
52048
53749
|
if (raw === null)
|
|
52049
53750
|
return error(400, "invalid JSON body");
|
|
52050
53751
|
const snapshot = normalizeImportSnapshot(raw);
|
|
@@ -52053,7 +53754,7 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
52053
53754
|
return error(400, "empty snapshot: provide at least one record array (tasks/projects/plans/...)");
|
|
52054
53755
|
}
|
|
52055
53756
|
const result = await store.sync.importSnapshot(snapshot, contextFromPrincipal(principal));
|
|
52056
|
-
return
|
|
53757
|
+
return json4({ result, received });
|
|
52057
53758
|
}
|
|
52058
53759
|
return error(404, `unknown /v1 resource: ${resource ?? "(root)"}`);
|
|
52059
53760
|
} catch (e) {
|
|
@@ -52076,13 +53777,14 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
52076
53777
|
return error(500, e.message || "internal error");
|
|
52077
53778
|
}
|
|
52078
53779
|
}
|
|
52079
|
-
var
|
|
53780
|
+
var JSON_HEADERS3, DEFAULT_COMMENT_PAGE_SIZE = 100, MAX_COMMENT_PAGE_SIZE = 500, LEGACY_COMMENT_RESPONSE_LIMIT = 500;
|
|
52080
53781
|
var init_v1 = __esm(() => {
|
|
52081
53782
|
init_types();
|
|
52082
53783
|
init_cloud();
|
|
52083
53784
|
init_pr_groups();
|
|
53785
|
+
init_project_registration();
|
|
52084
53786
|
init_redaction();
|
|
52085
|
-
|
|
53787
|
+
JSON_HEADERS3 = { "Content-Type": "application/json" };
|
|
52086
53788
|
});
|
|
52087
53789
|
|
|
52088
53790
|
// src/pr-groups/sqlite.ts
|
|
@@ -52241,12 +53943,12 @@ class SqlitePrGroupLedgerPersistence {
|
|
|
52241
53943
|
this.tx = new SqlitePrGroupTransaction(db);
|
|
52242
53944
|
}
|
|
52243
53945
|
async transaction(fn) {
|
|
52244
|
-
const previous =
|
|
53946
|
+
const previous = sqliteTransactionTails2.get(this.db) ?? Promise.resolve();
|
|
52245
53947
|
let release;
|
|
52246
53948
|
const current = new Promise((resolve17) => {
|
|
52247
53949
|
release = resolve17;
|
|
52248
53950
|
});
|
|
52249
|
-
|
|
53951
|
+
sqliteTransactionTails2.set(this.db, current);
|
|
52250
53952
|
await previous;
|
|
52251
53953
|
try {
|
|
52252
53954
|
this.db.exec("BEGIN IMMEDIATE");
|
|
@@ -52260,8 +53962,8 @@ class SqlitePrGroupLedgerPersistence {
|
|
|
52260
53962
|
throw error2;
|
|
52261
53963
|
} finally {
|
|
52262
53964
|
release();
|
|
52263
|
-
if (
|
|
52264
|
-
|
|
53965
|
+
if (sqliteTransactionTails2.get(this.db) === current)
|
|
53966
|
+
sqliteTransactionTails2.delete(this.db);
|
|
52265
53967
|
}
|
|
52266
53968
|
}
|
|
52267
53969
|
getGroup(id) {
|
|
@@ -52291,9 +53993,9 @@ class SqlitePrGroupLedgerPersistence {
|
|
|
52291
53993
|
return row ? eventFromRow2(row) : null;
|
|
52292
53994
|
}
|
|
52293
53995
|
}
|
|
52294
|
-
var
|
|
52295
|
-
var
|
|
52296
|
-
|
|
53996
|
+
var sqliteTransactionTails2;
|
|
53997
|
+
var init_sqlite2 = __esm(() => {
|
|
53998
|
+
sqliteTransactionTails2 = new WeakMap;
|
|
52297
53999
|
});
|
|
52298
54000
|
|
|
52299
54001
|
// src/pr-groups/index.ts
|
|
@@ -52323,10 +54025,10 @@ function createLocalPrGroupLedger(db = getDatabase()) {
|
|
|
52323
54025
|
var init_pr_groups2 = __esm(() => {
|
|
52324
54026
|
init_database();
|
|
52325
54027
|
init_ledger();
|
|
52326
|
-
|
|
54028
|
+
init_sqlite2();
|
|
52327
54029
|
init_types3();
|
|
52328
54030
|
init_ledger();
|
|
52329
|
-
|
|
54031
|
+
init_sqlite2();
|
|
52330
54032
|
init_http_client();
|
|
52331
54033
|
init_postgres();
|
|
52332
54034
|
});
|
|
@@ -52337,7 +54039,7 @@ __export(exports_serve, {
|
|
|
52337
54039
|
taskToSummary: () => taskToSummary,
|
|
52338
54040
|
startServer: () => startServer,
|
|
52339
54041
|
serveStaticFile: () => serveStaticFile,
|
|
52340
|
-
json: () =>
|
|
54042
|
+
json: () => json2,
|
|
52341
54043
|
checkAuth: () => checkAuth,
|
|
52342
54044
|
SECURITY_HEADERS: () => SECURITY_HEADERS,
|
|
52343
54045
|
MIME_TYPES: () => MIME_TYPES
|
|
@@ -52423,7 +54125,7 @@ function checkRateLimit(ip) {
|
|
|
52423
54125
|
}
|
|
52424
54126
|
return { allowed: true };
|
|
52425
54127
|
}
|
|
52426
|
-
function
|
|
54128
|
+
function json2(data, status = 200, headers) {
|
|
52427
54129
|
return new Response(JSON.stringify(data), {
|
|
52428
54130
|
status,
|
|
52429
54131
|
headers: {
|
|
@@ -52568,7 +54270,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
52568
54270
|
"Access-Control-Allow-Headers": "Content-Type, X-API-Key, Authorization",
|
|
52569
54271
|
Vary: "Origin"
|
|
52570
54272
|
} : undefined;
|
|
52571
|
-
const jsonWithCors = (data, status = 200) =>
|
|
54273
|
+
const jsonWithCors = (data, status = 200) => json2(data, status, corsHeaders);
|
|
52572
54274
|
if (method === "OPTIONS") {
|
|
52573
54275
|
return new Response(null, {
|
|
52574
54276
|
headers: corsHeaders || {
|
|
@@ -52649,13 +54351,13 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
52649
54351
|
return res;
|
|
52650
54352
|
}
|
|
52651
54353
|
if (path === "/api/health" && method === "GET") {
|
|
52652
|
-
return handleHealth(ctx,
|
|
54354
|
+
return handleHealth(ctx, json2);
|
|
52653
54355
|
}
|
|
52654
54356
|
if (path === "/api/headless" && method === "GET") {
|
|
52655
|
-
return handleHeadlessBoundary(ctx,
|
|
54357
|
+
return handleHeadlessBoundary(ctx, json2);
|
|
52656
54358
|
}
|
|
52657
54359
|
if (path === "/api/stats" && method === "GET") {
|
|
52658
|
-
return handleStats(ctx,
|
|
54360
|
+
return handleStats(ctx, json2);
|
|
52659
54361
|
}
|
|
52660
54362
|
if (path === "/api/tasks" && method === "GET") {
|
|
52661
54363
|
return handleListTasks(req, url, ctx, jsonWithCors, taskToSummary);
|
|
@@ -52670,16 +54372,16 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
52670
54372
|
return handleTasksExport(req, url, ctx, jsonWithCors, taskToSummary);
|
|
52671
54373
|
}
|
|
52672
54374
|
if (path === "/api/tasks/bulk" && method === "POST") {
|
|
52673
|
-
return handleTasksBulk(req, ctx,
|
|
54375
|
+
return handleTasksBulk(req, ctx, json2);
|
|
52674
54376
|
}
|
|
52675
54377
|
if (path === "/api/tasks/status" && method === "GET") {
|
|
52676
|
-
return handleTasksStatus(req, url, ctx,
|
|
54378
|
+
return handleTasksStatus(req, url, ctx, json2);
|
|
52677
54379
|
}
|
|
52678
54380
|
if (path === "/api/tasks/next" && method === "GET") {
|
|
52679
54381
|
return handleTasksNext(req, url, ctx, jsonWithCors, taskToSummary);
|
|
52680
54382
|
}
|
|
52681
54383
|
if (path === "/api/tasks/active" && method === "GET") {
|
|
52682
|
-
return handleTasksActive(req, url, ctx,
|
|
54384
|
+
return handleTasksActive(req, url, ctx, json2);
|
|
52683
54385
|
}
|
|
52684
54386
|
if (path === "/api/tasks/stale" && method === "GET") {
|
|
52685
54387
|
return handleTasksStale(req, url, ctx, jsonWithCors, taskToSummary);
|
|
@@ -52692,11 +54394,11 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
52692
54394
|
}
|
|
52693
54395
|
const attachmentsMatch = path.match(/^\/api\/tasks\/([^/]+)\/attachments$/);
|
|
52694
54396
|
if (attachmentsMatch && method === "GET") {
|
|
52695
|
-
return handleTaskAttachments(attachmentsMatch[1], ctx,
|
|
54397
|
+
return handleTaskAttachments(attachmentsMatch[1], ctx, json2);
|
|
52696
54398
|
}
|
|
52697
54399
|
const progressMatch = path.match(/^\/api\/tasks\/([^/]+)\/progress$/);
|
|
52698
54400
|
if (progressMatch) {
|
|
52699
|
-
const res = await handleTaskProgress(progressMatch[1], req, method, ctx,
|
|
54401
|
+
const res = await handleTaskProgress(progressMatch[1], req, method, ctx, json2, url);
|
|
52700
54402
|
if (res !== null)
|
|
52701
54403
|
return res;
|
|
52702
54404
|
}
|
|
@@ -52710,7 +54412,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
52710
54412
|
return handlePatchTask(id, req, ctx, jsonWithCors, taskToSummary);
|
|
52711
54413
|
}
|
|
52712
54414
|
if (method === "DELETE") {
|
|
52713
|
-
return handleDeleteTask(id, ctx,
|
|
54415
|
+
return handleDeleteTask(id, ctx, json2);
|
|
52714
54416
|
}
|
|
52715
54417
|
}
|
|
52716
54418
|
const startMatch = path.match(/^\/api\/tasks\/([^/]+)\/start$/);
|
|
@@ -52726,7 +54428,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
52726
54428
|
return handleCompleteTask(completeMatch[1], ctx, jsonWithCors, taskToSummary);
|
|
52727
54429
|
}
|
|
52728
54430
|
if (path === "/api/projects" && method === "GET") {
|
|
52729
|
-
return handleListProjects(url, ctx,
|
|
54431
|
+
return handleListProjects(url, ctx, json2);
|
|
52730
54432
|
}
|
|
52731
54433
|
if (path === "/api/agents/me" && method === "GET") {
|
|
52732
54434
|
return handleAgentMe(req, url, ctx, jsonWithCors, taskToSummary);
|
|
@@ -52739,92 +54441,92 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
52739
54441
|
return handleClaimTask(req, ctx, jsonWithCors, taskToSummary);
|
|
52740
54442
|
}
|
|
52741
54443
|
if (path === "/api/orgs" && method === "GET") {
|
|
52742
|
-
return handleListOrgs(ctx,
|
|
54444
|
+
return handleListOrgs(ctx, json2);
|
|
52743
54445
|
}
|
|
52744
54446
|
if (path === "/api/orgs" && method === "POST") {
|
|
52745
|
-
return handleCreateOrg(req, ctx,
|
|
54447
|
+
return handleCreateOrg(req, ctx, json2);
|
|
52746
54448
|
}
|
|
52747
54449
|
const orgMatch = path.match(/^\/api\/orgs\/([^/]+)$/);
|
|
52748
54450
|
if (orgMatch && method === "PATCH") {
|
|
52749
|
-
return handleUpdateOrg(orgMatch[1], req, ctx,
|
|
54451
|
+
return handleUpdateOrg(orgMatch[1], req, ctx, json2);
|
|
52750
54452
|
}
|
|
52751
54453
|
if (orgMatch && method === "DELETE") {
|
|
52752
|
-
return handleDeleteOrg(orgMatch[1], ctx,
|
|
54454
|
+
return handleDeleteOrg(orgMatch[1], ctx, json2);
|
|
52753
54455
|
}
|
|
52754
54456
|
if (path === "/api/org" && method === "GET") {
|
|
52755
|
-
return handleOrgChart(ctx,
|
|
54457
|
+
return handleOrgChart(ctx, json2);
|
|
52756
54458
|
}
|
|
52757
54459
|
const teamMatch = path.match(/^\/api\/agents\/([^/]+)\/team$/);
|
|
52758
54460
|
if (teamMatch && method === "GET") {
|
|
52759
|
-
return handleAgentTeam(teamMatch[1], ctx,
|
|
54461
|
+
return handleAgentTeam(teamMatch[1], ctx, json2);
|
|
52760
54462
|
}
|
|
52761
54463
|
if (path === "/api/agents" && method === "GET") {
|
|
52762
|
-
return handleListAgents(url, ctx,
|
|
54464
|
+
return handleListAgents(url, ctx, json2);
|
|
52763
54465
|
}
|
|
52764
54466
|
if (path === "/api/projects" && method === "POST") {
|
|
52765
|
-
return handleCreateProject(req, ctx,
|
|
54467
|
+
return handleCreateProject(req, ctx, json2);
|
|
52766
54468
|
}
|
|
52767
54469
|
const projectDeleteMatch = path.match(/^\/api\/projects\/([^/]+)$/);
|
|
52768
54470
|
if (projectDeleteMatch && method === "DELETE") {
|
|
52769
|
-
return handleDeleteProject(projectDeleteMatch[1], ctx,
|
|
54471
|
+
return handleDeleteProject(projectDeleteMatch[1], ctx, json2);
|
|
52770
54472
|
}
|
|
52771
54473
|
if (path === "/api/agents" && method === "POST") {
|
|
52772
|
-
return handleRegisterAgent(req, ctx,
|
|
54474
|
+
return handleRegisterAgent(req, ctx, json2);
|
|
52773
54475
|
}
|
|
52774
54476
|
const agentMatch = path.match(/^\/api\/agents\/([^/]+)$/);
|
|
52775
54477
|
if (agentMatch && method === "PATCH") {
|
|
52776
|
-
return handleUpdateAgent(agentMatch[1], req, ctx,
|
|
54478
|
+
return handleUpdateAgent(agentMatch[1], req, ctx, json2);
|
|
52777
54479
|
}
|
|
52778
54480
|
if (agentMatch && method === "DELETE") {
|
|
52779
|
-
return handleDeleteAgent(agentMatch[1], ctx,
|
|
54481
|
+
return handleDeleteAgent(agentMatch[1], ctx, json2);
|
|
52780
54482
|
}
|
|
52781
54483
|
if (path === "/api/agents/bulk" && method === "POST") {
|
|
52782
|
-
return handleBulkDeleteAgents(req, ctx,
|
|
54484
|
+
return handleBulkDeleteAgents(req, ctx, json2);
|
|
52783
54485
|
}
|
|
52784
54486
|
if (path === "/api/projects/bulk" && method === "POST") {
|
|
52785
|
-
return handleBulkDeleteProjects(req, ctx,
|
|
54487
|
+
return handleBulkDeleteProjects(req, ctx, json2);
|
|
52786
54488
|
}
|
|
52787
54489
|
if (path === "/api/doctor" && method === "GET") {
|
|
52788
|
-
return handleDoctor(ctx,
|
|
54490
|
+
return handleDoctor(ctx, json2);
|
|
52789
54491
|
}
|
|
52790
54492
|
if (path === "/api/report" && method === "GET") {
|
|
52791
|
-
return handleReport(req, url, ctx,
|
|
54493
|
+
return handleReport(req, url, ctx, json2);
|
|
52792
54494
|
}
|
|
52793
54495
|
if (path === "/api/activity" && method === "GET") {
|
|
52794
|
-
return handleActivity(req, url, ctx,
|
|
54496
|
+
return handleActivity(req, url, ctx, json2);
|
|
52795
54497
|
}
|
|
52796
54498
|
const historyMatch = path.match(/^\/api\/tasks\/([^/]+)\/history$/);
|
|
52797
54499
|
if (historyMatch && method === "GET") {
|
|
52798
|
-
return handleTaskHistory(historyMatch[1], ctx,
|
|
54500
|
+
return handleTaskHistory(historyMatch[1], ctx, json2, url);
|
|
52799
54501
|
}
|
|
52800
54502
|
if (path === "/api/webhooks" && method === "GET") {
|
|
52801
|
-
return handleListWebhooks(ctx,
|
|
54503
|
+
return handleListWebhooks(ctx, json2);
|
|
52802
54504
|
}
|
|
52803
54505
|
if (path === "/api/webhooks" && method === "POST") {
|
|
52804
|
-
return handleCreateWebhook(req, ctx,
|
|
54506
|
+
return handleCreateWebhook(req, ctx, json2);
|
|
52805
54507
|
}
|
|
52806
54508
|
const webhookMatch = path.match(/^\/api\/webhooks\/([^/]+)$/);
|
|
52807
54509
|
if (webhookMatch && method === "DELETE") {
|
|
52808
|
-
return handleDeleteWebhook(webhookMatch[1], ctx,
|
|
54510
|
+
return handleDeleteWebhook(webhookMatch[1], ctx, json2);
|
|
52809
54511
|
}
|
|
52810
54512
|
if (path === "/api/templates" && method === "GET") {
|
|
52811
|
-
return handleListTemplates(ctx,
|
|
54513
|
+
return handleListTemplates(ctx, json2);
|
|
52812
54514
|
}
|
|
52813
54515
|
if (path === "/api/templates" && method === "POST") {
|
|
52814
|
-
return handleCreateTemplate(req, ctx,
|
|
54516
|
+
return handleCreateTemplate(req, ctx, json2);
|
|
52815
54517
|
}
|
|
52816
54518
|
const templateMatch = path.match(/^\/api\/templates\/([^/]+)$/);
|
|
52817
54519
|
if (templateMatch && method === "DELETE") {
|
|
52818
|
-
return handleDeleteTemplate(templateMatch[1], ctx,
|
|
54520
|
+
return handleDeleteTemplate(templateMatch[1], ctx, json2);
|
|
52819
54521
|
}
|
|
52820
54522
|
if (path === "/api/plans" && method === "GET") {
|
|
52821
|
-
return handleListPlans(url, ctx,
|
|
54523
|
+
return handleListPlans(url, ctx, json2);
|
|
52822
54524
|
}
|
|
52823
54525
|
if (path === "/api/plans" && method === "POST") {
|
|
52824
|
-
return handleCreatePlan(req, ctx,
|
|
54526
|
+
return handleCreatePlan(req, ctx, json2);
|
|
52825
54527
|
}
|
|
52826
54528
|
if (path === "/api/plans/bulk" && method === "POST") {
|
|
52827
|
-
return handleBulkDeletePlans(req, ctx,
|
|
54529
|
+
return handleBulkDeletePlans(req, ctx, json2);
|
|
52828
54530
|
}
|
|
52829
54531
|
const planMatch = path.match(/^\/api\/plans\/([^/]+)$/);
|
|
52830
54532
|
if (planMatch) {
|
|
@@ -52833,16 +54535,16 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
52833
54535
|
return handleGetPlan(id, ctx, jsonWithCors, taskToSummary);
|
|
52834
54536
|
}
|
|
52835
54537
|
if (method === "PATCH") {
|
|
52836
|
-
return handleUpdatePlan(id, req, ctx,
|
|
54538
|
+
return handleUpdatePlan(id, req, ctx, json2);
|
|
52837
54539
|
}
|
|
52838
54540
|
if (method === "DELETE") {
|
|
52839
|
-
return handleDeletePlan(id, ctx,
|
|
54541
|
+
return handleDeletePlan(id, ctx, json2);
|
|
52840
54542
|
}
|
|
52841
54543
|
}
|
|
52842
54544
|
const staticRes = handleStaticFiles(path, method, ctx, jsonWithCors, serveStaticFile);
|
|
52843
54545
|
if (staticRes)
|
|
52844
54546
|
return staticRes;
|
|
52845
|
-
return
|
|
54547
|
+
return json2({ error: "Not found" }, 404);
|
|
52846
54548
|
}
|
|
52847
54549
|
});
|
|
52848
54550
|
const shutdown = () => {
|