@hasna/todos 0.15.6 → 0.15.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/cli/cloud-router.d.ts +13 -1
  2. package/dist/cli/cloud-router.d.ts.map +1 -1
  3. package/dist/cli/commands/project-commands.d.ts.map +1 -1
  4. package/dist/cli/commands/task-commands.d.ts.map +1 -1
  5. package/dist/cli/index.js +4858 -1995
  6. package/dist/contracts.js +247 -3
  7. package/dist/db/migrations.d.ts.map +1 -1
  8. package/dist/db/schema.d.ts.map +1 -1
  9. package/dist/db/task-lists.d.ts +5 -0
  10. package/dist/db/task-lists.d.ts.map +1 -1
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +2405 -27
  14. package/dist/lib/assignee-validation.d.ts +44 -0
  15. package/dist/lib/assignee-validation.d.ts.map +1 -1
  16. package/dist/lib/project-task-list-ensure.d.ts +19 -0
  17. package/dist/lib/project-task-list-ensure.d.ts.map +1 -0
  18. package/dist/mcp/index.js +3156 -374
  19. package/dist/mcp.js +7 -3
  20. package/dist/project-registration/authority.d.ts +45 -0
  21. package/dist/project-registration/authority.d.ts.map +1 -0
  22. package/dist/project-registration/backend.d.ts +83 -0
  23. package/dist/project-registration/backend.d.ts.map +1 -0
  24. package/dist/project-registration/http.d.ts +24 -0
  25. package/dist/project-registration/http.d.ts.map +1 -0
  26. package/dist/project-registration/index.d.ts +8 -0
  27. package/dist/project-registration/index.d.ts.map +1 -0
  28. package/dist/project-registration/postgres.d.ts +30 -0
  29. package/dist/project-registration/postgres.d.ts.map +1 -0
  30. package/dist/project-registration/schema.d.ts +3 -0
  31. package/dist/project-registration/schema.d.ts.map +1 -0
  32. package/dist/project-registration/sqlite.d.ts +17 -0
  33. package/dist/project-registration/sqlite.d.ts.map +1 -0
  34. package/dist/project-registration/types.d.ts +155 -0
  35. package/dist/project-registration/types.d.ts.map +1 -0
  36. package/dist/project-registration.d.ts +2 -0
  37. package/dist/project-registration.d.ts.map +1 -0
  38. package/dist/project-registration.js +18145 -0
  39. package/dist/registry.d.ts +1 -1
  40. package/dist/registry.d.ts.map +1 -1
  41. package/dist/registry.js +254 -3
  42. package/dist/release-provenance.json +5 -5
  43. package/dist/sdk/index.d.ts +1 -1
  44. package/dist/sdk/index.d.ts.map +1 -1
  45. package/dist/sdk/index.js +21 -0
  46. package/dist/sdk/v1.generated.d.ts +43 -0
  47. package/dist/sdk/v1.generated.d.ts.map +1 -1
  48. package/dist/server/cloud.d.ts +3 -0
  49. package/dist/server/cloud.d.ts.map +1 -1
  50. package/dist/server/index.js +5001 -2219
  51. package/dist/server/openapi.d.ts +311 -0
  52. package/dist/server/openapi.d.ts.map +1 -1
  53. package/dist/server/v1.d.ts +2 -1
  54. package/dist/server/v1.d.ts.map +1 -1
  55. package/dist/storage/interfaces.d.ts +11 -0
  56. package/dist/storage/interfaces.d.ts.map +1 -1
  57. package/dist/storage/local-sqlite.d.ts.map +1 -1
  58. package/dist/storage.js +242 -1
  59. package/dist/types/index.d.ts +29 -0
  60. package/dist/types/index.d.ts.map +1 -1
  61. 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,
@@ -11440,6 +11646,40 @@ function deleteTaskList(id, db) {
11440
11646
  return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
11441
11647
  })();
11442
11648
  }
11649
+ function deleteTaskListIfUnchangedAndUnused(id, expected, db) {
11650
+ const d = db || getDatabase();
11651
+ return d.transaction(() => {
11652
+ const current = getTaskList(id, d);
11653
+ if (!current) {
11654
+ return { status: "not_found", task_dependents: 0, plan_dependents: 0 };
11655
+ }
11656
+ const changed = current.project_id !== expected.project_id || current.slug !== expected.slug || current.name !== expected.name || current.description !== expected.description || current.updated_at !== expected.updated_at || JSON.stringify(current.metadata) !== JSON.stringify(expected.metadata);
11657
+ if (changed) {
11658
+ return { status: "changed", task_dependents: 0, plan_dependents: 0 };
11659
+ }
11660
+ const taskDependents = Number(d.query("SELECT COUNT(*) AS count FROM tasks WHERE task_list_id = ?").get(id).count);
11661
+ const planDependents = Number(d.query("SELECT COUNT(*) AS count FROM plans WHERE task_list_id = ?").get(id).count);
11662
+ if (taskDependents > 0 || planDependents > 0) {
11663
+ return {
11664
+ status: "has_dependents",
11665
+ task_dependents: taskDependents,
11666
+ plan_dependents: planDependents
11667
+ };
11668
+ }
11669
+ recordStorageTombstone({
11670
+ object_type: "task_lists",
11671
+ object_id: id,
11672
+ payload: current
11673
+ }, d);
11674
+ releaseCanonicalSlugClaims("task_list", id, d);
11675
+ const deleted = d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
11676
+ return {
11677
+ status: deleted ? "deleted" : "not_found",
11678
+ task_dependents: 0,
11679
+ plan_dependents: 0
11680
+ };
11681
+ })();
11682
+ }
11443
11683
  function ensureTaskList(name, slug, projectId, db) {
11444
11684
  const d = db || getDatabase();
11445
11685
  const existing = getTaskListBySlug(slug, projectId, d);
@@ -20563,12 +20803,16 @@ function protectRemoteClient(client) {
20563
20803
  function remoteAuthorityBase(client) {
20564
20804
  return client.baseUrl.replace(/\/v1\/?$/, "");
20565
20805
  }
20566
- async function requiredRemoteRoute(client, route, request) {
20806
+ async function requiredRemoteRoute(client, route, request, recognized404Codes = []) {
20567
20807
  try {
20568
20808
  return await request();
20569
20809
  } catch (error) {
20570
20810
  const status = error && typeof error === "object" ? error.status : undefined;
20571
20811
  if (status === 404) {
20812
+ const body = error && typeof error === "object" ? error.body : undefined;
20813
+ const code = body && typeof body === "object" && !Array.isArray(body) ? body.code : undefined;
20814
+ if (typeof code === "string" && recognized404Codes.includes(code))
20815
+ throw error;
20572
20816
  throw new Error(`REMOTE_API_INCOMPATIBLE: configured Todos authority ${remoteAuthorityBase(client)} does not expose ${route}; ` + "deploy the @hasna/todos /v1 server contract before retrying; local SQLite fallback is disabled", { cause: error });
20573
20817
  }
20574
20818
  throw error;
@@ -20684,6 +20928,16 @@ async function cloudListProjects(client) {
20684
20928
  const envelope = res.raw;
20685
20929
  return Array.isArray(envelope?.projects) ? envelope.projects : res.items;
20686
20930
  }
20931
+ function unwrapProject(raw) {
20932
+ if (raw && typeof raw === "object" && "project" in raw) {
20933
+ return raw.project;
20934
+ }
20935
+ return raw;
20936
+ }
20937
+ async function cloudGetProjectById(client, id) {
20938
+ const raw = await client.get("projects", id);
20939
+ return raw == null ? null : unwrapProject(raw);
20940
+ }
20687
20941
  function cloudProjectSlug(value) {
20688
20942
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
20689
20943
  }
@@ -20790,29 +21044,63 @@ async function cloudListTaskLists(client, projectId) {
20790
21044
  return envelope.taskLists;
20791
21045
  return Array.isArray(raw) ? raw : [];
20792
21046
  }
21047
+ function unwrapTaskList(raw) {
21048
+ if (raw && typeof raw === "object" && "task_list" in raw) {
21049
+ return raw.task_list;
21050
+ }
21051
+ return raw;
21052
+ }
21053
+ async function cloudGetTaskList(client, id) {
21054
+ const raw = await client.get("task-lists", id);
21055
+ return raw == null ? null : unwrapTaskList(raw);
21056
+ }
21057
+ function resolveTaskListFromCandidates(lists, input) {
21058
+ const normalizedIdRef = input.toLowerCase();
21059
+ const matchGroups = [
21060
+ lists.filter((list) => list.id.toLowerCase() === normalizedIdRef),
21061
+ lists.filter((list) => list.slug === input),
21062
+ lists.filter((list) => list.id.toLowerCase().startsWith(normalizedIdRef))
21063
+ ];
21064
+ for (const matches of matchGroups) {
21065
+ if (matches.length === 1)
21066
+ return matches[0].id;
21067
+ if (matches.length > 1) {
21068
+ throw new Error(`Task list reference is ambiguous: "${input}"`);
21069
+ }
21070
+ }
21071
+ return null;
21072
+ }
21073
+ async function legacyProjectTaskLists(client, projectId) {
21074
+ const project = await cloudGetProjectById(client, projectId);
21075
+ if (!project?.task_list_id)
21076
+ return [];
21077
+ return (await cloudListTaskLists(client)).filter((list) => list.project_id == null && list.slug === project.task_list_id);
21078
+ }
20793
21079
  async function cloudResolveTaskListRef(client, ref, projectId) {
20794
21080
  const input = ref.trim();
20795
21081
  const normalizedIdRef = input.toLowerCase();
20796
21082
  if (UUID_RE.test(input) && !projectId)
20797
21083
  return normalizedIdRef;
20798
- const lists = await cloudListTaskLists(client, projectId);
20799
- const exactIds = lists.filter((list) => list.id.toLowerCase() === normalizedIdRef);
20800
- if (exactIds.length === 1)
20801
- return exactIds[0].id;
20802
- if (exactIds.length > 1) {
20803
- throw new Error(`Task list reference is ambiguous: "${input}"`);
20804
- }
20805
- const slugs = lists.filter((list) => list.slug === input);
20806
- if (slugs.length === 1)
20807
- return slugs[0].id;
20808
- if (slugs.length > 1) {
20809
- throw new Error(`Task list reference is ambiguous: "${input}"`);
20810
- }
20811
- const prefixes = lists.filter((list) => list.id.toLowerCase().startsWith(normalizedIdRef));
20812
- if (prefixes.length === 1)
20813
- return prefixes[0].id;
20814
- if (prefixes.length > 1) {
20815
- throw new Error(`Task list reference is ambiguous: "${input}"`);
21084
+ if (UUID_RE.test(input) && projectId) {
21085
+ const direct = await cloudGetTaskList(client, normalizedIdRef);
21086
+ if (direct?.id?.toLowerCase() === normalizedIdRef) {
21087
+ if (direct.project_id === projectId)
21088
+ return direct.id;
21089
+ if (direct.project_id == null) {
21090
+ const project = await cloudGetProjectById(client, projectId);
21091
+ if (project?.task_list_id === direct.slug)
21092
+ return direct.id;
21093
+ }
21094
+ throw new Error(`Task list not found: "${input}"`);
21095
+ }
21096
+ }
21097
+ const scopedMatch = resolveTaskListFromCandidates(await cloudListTaskLists(client, projectId), input);
21098
+ if (scopedMatch)
21099
+ return scopedMatch;
21100
+ if (projectId) {
21101
+ const legacyMatch = resolveTaskListFromCandidates(await legacyProjectTaskLists(client, projectId), input);
21102
+ if (legacyMatch)
21103
+ return legacyMatch;
20816
21104
  }
20817
21105
  throw new Error(`Task list not found: "${input}"`);
20818
21106
  }
@@ -34926,7 +35214,7 @@ var package_default;
34926
35214
  var init_package = __esm(() => {
34927
35215
  package_default = {
34928
35216
  name: "@hasna/todos",
34929
- version: "0.15.6",
35217
+ version: "0.15.9",
34930
35218
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
34931
35219
  type: "module",
34932
35220
  main: "dist/index.js",
@@ -34964,6 +35252,10 @@ var init_package = __esm(() => {
34964
35252
  "./testing": {
34965
35253
  types: "./dist/testing.d.ts",
34966
35254
  import: "./dist/testing.js"
35255
+ },
35256
+ "./project-registration": {
35257
+ types: "./dist/project-registration.d.ts",
35258
+ import: "./dist/project-registration.js"
34967
35259
  }
34968
35260
  },
34969
35261
  workspaces: [
@@ -34976,8 +35268,8 @@ var init_package = __esm(() => {
34976
35268
  "README.md"
34977
35269
  ],
34978
35270
  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/*'",
35271
+ 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",
35272
+ "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
35273
  migrate: "bun run src/server/index.ts migrate",
34982
35274
  "backfill:comment-redaction": "bun run src/server/index.ts redact-comments",
34983
35275
  "generate:sdk": "bun run scripts/generate-sdk.ts",
@@ -44817,7 +45109,8 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
44817
45109
  getBySlug: (slug, projectId) => getTaskListBySlug(slug, projectId, database()),
44818
45110
  list: (projectId) => listTaskLists(projectId, database()),
44819
45111
  update: (id, input) => updateTaskList(id, input, database()),
44820
- delete: (id) => deleteTaskList(id, database())
45112
+ delete: (id) => deleteTaskList(id, database()),
45113
+ deleteIfUnchangedAndUnused: (id, expected) => deleteTaskListIfUnchangedAndUnused(id, expected, database())
44821
45114
  },
44822
45115
  templates: {
44823
45116
  create: (input) => createTemplate(input, database()),
@@ -47723,6 +48016,2023 @@ var init_postgres = __esm(() => {
47723
48016
  init_types3();
47724
48017
  });
47725
48018
 
48019
+ // src/project-registration/types.ts
48020
+ var TODOS_PROJECT_REGISTRATION_ROUTE = "todos.project-registration.v1", TODOS_PROJECT_REGISTRATION_CALLER_ROUTE = "projects.full-registration.v1", TodosProjectRegistrationError;
48021
+ var init_types4 = __esm(() => {
48022
+ TodosProjectRegistrationError = class TodosProjectRegistrationError extends Error {
48023
+ code;
48024
+ details;
48025
+ constructor(code, message, details = {}) {
48026
+ super(message);
48027
+ this.code = code;
48028
+ this.details = details;
48029
+ this.name = "TodosProjectRegistrationError";
48030
+ }
48031
+ };
48032
+ });
48033
+
48034
+ // src/project-registration/postgres.ts
48035
+ function safeIdentifier(value, field) {
48036
+ if (!/^[a-z_][a-z0-9_]*$/.test(value)) {
48037
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field} must be a safe PostgreSQL identifier`);
48038
+ }
48039
+ return value;
48040
+ }
48041
+ function normalizeTimestamp2(value) {
48042
+ return value instanceof Date ? value.toISOString() : new Date(String(value)).toISOString();
48043
+ }
48044
+ function parsePayload3(value) {
48045
+ if (typeof value === "string")
48046
+ return JSON.parse(value);
48047
+ return value;
48048
+ }
48049
+ function receiptFromRow(row) {
48050
+ return {
48051
+ ...row,
48052
+ authority: "todos",
48053
+ created_by_operation: Boolean(row["created_by_operation"]),
48054
+ created_at: normalizeTimestamp2(row["created_at"])
48055
+ };
48056
+ }
48057
+ function bindingFromRow(row) {
48058
+ return {
48059
+ ...row,
48060
+ created_at: normalizeTimestamp2(row["created_at"]),
48061
+ updated_at: normalizeTimestamp2(row["updated_at"])
48062
+ };
48063
+ }
48064
+
48065
+ class PostgresTodosProjectRegistrationTransaction {
48066
+ client;
48067
+ service;
48068
+ tableName;
48069
+ storage;
48070
+ constructor(client, service, tableName, cursorTableName) {
48071
+ this.client = client;
48072
+ this.service = service;
48073
+ this.tableName = tableName;
48074
+ this.storage = createPostgresTodosStorageAdapter({
48075
+ client,
48076
+ service,
48077
+ tableName,
48078
+ cursorTableName
48079
+ });
48080
+ }
48081
+ async lockStep(identity) {
48082
+ const key = [
48083
+ identity.authority_id,
48084
+ identity.tenant_id,
48085
+ identity.corpus_id,
48086
+ identity.operation_id,
48087
+ identity.step_id,
48088
+ identity.resource_kind,
48089
+ identity.direction
48090
+ ].join("\x1F");
48091
+ await this.client.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [key]);
48092
+ }
48093
+ async getReceiptForLookup(identity) {
48094
+ const result = await this.client.query(`
48095
+ SELECT * FROM todos_project_registration_receipts
48096
+ WHERE authority_id = $1 AND tenant_id = $2 AND corpus_id = $3
48097
+ AND operation_id = $4 AND step_id = $5 AND resource_kind = $6
48098
+ AND direction = $7 AND idempotency_key = $8 AND target_selector = $9
48099
+ ORDER BY CASE outcome
48100
+ WHEN 'terminal_nonacceptance' THEN 0
48101
+ WHEN 'duplicate_of_accepted' THEN 1
48102
+ ELSE 2
48103
+ END, created_at DESC, receipt_id DESC
48104
+ LIMIT 1
48105
+ `, [
48106
+ identity.authority_id,
48107
+ identity.tenant_id,
48108
+ identity.corpus_id,
48109
+ identity.operation_id,
48110
+ identity.step_id,
48111
+ identity.resource_kind,
48112
+ identity.direction,
48113
+ identity.idempotency_key,
48114
+ identity.target_selector
48115
+ ]);
48116
+ return result.rows[0] ? receiptFromRow(result.rows[0]) : null;
48117
+ }
48118
+ async getReceiptById(receiptId) {
48119
+ const result = await this.client.query("SELECT * FROM todos_project_registration_receipts WHERE receipt_id = $1 LIMIT 1", [receiptId]);
48120
+ return result.rows[0] ? receiptFromRow(result.rows[0]) : null;
48121
+ }
48122
+ async getAcceptedReceiptForStep(identity) {
48123
+ const result = await this.client.query(`
48124
+ SELECT * FROM todos_project_registration_receipts
48125
+ WHERE authority_id = $1 AND tenant_id = $2 AND corpus_id = $3
48126
+ AND operation_id = $4 AND step_id = $5 AND resource_kind = $6
48127
+ AND direction = $7 AND outcome = 'accepted'
48128
+ ORDER BY created_at ASC, receipt_id ASC
48129
+ LIMIT 1
48130
+ FOR UPDATE
48131
+ `, [
48132
+ identity.authority_id,
48133
+ identity.tenant_id,
48134
+ identity.corpus_id,
48135
+ identity.operation_id,
48136
+ identity.step_id,
48137
+ identity.resource_kind,
48138
+ identity.direction
48139
+ ]);
48140
+ return result.rows[0] ? receiptFromRow(result.rows[0]) : null;
48141
+ }
48142
+ async insertReceipt(receipt) {
48143
+ const result = await this.client.query(`
48144
+ INSERT INTO todos_project_registration_receipts (
48145
+ receipt_id, authority, route, package_version, authority_id, tenant_id,
48146
+ corpus_id, operation_id, step_id, resource_kind, direction,
48147
+ target_selector, idempotency_key, request_digest, precondition_digest,
48148
+ normalized_call_digest, outcome, reason, target_id, result_revision,
48149
+ result_digest, duplicate_of_receipt_id, accepted_receipt_id,
48150
+ created_by_operation, created_at
48151
+ ) VALUES (
48152
+ $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,
48153
+ $19,$20,$21,$22,$23,$24,$25
48154
+ )
48155
+ ON CONFLICT (receipt_id) DO NOTHING
48156
+ RETURNING receipt_id
48157
+ `, [
48158
+ receipt.receipt_id,
48159
+ receipt.authority,
48160
+ receipt.route,
48161
+ receipt.package_version,
48162
+ receipt.authority_id,
48163
+ receipt.tenant_id,
48164
+ receipt.corpus_id,
48165
+ receipt.operation_id,
48166
+ receipt.step_id,
48167
+ receipt.resource_kind,
48168
+ receipt.direction,
48169
+ receipt.target_selector,
48170
+ receipt.idempotency_key,
48171
+ receipt.request_digest,
48172
+ receipt.precondition_digest,
48173
+ receipt.normalized_call_digest,
48174
+ receipt.outcome,
48175
+ receipt.reason,
48176
+ receipt.target_id,
48177
+ receipt.result_revision,
48178
+ receipt.result_digest,
48179
+ receipt.duplicate_of_receipt_id,
48180
+ receipt.accepted_receipt_id,
48181
+ receipt.created_by_operation,
48182
+ receipt.created_at
48183
+ ]);
48184
+ return result.rows.length === 1;
48185
+ }
48186
+ async getBinding(scope, resourceKind, targetSelector) {
48187
+ const result = await this.client.query(`
48188
+ SELECT * FROM todos_project_registration_bindings
48189
+ WHERE authority_id = $1 AND tenant_id = $2 AND corpus_id = $3
48190
+ AND resource_kind = $4 AND target_selector = $5
48191
+ LIMIT 1
48192
+ FOR UPDATE
48193
+ `, [
48194
+ scope.authority_id,
48195
+ scope.tenant_id,
48196
+ scope.corpus_id,
48197
+ resourceKind,
48198
+ targetSelector
48199
+ ]);
48200
+ return result.rows[0] ? bindingFromRow(result.rows[0]) : null;
48201
+ }
48202
+ async claimBinding(binding) {
48203
+ const result = await this.client.query(`
48204
+ INSERT INTO todos_project_registration_bindings (
48205
+ authority_id, tenant_id, corpus_id, resource_kind, target_selector,
48206
+ operation_id, step_id, direction, idempotency_key, request_digest,
48207
+ precondition_digest, normalized_call_digest, state, target_id,
48208
+ accepted_receipt_id, result_revision, result_digest, removed_receipt_id,
48209
+ created_at, updated_at
48210
+ ) VALUES (
48211
+ $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,
48212
+ $18,$19,$20
48213
+ )
48214
+ ON CONFLICT (
48215
+ authority_id, tenant_id, corpus_id, resource_kind, target_selector
48216
+ ) DO NOTHING
48217
+ RETURNING target_selector
48218
+ `, [
48219
+ binding.authority_id,
48220
+ binding.tenant_id,
48221
+ binding.corpus_id,
48222
+ binding.resource_kind,
48223
+ binding.target_selector,
48224
+ binding.operation_id,
48225
+ binding.step_id,
48226
+ binding.direction,
48227
+ binding.idempotency_key,
48228
+ binding.request_digest,
48229
+ binding.precondition_digest,
48230
+ binding.normalized_call_digest,
48231
+ binding.state,
48232
+ binding.target_id,
48233
+ binding.accepted_receipt_id,
48234
+ binding.result_revision,
48235
+ binding.result_digest,
48236
+ binding.removed_receipt_id,
48237
+ binding.created_at,
48238
+ binding.updated_at
48239
+ ]);
48240
+ return result.rows.length === 1;
48241
+ }
48242
+ async setBindingAccepted(scope, resourceKind, targetSelector, update) {
48243
+ const result = await this.client.query(`
48244
+ UPDATE todos_project_registration_bindings
48245
+ SET state = 'accepted', target_id = $1, accepted_receipt_id = $2,
48246
+ result_revision = $3, result_digest = $4, updated_at = $5
48247
+ WHERE authority_id = $6 AND tenant_id = $7 AND corpus_id = $8
48248
+ AND resource_kind = $9 AND target_selector = $10 AND state = 'pending'
48249
+ RETURNING target_selector
48250
+ `, [
48251
+ update.target_id,
48252
+ update.accepted_receipt_id,
48253
+ update.result_revision,
48254
+ update.result_digest,
48255
+ update.updated_at,
48256
+ scope.authority_id,
48257
+ scope.tenant_id,
48258
+ scope.corpus_id,
48259
+ resourceKind,
48260
+ targetSelector
48261
+ ]);
48262
+ if (result.rows.length !== 1) {
48263
+ throw new Error("Todos project registration binding was not pending at acceptance");
48264
+ }
48265
+ }
48266
+ async setBindingTerminal(scope, resourceKind, targetSelector, updatedAt) {
48267
+ await this.client.query(`
48268
+ UPDATE todos_project_registration_bindings
48269
+ SET state = 'terminal_nonacceptance', updated_at = $1
48270
+ WHERE authority_id = $2 AND tenant_id = $3 AND corpus_id = $4
48271
+ AND resource_kind = $5 AND target_selector = $6 AND state = 'pending'
48272
+ `, [
48273
+ updatedAt,
48274
+ scope.authority_id,
48275
+ scope.tenant_id,
48276
+ scope.corpus_id,
48277
+ resourceKind,
48278
+ targetSelector
48279
+ ]);
48280
+ }
48281
+ async setBindingRemoved(scope, resourceKind, targetSelector, removedReceiptId, updatedAt) {
48282
+ const result = await this.client.query(`
48283
+ UPDATE todos_project_registration_bindings
48284
+ SET state = 'removed', removed_receipt_id = $1, updated_at = $2
48285
+ WHERE authority_id = $3 AND tenant_id = $4 AND corpus_id = $5
48286
+ AND resource_kind = $6 AND target_selector = $7 AND state = 'accepted'
48287
+ RETURNING target_selector
48288
+ `, [
48289
+ removedReceiptId,
48290
+ updatedAt,
48291
+ scope.authority_id,
48292
+ scope.tenant_id,
48293
+ scope.corpus_id,
48294
+ resourceKind,
48295
+ targetSelector
48296
+ ]);
48297
+ if (result.rows.length !== 1) {
48298
+ throw new Error("Todos project registration binding was not accepted at removal");
48299
+ }
48300
+ }
48301
+ async findProjectConflict(path, taskListSlug) {
48302
+ const result = await this.client.query(`
48303
+ SELECT payload FROM ${this.tableName}
48304
+ WHERE service = $1 AND object_type = 'projects' AND deleted_at IS NULL
48305
+ AND (payload->>'path' = $2 OR payload->>'task_list_id' = $3)
48306
+ ORDER BY payload->>'created_at' ASC, object_id ASC
48307
+ LIMIT 1
48308
+ `, [this.service, path, taskListSlug]);
48309
+ return result.rows[0] ? parsePayload3(result.rows[0].payload) : null;
48310
+ }
48311
+ async findTaskListConflict(projectId, slug) {
48312
+ const result = await this.client.query(`
48313
+ SELECT payload FROM ${this.tableName}
48314
+ WHERE service = $1 AND object_type = 'task_lists' AND deleted_at IS NULL
48315
+ AND payload->>'project_id' = $2 AND payload->>'slug' = $3
48316
+ ORDER BY payload->>'created_at' ASC, object_id ASC
48317
+ LIMIT 1
48318
+ `, [this.service, projectId, slug]);
48319
+ return result.rows[0] ? parsePayload3(result.rows[0].payload) : null;
48320
+ }
48321
+ async createProject(input) {
48322
+ return await this.storage.projects.create(input);
48323
+ }
48324
+ async createTaskList(input) {
48325
+ return await this.storage.taskLists.create(input);
48326
+ }
48327
+ async getProject(id) {
48328
+ return await this.storage.projects.get(id);
48329
+ }
48330
+ async getTaskList(id) {
48331
+ return await this.storage.taskLists.get(id);
48332
+ }
48333
+ async lockCompensationWrites() {
48334
+ await this.client.query(`LOCK TABLE ${this.tableName} IN SHARE ROW EXCLUSIVE MODE`);
48335
+ }
48336
+ async hasDependents(resourceKind, targetId) {
48337
+ const referencePredicate = resourceKind === "project" ? `(
48338
+ payload->>'project_id' = $2
48339
+ OR payload->>'active_project_id' = $2
48340
+ OR payload->>'assigned_from_project' = $2
48341
+ OR payload->>'external_project_id' = $2
48342
+ )` : "payload->>'task_list_id' = $2";
48343
+ const result = await this.client.query(`
48344
+ SELECT EXISTS (
48345
+ SELECT 1 FROM ${this.tableName}
48346
+ WHERE service = $1 AND deleted_at IS NULL
48347
+ AND ${referencePredicate}
48348
+ LIMIT 1
48349
+ ) AS exists
48350
+ `, [this.service, targetId]);
48351
+ return result.rows[0]?.exists === true;
48352
+ }
48353
+ async deleteProject(id) {
48354
+ return await this.storage.projects.delete(id);
48355
+ }
48356
+ async deleteTaskList(id) {
48357
+ return await this.storage.taskLists.delete(id);
48358
+ }
48359
+ }
48360
+
48361
+ class PostgresTodosProjectRegistrationBackend {
48362
+ client;
48363
+ kind = "postgresql";
48364
+ service;
48365
+ tableName;
48366
+ cursorTableName;
48367
+ schemaReady = null;
48368
+ constructor(client, options = {}) {
48369
+ this.client = client;
48370
+ this.service = options.service ?? "todos";
48371
+ this.tableName = safeIdentifier(options.tableName ?? "todos_sync_records", "tableName");
48372
+ this.cursorTableName = safeIdentifier(options.cursorTableName ?? "todos_sync_cursors", "cursorTableName");
48373
+ }
48374
+ async ensureSchema() {
48375
+ this.schemaReady ??= (async () => {
48376
+ for (const statement of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
48377
+ await this.client.query(statement);
48378
+ }
48379
+ for (const statement of postgresTodosProjectRegistrationSchemaSql()) {
48380
+ await this.client.query(statement);
48381
+ }
48382
+ })();
48383
+ await this.schemaReady;
48384
+ }
48385
+ async transaction(fn) {
48386
+ await this.ensureSchema();
48387
+ if (typeof this.client.transaction !== "function") {
48388
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE", "PostgreSQL project registration requires an authoritative transaction");
48389
+ }
48390
+ return this.client.transaction((transaction) => fn(new PostgresTodosProjectRegistrationTransaction(transaction, this.service, this.tableName, this.cursorTableName)));
48391
+ }
48392
+ async direct() {
48393
+ await this.ensureSchema();
48394
+ return new PostgresTodosProjectRegistrationTransaction(this.client, this.service, this.tableName, this.cursorTableName);
48395
+ }
48396
+ async getReceiptForLookup(identity) {
48397
+ return (await this.direct()).getReceiptForLookup(identity);
48398
+ }
48399
+ async getReceiptById(receiptId) {
48400
+ return (await this.direct()).getReceiptById(receiptId);
48401
+ }
48402
+ async getBinding(scope, resourceKind, targetSelector) {
48403
+ return (await this.direct()).getBinding(scope, resourceKind, targetSelector);
48404
+ }
48405
+ async getProject(id) {
48406
+ return (await this.direct()).getProject(id);
48407
+ }
48408
+ async getTaskList(id) {
48409
+ return (await this.direct()).getTaskList(id);
48410
+ }
48411
+ }
48412
+ var init_postgres2 = __esm(() => {
48413
+ init_postgres_adapter();
48414
+ init_postgres_sync();
48415
+ init_types4();
48416
+ });
48417
+
48418
+ // src/project-registration/sqlite.ts
48419
+ function sameSqliteValue(left, right) {
48420
+ return JSON.stringify(left) === JSON.stringify(right);
48421
+ }
48422
+ function taskListFromRow(row) {
48423
+ return {
48424
+ ...row,
48425
+ metadata: JSON.parse(row.metadata || "{}")
48426
+ };
48427
+ }
48428
+ function selectProject(db, id) {
48429
+ return db.query("SELECT * FROM projects WHERE id = ? LIMIT 1").get(id);
48430
+ }
48431
+ function selectTaskList(db, id) {
48432
+ const row = db.query("SELECT * FROM task_lists WHERE id = ? LIMIT 1").get(id);
48433
+ return row ? taskListFromRow(row) : null;
48434
+ }
48435
+ function selectProjectConflict(db, path, taskListSlug) {
48436
+ return db.query(`
48437
+ SELECT * FROM projects
48438
+ WHERE path = ? OR task_list_id = ?
48439
+ ORDER BY created_at ASC, id ASC
48440
+ LIMIT 1
48441
+ `).get(path, taskListSlug);
48442
+ }
48443
+ function selectTaskListConflict(db, projectId, slug) {
48444
+ const row = db.query(`
48445
+ SELECT * FROM task_lists
48446
+ WHERE project_id = ? AND slug = ?
48447
+ LIMIT 1
48448
+ `).get(projectId, slug);
48449
+ return row ? taskListFromRow(row) : null;
48450
+ }
48451
+ function quoteSqliteIdentifier(value) {
48452
+ return `"${value.replaceAll('"', '""')}"`;
48453
+ }
48454
+ function hasSqliteDependents(db, resourceKind, targetId) {
48455
+ const targetTable = resourceKind === "project" ? "projects" : "task_lists";
48456
+ const semanticColumns = resourceKind === "project" ? PROJECT_REFERENCE_COLUMNS : TASK_LIST_REFERENCE_COLUMNS;
48457
+ const tables = db.query(`
48458
+ SELECT name FROM sqlite_schema
48459
+ WHERE type = 'table' AND name NOT LIKE 'sqlite_%'
48460
+ ORDER BY name
48461
+ `).all();
48462
+ for (const { name: tableName } of tables) {
48463
+ const quotedTable = quoteSqliteIdentifier(tableName);
48464
+ const columns = db.query(`PRAGMA table_info(${quotedTable})`).all();
48465
+ const foreignKeys = db.query(`PRAGMA foreign_key_list(${quotedTable})`).all();
48466
+ const referenceColumns = columns.map((column) => column.name).filter((columnName) => semanticColumns.has(columnName) || foreignKeys.some((foreignKey) => foreignKey.from === columnName && foreignKey.table === targetTable));
48467
+ for (const columnName of referenceColumns) {
48468
+ const row = db.query(`
48469
+ SELECT 1 AS found
48470
+ FROM ${quotedTable}
48471
+ WHERE ${quoteSqliteIdentifier(columnName)} = ?
48472
+ LIMIT 1
48473
+ `).get(targetId);
48474
+ if (row)
48475
+ return true;
48476
+ }
48477
+ }
48478
+ return false;
48479
+ }
48480
+ function receiptFromRow2(row) {
48481
+ return {
48482
+ ...row,
48483
+ authority: "todos",
48484
+ created_by_operation: Number(row["created_by_operation"]) === 1
48485
+ };
48486
+ }
48487
+ function bindingFromRow2(row) {
48488
+ return row;
48489
+ }
48490
+
48491
+ class SqliteTodosProjectRegistrationTransaction {
48492
+ db;
48493
+ storage;
48494
+ constructor(db) {
48495
+ this.db = db;
48496
+ this.storage = createLocalSqliteTodosStorageAdapter({ db });
48497
+ }
48498
+ async lockStep(_identity) {}
48499
+ async getReceiptForLookup(identity) {
48500
+ const row = this.db.query(`
48501
+ SELECT * FROM todos_project_registration_receipts
48502
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
48503
+ AND operation_id = ? AND step_id = ? AND resource_kind = ?
48504
+ AND direction = ? AND idempotency_key = ? AND target_selector = ?
48505
+ ORDER BY CASE outcome
48506
+ WHEN 'terminal_nonacceptance' THEN 0
48507
+ WHEN 'duplicate_of_accepted' THEN 1
48508
+ ELSE 2
48509
+ END, created_at DESC, receipt_id DESC
48510
+ LIMIT 1
48511
+ `).get(identity.authority_id, identity.tenant_id, identity.corpus_id, identity.operation_id, identity.step_id, identity.resource_kind, identity.direction, identity.idempotency_key, identity.target_selector);
48512
+ return row ? receiptFromRow2(row) : null;
48513
+ }
48514
+ async getReceiptById(receiptId) {
48515
+ const row = this.db.query("SELECT * FROM todos_project_registration_receipts WHERE receipt_id = ? LIMIT 1").get(receiptId);
48516
+ return row ? receiptFromRow2(row) : null;
48517
+ }
48518
+ async getAcceptedReceiptForStep(identity) {
48519
+ const row = this.db.query(`
48520
+ SELECT * FROM todos_project_registration_receipts
48521
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
48522
+ AND operation_id = ? AND step_id = ? AND resource_kind = ?
48523
+ AND direction = ? AND outcome = 'accepted'
48524
+ ORDER BY created_at ASC, receipt_id ASC
48525
+ LIMIT 1
48526
+ `).get(identity.authority_id, identity.tenant_id, identity.corpus_id, identity.operation_id, identity.step_id, identity.resource_kind, identity.direction);
48527
+ return row ? receiptFromRow2(row) : null;
48528
+ }
48529
+ async insertReceipt(receipt) {
48530
+ const result = this.db.query(`
48531
+ INSERT OR IGNORE INTO todos_project_registration_receipts (
48532
+ receipt_id, authority, route, package_version, authority_id, tenant_id,
48533
+ corpus_id, operation_id, step_id, resource_kind, direction,
48534
+ target_selector, idempotency_key, request_digest, precondition_digest,
48535
+ normalized_call_digest, outcome, reason, target_id, result_revision,
48536
+ result_digest, duplicate_of_receipt_id, accepted_receipt_id,
48537
+ created_by_operation, created_at
48538
+ ) VALUES (
48539
+ ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?
48540
+ )
48541
+ `).run(receipt.receipt_id, receipt.authority, receipt.route, receipt.package_version, receipt.authority_id, receipt.tenant_id, receipt.corpus_id, receipt.operation_id, receipt.step_id, receipt.resource_kind, receipt.direction, receipt.target_selector, receipt.idempotency_key, receipt.request_digest, receipt.precondition_digest, receipt.normalized_call_digest, receipt.outcome, receipt.reason, receipt.target_id, receipt.result_revision, receipt.result_digest, receipt.duplicate_of_receipt_id, receipt.accepted_receipt_id, receipt.created_by_operation ? 1 : 0, receipt.created_at);
48542
+ return result.changes === 1;
48543
+ }
48544
+ async getBinding(scope, resourceKind, targetSelector) {
48545
+ const row = this.db.query(`
48546
+ SELECT * FROM todos_project_registration_bindings
48547
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
48548
+ AND resource_kind = ? AND target_selector = ?
48549
+ LIMIT 1
48550
+ `).get(scope.authority_id, scope.tenant_id, scope.corpus_id, resourceKind, targetSelector);
48551
+ return row ? bindingFromRow2(row) : null;
48552
+ }
48553
+ async claimBinding(binding) {
48554
+ const result = this.db.query(`
48555
+ INSERT OR IGNORE INTO todos_project_registration_bindings (
48556
+ authority_id, tenant_id, corpus_id, resource_kind, target_selector,
48557
+ operation_id, step_id, direction, idempotency_key, request_digest,
48558
+ precondition_digest, normalized_call_digest, state, target_id,
48559
+ accepted_receipt_id, result_revision, result_digest, removed_receipt_id,
48560
+ created_at, updated_at
48561
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
48562
+ `).run(binding.authority_id, binding.tenant_id, binding.corpus_id, binding.resource_kind, binding.target_selector, binding.operation_id, binding.step_id, binding.direction, binding.idempotency_key, binding.request_digest, binding.precondition_digest, binding.normalized_call_digest, binding.state, binding.target_id, binding.accepted_receipt_id, binding.result_revision, binding.result_digest, binding.removed_receipt_id, binding.created_at, binding.updated_at);
48563
+ return result.changes === 1;
48564
+ }
48565
+ async setBindingAccepted(scope, resourceKind, targetSelector, update) {
48566
+ const result = this.db.query(`
48567
+ UPDATE todos_project_registration_bindings
48568
+ SET state = 'accepted', target_id = ?, accepted_receipt_id = ?,
48569
+ result_revision = ?, result_digest = ?, updated_at = ?
48570
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
48571
+ AND resource_kind = ? AND target_selector = ? AND state = 'pending'
48572
+ `).run(update.target_id, update.accepted_receipt_id, update.result_revision, update.result_digest, update.updated_at, scope.authority_id, scope.tenant_id, scope.corpus_id, resourceKind, targetSelector);
48573
+ if (result.changes !== 1) {
48574
+ throw new Error("Todos project registration binding was not pending at acceptance");
48575
+ }
48576
+ }
48577
+ async setBindingTerminal(scope, resourceKind, targetSelector, updatedAt) {
48578
+ this.db.query(`
48579
+ UPDATE todos_project_registration_bindings
48580
+ SET state = 'terminal_nonacceptance', updated_at = ?
48581
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
48582
+ AND resource_kind = ? AND target_selector = ? AND state = 'pending'
48583
+ `).run(updatedAt, scope.authority_id, scope.tenant_id, scope.corpus_id, resourceKind, targetSelector);
48584
+ }
48585
+ async setBindingRemoved(scope, resourceKind, targetSelector, removedReceiptId, updatedAt) {
48586
+ const result = this.db.query(`
48587
+ UPDATE todos_project_registration_bindings
48588
+ SET state = 'removed', removed_receipt_id = ?, updated_at = ?
48589
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
48590
+ AND resource_kind = ? AND target_selector = ? AND state = 'accepted'
48591
+ `).run(removedReceiptId, updatedAt, scope.authority_id, scope.tenant_id, scope.corpus_id, resourceKind, targetSelector);
48592
+ if (result.changes !== 1) {
48593
+ throw new Error("Todos project registration binding was not accepted at removal");
48594
+ }
48595
+ }
48596
+ async findProjectConflict(path, taskListSlug) {
48597
+ const row = this.db.query(`
48598
+ SELECT * FROM projects
48599
+ WHERE path = ? OR task_list_id = ?
48600
+ ORDER BY created_at ASC, id ASC
48601
+ LIMIT 1
48602
+ `).get(path, taskListSlug);
48603
+ return row ?? null;
48604
+ }
48605
+ async findTaskListConflict(projectId, slug) {
48606
+ return await this.storage.taskLists.getBySlug(slug, projectId);
48607
+ }
48608
+ async createProject(input) {
48609
+ return await this.storage.projects.create(input);
48610
+ }
48611
+ async createTaskList(input) {
48612
+ return await this.storage.taskLists.create(input);
48613
+ }
48614
+ async getProject(id) {
48615
+ return await this.storage.projects.get(id);
48616
+ }
48617
+ async getTaskList(id) {
48618
+ return await this.storage.taskLists.get(id);
48619
+ }
48620
+ async lockCompensationWrites() {}
48621
+ async hasDependents(resourceKind, targetId) {
48622
+ return hasSqliteDependents(this.db, resourceKind, targetId);
48623
+ }
48624
+ async deleteProject(id) {
48625
+ return await this.storage.projects.delete(id);
48626
+ }
48627
+ async deleteTaskList(id) {
48628
+ return await this.storage.taskLists.delete(id);
48629
+ }
48630
+ }
48631
+
48632
+ class StagedSqliteTodosProjectRegistrationTransaction {
48633
+ db;
48634
+ direct;
48635
+ validators = [];
48636
+ mutations = [];
48637
+ receipts = new Map;
48638
+ bindings = new Map;
48639
+ projects = new Map;
48640
+ taskLists = new Map;
48641
+ constructor(db) {
48642
+ this.db = db;
48643
+ this.direct = new SqliteTodosProjectRegistrationTransaction(db);
48644
+ }
48645
+ commit() {
48646
+ this.db.exec("BEGIN IMMEDIATE");
48647
+ try {
48648
+ for (const validate of this.validators) {
48649
+ if (!validate()) {
48650
+ throw new SqliteRegistrationOptimisticConflict("Todos project registration input changed before SQLite commit");
48651
+ }
48652
+ }
48653
+ for (const mutate of this.mutations)
48654
+ mutate();
48655
+ this.db.exec("COMMIT");
48656
+ } catch (error) {
48657
+ try {
48658
+ this.db.exec("ROLLBACK");
48659
+ } catch {}
48660
+ throw error;
48661
+ }
48662
+ }
48663
+ async lockStep(_identity) {}
48664
+ async getReceiptForLookup(identity) {
48665
+ const staged = [...this.receipts.values()].filter((receipt) => receipt.authority_id === identity.authority_id && receipt.tenant_id === identity.tenant_id && receipt.corpus_id === identity.corpus_id && receipt.operation_id === identity.operation_id && receipt.step_id === identity.step_id && receipt.resource_kind === identity.resource_kind && receipt.direction === identity.direction && receipt.idempotency_key === identity.idempotency_key && receipt.target_selector === identity.target_selector);
48666
+ const stored = await this.direct.getReceiptForLookup(identity);
48667
+ if (stored)
48668
+ staged.push(stored);
48669
+ const outcomeRank = (receipt) => receipt.outcome === "terminal_nonacceptance" ? 0 : receipt.outcome === "duplicate_of_accepted" ? 1 : 2;
48670
+ return staged.sort((left, right) => outcomeRank(left) - outcomeRank(right) || right.created_at.localeCompare(left.created_at) || right.receipt_id.localeCompare(left.receipt_id))[0] ?? null;
48671
+ }
48672
+ async getReceiptById(receiptId) {
48673
+ return this.receipts.get(receiptId) ?? this.direct.getReceiptById(receiptId);
48674
+ }
48675
+ async getAcceptedReceiptForStep(identity) {
48676
+ const staged = [...this.receipts.values()].filter((receipt) => receipt.authority_id === identity.authority_id && receipt.tenant_id === identity.tenant_id && receipt.corpus_id === identity.corpus_id && receipt.operation_id === identity.operation_id && receipt.step_id === identity.step_id && receipt.resource_kind === identity.resource_kind && receipt.direction === identity.direction && receipt.outcome === "accepted");
48677
+ const stored = await this.direct.getAcceptedReceiptForStep(identity);
48678
+ if (stored)
48679
+ staged.push(stored);
48680
+ return staged.sort((left, right) => left.created_at.localeCompare(right.created_at) || left.receipt_id.localeCompare(right.receipt_id))[0] ?? null;
48681
+ }
48682
+ async insertReceipt(receipt) {
48683
+ if (this.receipts.has(receipt.receipt_id))
48684
+ return false;
48685
+ if (await this.direct.getReceiptById(receipt.receipt_id))
48686
+ return false;
48687
+ const planned = { ...receipt };
48688
+ this.receipts.set(planned.receipt_id, planned);
48689
+ this.mutations.push(() => {
48690
+ try {
48691
+ const result = this.db.query(`
48692
+ INSERT OR IGNORE INTO todos_project_registration_receipts (
48693
+ receipt_id, authority, route, package_version, authority_id, tenant_id,
48694
+ corpus_id, operation_id, step_id, resource_kind, direction,
48695
+ target_selector, idempotency_key, request_digest, precondition_digest,
48696
+ normalized_call_digest, outcome, reason, target_id, result_revision,
48697
+ result_digest, duplicate_of_receipt_id, accepted_receipt_id,
48698
+ created_by_operation, created_at
48699
+ ) VALUES (
48700
+ ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?
48701
+ )
48702
+ `).run(planned.receipt_id, planned.authority, planned.route, planned.package_version, planned.authority_id, planned.tenant_id, planned.corpus_id, planned.operation_id, planned.step_id, planned.resource_kind, planned.direction, planned.target_selector, planned.idempotency_key, planned.request_digest, planned.precondition_digest, planned.normalized_call_digest, planned.outcome, planned.reason, planned.target_id, planned.result_revision, planned.result_digest, planned.duplicate_of_receipt_id, planned.accepted_receipt_id, planned.created_by_operation ? 1 : 0, planned.created_at);
48703
+ if (result.changes !== 1) {
48704
+ throw new SqliteRegistrationOptimisticConflict("Todos project registration receipt changed before SQLite commit");
48705
+ }
48706
+ } catch (error) {
48707
+ if (error instanceof SqliteRegistrationOptimisticConflict)
48708
+ throw error;
48709
+ throw new SqliteRegistrationOptimisticConflict("Todos project registration receipt conflicted at SQLite commit", { cause: error });
48710
+ }
48711
+ });
48712
+ return true;
48713
+ }
48714
+ async getBinding(scope, resourceKind, targetSelector) {
48715
+ const key = this.bindingKey(scope, resourceKind, targetSelector);
48716
+ return this.bindings.get(key) ?? this.direct.getBinding(scope, resourceKind, targetSelector);
48717
+ }
48718
+ async claimBinding(binding) {
48719
+ const key = this.bindingKey(binding, binding.resource_kind, binding.target_selector);
48720
+ if (this.bindings.has(key))
48721
+ return false;
48722
+ if (await this.direct.getBinding(binding, binding.resource_kind, binding.target_selector)) {
48723
+ return false;
48724
+ }
48725
+ const planned = { ...binding };
48726
+ this.bindings.set(key, planned);
48727
+ this.mutations.push(() => {
48728
+ try {
48729
+ const result = this.db.query(`
48730
+ INSERT OR IGNORE INTO todos_project_registration_bindings (
48731
+ authority_id, tenant_id, corpus_id, resource_kind, target_selector,
48732
+ operation_id, step_id, direction, idempotency_key, request_digest,
48733
+ precondition_digest, normalized_call_digest, state, target_id,
48734
+ accepted_receipt_id, result_revision, result_digest, removed_receipt_id,
48735
+ created_at, updated_at
48736
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
48737
+ `).run(planned.authority_id, planned.tenant_id, planned.corpus_id, planned.resource_kind, planned.target_selector, planned.operation_id, planned.step_id, planned.direction, planned.idempotency_key, planned.request_digest, planned.precondition_digest, planned.normalized_call_digest, planned.state, planned.target_id, planned.accepted_receipt_id, planned.result_revision, planned.result_digest, planned.removed_receipt_id, planned.created_at, planned.updated_at);
48738
+ if (result.changes !== 1) {
48739
+ throw new SqliteRegistrationOptimisticConflict("Todos project registration binding changed before SQLite commit");
48740
+ }
48741
+ } catch (error) {
48742
+ if (error instanceof SqliteRegistrationOptimisticConflict)
48743
+ throw error;
48744
+ throw new SqliteRegistrationOptimisticConflict("Todos project registration binding conflicted at SQLite commit", { cause: error });
48745
+ }
48746
+ });
48747
+ return true;
48748
+ }
48749
+ async setBindingAccepted(scope, resourceKind, targetSelector, update) {
48750
+ const binding = await this.requireBinding(scope, resourceKind, targetSelector, "pending");
48751
+ this.bindings.set(this.bindingKey(scope, resourceKind, targetSelector), {
48752
+ ...binding,
48753
+ state: "accepted",
48754
+ target_id: update.target_id,
48755
+ accepted_receipt_id: update.accepted_receipt_id,
48756
+ result_revision: update.result_revision,
48757
+ result_digest: update.result_digest,
48758
+ updated_at: update.updated_at
48759
+ });
48760
+ this.mutations.push(() => {
48761
+ const result = this.db.query(`
48762
+ UPDATE todos_project_registration_bindings
48763
+ SET state = 'accepted', target_id = ?, accepted_receipt_id = ?,
48764
+ result_revision = ?, result_digest = ?, updated_at = ?
48765
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
48766
+ AND resource_kind = ? AND target_selector = ? AND state = 'pending'
48767
+ `).run(update.target_id, update.accepted_receipt_id, update.result_revision, update.result_digest, update.updated_at, scope.authority_id, scope.tenant_id, scope.corpus_id, resourceKind, targetSelector);
48768
+ if (result.changes !== 1) {
48769
+ throw new SqliteRegistrationOptimisticConflict("Todos project registration binding was no longer pending at SQLite commit");
48770
+ }
48771
+ });
48772
+ }
48773
+ async setBindingTerminal(scope, resourceKind, targetSelector, updatedAt) {
48774
+ const binding = await this.requireBinding(scope, resourceKind, targetSelector, "pending");
48775
+ this.bindings.set(this.bindingKey(scope, resourceKind, targetSelector), {
48776
+ ...binding,
48777
+ state: "terminal_nonacceptance",
48778
+ updated_at: updatedAt
48779
+ });
48780
+ this.mutations.push(() => {
48781
+ const result = this.db.query(`
48782
+ UPDATE todos_project_registration_bindings
48783
+ SET state = 'terminal_nonacceptance', updated_at = ?
48784
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
48785
+ AND resource_kind = ? AND target_selector = ? AND state = 'pending'
48786
+ `).run(updatedAt, scope.authority_id, scope.tenant_id, scope.corpus_id, resourceKind, targetSelector);
48787
+ if (result.changes !== 1) {
48788
+ throw new SqliteRegistrationOptimisticConflict("Todos project registration binding was no longer pending at SQLite commit");
48789
+ }
48790
+ });
48791
+ }
48792
+ async setBindingRemoved(scope, resourceKind, targetSelector, removedReceiptId, updatedAt) {
48793
+ const binding = await this.requireBinding(scope, resourceKind, targetSelector, "accepted");
48794
+ this.bindings.set(this.bindingKey(scope, resourceKind, targetSelector), {
48795
+ ...binding,
48796
+ state: "removed",
48797
+ removed_receipt_id: removedReceiptId,
48798
+ updated_at: updatedAt
48799
+ });
48800
+ this.mutations.push(() => {
48801
+ const result = this.db.query(`
48802
+ UPDATE todos_project_registration_bindings
48803
+ SET state = 'removed', removed_receipt_id = ?, updated_at = ?
48804
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
48805
+ AND resource_kind = ? AND target_selector = ? AND state = 'accepted'
48806
+ `).run(removedReceiptId, updatedAt, scope.authority_id, scope.tenant_id, scope.corpus_id, resourceKind, targetSelector);
48807
+ if (result.changes !== 1) {
48808
+ throw new SqliteRegistrationOptimisticConflict("Todos project registration binding was no longer accepted at SQLite commit");
48809
+ }
48810
+ });
48811
+ }
48812
+ async findProjectConflict(path, taskListSlug) {
48813
+ const planned = [...this.projects.values()].find((project) => project?.path === path || project?.task_list_id === taskListSlug);
48814
+ if (planned)
48815
+ return planned;
48816
+ const observed = selectProjectConflict(this.db, path, taskListSlug);
48817
+ this.validators.push(() => sameSqliteValue(selectProjectConflict(this.db, path, taskListSlug), observed));
48818
+ return observed;
48819
+ }
48820
+ async findTaskListConflict(projectId, slug) {
48821
+ const planned = [...this.taskLists.values()].find((taskList) => taskList?.project_id === projectId && taskList.slug === slug);
48822
+ if (planned)
48823
+ return planned;
48824
+ const observed = selectTaskListConflict(this.db, projectId, slug);
48825
+ this.validators.push(() => sameSqliteValue(selectTaskListConflict(this.db, projectId, slug), observed));
48826
+ return observed;
48827
+ }
48828
+ async createProject(input) {
48829
+ const derivedSlug = normalizeSlug(input.name);
48830
+ const taskListId = input.task_list_id === undefined ? `todos-${derivedSlug}` : normalizeSlug(input.task_list_id);
48831
+ if (!derivedSlug || !taskListId) {
48832
+ throw new Error("Project name and task-list slug must be non-empty");
48833
+ }
48834
+ const project = {
48835
+ id: uuid(),
48836
+ name: input.name,
48837
+ path: input.path,
48838
+ description: input.description || null,
48839
+ task_list_id: taskListId,
48840
+ task_prefix: input.task_prefix ?? this.availableProjectPrefix(input.name),
48841
+ task_counter: 0,
48842
+ created_at: now(),
48843
+ updated_at: now(),
48844
+ machine_id: currentStorageMachineId(this.db)
48845
+ };
48846
+ project.updated_at = project.created_at;
48847
+ this.projects.set(project.id, project);
48848
+ this.mutations.push(() => {
48849
+ try {
48850
+ const result = this.db.run(`INSERT INTO projects (
48851
+ id, name, path, description, task_list_id, task_prefix,
48852
+ task_counter, created_at, updated_at, machine_id
48853
+ ) VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)`, [
48854
+ project.id,
48855
+ project.name,
48856
+ project.path,
48857
+ project.description,
48858
+ project.task_list_id,
48859
+ project.task_prefix,
48860
+ project.created_at,
48861
+ project.updated_at,
48862
+ project.machine_id ?? null
48863
+ ]);
48864
+ if (result.changes < 1) {
48865
+ throw new SqliteRegistrationOptimisticConflict("Todos project changed before SQLite registration commit");
48866
+ }
48867
+ } catch (error) {
48868
+ if (error instanceof SqliteRegistrationOptimisticConflict)
48869
+ throw error;
48870
+ throw new SqliteRegistrationOptimisticConflict("Todos project conflicted at SQLite registration commit", { cause: error });
48871
+ }
48872
+ });
48873
+ return project;
48874
+ }
48875
+ async createTaskList(input) {
48876
+ const slug = normalizeSlug(input.slug === undefined ? input.name : input.slug);
48877
+ if (!slug)
48878
+ throw new Error("Invalid task-list slug \u2014 must be non-empty kebab-case");
48879
+ const taskList = {
48880
+ id: uuid(),
48881
+ project_id: input.project_id || null,
48882
+ slug,
48883
+ name: input.name,
48884
+ description: input.description || null,
48885
+ metadata: input.metadata ?? {},
48886
+ created_at: now(),
48887
+ updated_at: now(),
48888
+ machine_id: currentStorageMachineId(this.db)
48889
+ };
48890
+ taskList.updated_at = taskList.created_at;
48891
+ this.taskLists.set(taskList.id, taskList);
48892
+ this.mutations.push(() => {
48893
+ try {
48894
+ const result = this.db.run(`INSERT INTO task_lists (
48895
+ id, project_id, slug, name, description, metadata,
48896
+ created_at, updated_at, machine_id
48897
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
48898
+ taskList.id,
48899
+ taskList.project_id,
48900
+ taskList.slug,
48901
+ taskList.name,
48902
+ taskList.description,
48903
+ JSON.stringify(taskList.metadata),
48904
+ taskList.created_at,
48905
+ taskList.updated_at,
48906
+ taskList.machine_id ?? null
48907
+ ]);
48908
+ if (result.changes < 1) {
48909
+ throw new SqliteRegistrationOptimisticConflict("Todos task list changed before SQLite registration commit");
48910
+ }
48911
+ } catch (error) {
48912
+ if (error instanceof SqliteRegistrationOptimisticConflict)
48913
+ throw error;
48914
+ throw new SqliteRegistrationOptimisticConflict("Todos task list conflicted at SQLite registration commit", { cause: error });
48915
+ }
48916
+ });
48917
+ return taskList;
48918
+ }
48919
+ async getProject(id) {
48920
+ if (this.projects.has(id))
48921
+ return this.projects.get(id) ?? null;
48922
+ const observed = selectProject(this.db, id);
48923
+ this.validators.push(() => sameSqliteValue(selectProject(this.db, id), observed));
48924
+ return observed;
48925
+ }
48926
+ async getTaskList(id) {
48927
+ if (this.taskLists.has(id))
48928
+ return this.taskLists.get(id) ?? null;
48929
+ const observed = selectTaskList(this.db, id);
48930
+ this.validators.push(() => sameSqliteValue(selectTaskList(this.db, id), observed));
48931
+ return observed;
48932
+ }
48933
+ async lockCompensationWrites() {}
48934
+ async hasDependents(resourceKind, targetId) {
48935
+ const observed = hasSqliteDependents(this.db, resourceKind, targetId);
48936
+ this.validators.push(() => hasSqliteDependents(this.db, resourceKind, targetId) === observed);
48937
+ return observed;
48938
+ }
48939
+ async deleteProject(id) {
48940
+ const project = await this.getProject(id);
48941
+ if (!project)
48942
+ return false;
48943
+ this.projects.set(id, null);
48944
+ this.mutations.push(() => {
48945
+ recordStorageTombstone({
48946
+ object_type: "projects",
48947
+ object_id: id,
48948
+ payload: project
48949
+ }, this.db);
48950
+ if (this.db.run("DELETE FROM projects WHERE id = ?", [id]).changes < 1) {
48951
+ throw new SqliteRegistrationOptimisticConflict("Todos project changed before SQLite compensation commit");
48952
+ }
48953
+ });
48954
+ return true;
48955
+ }
48956
+ async deleteTaskList(id) {
48957
+ const taskList = await this.getTaskList(id);
48958
+ if (!taskList)
48959
+ return false;
48960
+ this.taskLists.set(id, null);
48961
+ this.mutations.push(() => {
48962
+ recordStorageTombstone({
48963
+ object_type: "task_lists",
48964
+ object_id: id,
48965
+ payload: taskList
48966
+ }, this.db);
48967
+ if (this.db.run("DELETE FROM task_lists WHERE id = ?", [id]).changes < 1) {
48968
+ throw new SqliteRegistrationOptimisticConflict("Todos task list changed before SQLite compensation commit");
48969
+ }
48970
+ });
48971
+ return true;
48972
+ }
48973
+ bindingKey(scope, resourceKind, targetSelector) {
48974
+ return JSON.stringify([
48975
+ scope.authority_id,
48976
+ scope.tenant_id,
48977
+ scope.corpus_id,
48978
+ resourceKind,
48979
+ targetSelector
48980
+ ]);
48981
+ }
48982
+ async requireBinding(scope, resourceKind, targetSelector, state) {
48983
+ const binding = await this.getBinding(scope, resourceKind, targetSelector);
48984
+ if (!binding || binding.state !== state) {
48985
+ throw new Error(`Todos project registration binding was not ${state}`);
48986
+ }
48987
+ return binding;
48988
+ }
48989
+ availableProjectPrefix(name) {
48990
+ const words = name.replace(/[^a-zA-Z0-9\s]/g, "").trim().split(/\s+/);
48991
+ const prefix = words.length >= 3 ? words.slice(0, 3).map((word) => word[0].toUpperCase()).join("") : words.length === 2 ? (words[0].slice(0, 2) + words[1][0]).toUpperCase() : words[0].slice(0, 3).toUpperCase();
48992
+ let candidate = prefix;
48993
+ let suffix = 1;
48994
+ while (this.db.query("SELECT id FROM projects WHERE task_prefix = ? LIMIT 1").get(candidate) || [...this.projects.values()].some((project) => project?.task_prefix === candidate)) {
48995
+ suffix += 1;
48996
+ candidate = `${prefix}${suffix}`;
48997
+ }
48998
+ return candidate;
48999
+ }
49000
+ }
49001
+ var sqliteTransactionTails, PROJECT_REFERENCE_COLUMNS, TASK_LIST_REFERENCE_COLUMNS, SqliteRegistrationOptimisticConflict;
49002
+ var init_sqlite = __esm(() => {
49003
+ init_database();
49004
+ init_storage_tombstones();
49005
+ init_local_sqlite();
49006
+ sqliteTransactionTails = new WeakMap;
49007
+ PROJECT_REFERENCE_COLUMNS = new Set([
49008
+ "project_id",
49009
+ "active_project_id",
49010
+ "assigned_from_project",
49011
+ "external_project_id"
49012
+ ]);
49013
+ TASK_LIST_REFERENCE_COLUMNS = new Set(["task_list_id"]);
49014
+ SqliteRegistrationOptimisticConflict = class SqliteRegistrationOptimisticConflict extends Error {
49015
+ constructor(message, options = {}) {
49016
+ super(message, options);
49017
+ this.name = "SqliteRegistrationOptimisticConflict";
49018
+ }
49019
+ };
49020
+ });
49021
+
49022
+ // src/project-registration/authority.ts
49023
+ import { createHash as createHash14 } from "crypto";
49024
+ function canonicalProjectRegistrationJson(value) {
49025
+ return JSON.stringify(canonicalize2(value));
49026
+ }
49027
+ function canonicalize2(value) {
49028
+ if (Array.isArray(value))
49029
+ return value.map(canonicalize2);
49030
+ if (!value || typeof value !== "object")
49031
+ return value;
49032
+ const out = {};
49033
+ for (const key of Object.keys(value).sort()) {
49034
+ const entry2 = value[key];
49035
+ if (entry2 !== undefined)
49036
+ out[key] = canonicalize2(entry2);
49037
+ }
49038
+ return out;
49039
+ }
49040
+ function digestProjectRegistrationValue(value) {
49041
+ return createHash14("sha256").update(canonicalProjectRegistrationJson(value)).digest("hex");
49042
+ }
49043
+ function deriveTodosProjectRegistrationIdempotencyKey(input) {
49044
+ return `prk_${digestProjectRegistrationValue({
49045
+ route: TODOS_PROJECT_REGISTRATION_CALLER_ROUTE,
49046
+ ...input
49047
+ }).slice(0, 48)}`;
49048
+ }
49049
+ function responseBytes(value) {
49050
+ return Buffer.byteLength(JSON.stringify(value), "utf8");
49051
+ }
49052
+ function assertBounds(bounds) {
49053
+ if (!Number.isSafeInteger(bounds.response_byte_limit) || bounds.response_byte_limit <= 0) {
49054
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "response_byte_limit must be a positive integer");
49055
+ }
49056
+ if (!Number.isSafeInteger(bounds.time_budget_ms) || bounds.time_budget_ms <= 0) {
49057
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "time_budget_ms must be a positive integer");
49058
+ }
49059
+ }
49060
+ function assertResourceKind(value) {
49061
+ if (value !== "project" && value !== "task_list") {
49062
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "resource_kind must be project or task_list");
49063
+ }
49064
+ }
49065
+ function assertDirection(value) {
49066
+ if (value !== "forward" && value !== "inverse") {
49067
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "direction must be forward or inverse");
49068
+ }
49069
+ }
49070
+ function assertWithinBounds(value, bounds, startedAt) {
49071
+ const bytes = responseBytes(value);
49072
+ if (bytes > bounds.response_byte_limit) {
49073
+ 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 });
49074
+ }
49075
+ const elapsed = Date.now() - startedAt;
49076
+ if (elapsed > bounds.time_budget_ms) {
49077
+ 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 });
49078
+ }
49079
+ return { response_bytes: bytes, elapsed_ms: elapsed };
49080
+ }
49081
+ function withResponseControl(payload, bounds, startedAt) {
49082
+ const envelope = {
49083
+ ...payload,
49084
+ response_control: {
49085
+ response_byte_limit: bounds.response_byte_limit,
49086
+ time_budget_ms: bounds.time_budget_ms,
49087
+ response_bytes: 0,
49088
+ elapsed_ms: 0,
49089
+ complete: true,
49090
+ truncated: false
49091
+ }
49092
+ };
49093
+ for (let attempt = 0;attempt < 8; attempt += 1) {
49094
+ const measured = assertWithinBounds(envelope, bounds, startedAt);
49095
+ const stable3 = envelope.response_control.response_bytes === measured.response_bytes && envelope.response_control.elapsed_ms === measured.elapsed_ms;
49096
+ envelope.response_control = {
49097
+ response_byte_limit: bounds.response_byte_limit,
49098
+ time_budget_ms: bounds.time_budget_ms,
49099
+ response_bytes: measured.response_bytes,
49100
+ elapsed_ms: measured.elapsed_ms,
49101
+ complete: true,
49102
+ truncated: false
49103
+ };
49104
+ if (stable3)
49105
+ break;
49106
+ }
49107
+ const finalMeasurement = assertWithinBounds(envelope, bounds, startedAt);
49108
+ envelope.response_control.response_bytes = finalMeasurement.response_bytes;
49109
+ envelope.response_control.elapsed_ms = finalMeasurement.elapsed_ms;
49110
+ return envelope;
49111
+ }
49112
+ function requireString(value, field, options = {}) {
49113
+ const min = options.min ?? 1;
49114
+ const max = options.max ?? 512;
49115
+ if (typeof value !== "string" || value.length < min || value.length > max || /[\u0000-\u001f]/.test(value) || options.pattern && !options.pattern.test(value)) {
49116
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field} is not a valid bounded registration identifier`);
49117
+ }
49118
+ return value;
49119
+ }
49120
+ function exactKeys(value, expected, field) {
49121
+ const actual = Object.keys(value).sort();
49122
+ const wanted = [...expected].sort();
49123
+ if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) {
49124
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field} must contain exactly: ${wanted.join(", ")}`);
49125
+ }
49126
+ }
49127
+ function publicReceipt(row) {
49128
+ const {
49129
+ target_selector: _targetSelector,
49130
+ normalized_call_digest: _normalizedCallDigest,
49131
+ ...receipt
49132
+ } = row;
49133
+ return receipt;
49134
+ }
49135
+ function projectRegistrationPath(projectId) {
49136
+ return `hasna-project://${encodeURIComponent(projectId)}`;
49137
+ }
49138
+ function taskListSlug(projectSlug) {
49139
+ const slug = normalizeSlug(projectSlug);
49140
+ if (!slug || slug !== projectSlug) {
49141
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "project_slug must be canonical kebab-case");
49142
+ }
49143
+ return `todos-${slug}`;
49144
+ }
49145
+ function deterministicTaskPrefix(projectSlug) {
49146
+ const letters = projectSlug.replace(/[^a-z0-9]/gi, "").toUpperCase();
49147
+ return (letters.slice(0, 3) || "PRJ").padEnd(3, "X");
49148
+ }
49149
+ function projectRecord(project) {
49150
+ return {
49151
+ target_id: project.id,
49152
+ revision: project.updated_at,
49153
+ digest: digestProjectRegistrationValue({
49154
+ id: project.id,
49155
+ name: project.name,
49156
+ path: project.path,
49157
+ description: project.description,
49158
+ task_list_id: project.task_list_id,
49159
+ task_prefix: project.task_prefix,
49160
+ task_counter: project.task_counter,
49161
+ created_at: project.created_at,
49162
+ updated_at: project.updated_at
49163
+ })
49164
+ };
49165
+ }
49166
+ function taskListRecord(taskList) {
49167
+ return {
49168
+ target_id: taskList.id,
49169
+ revision: taskList.updated_at,
49170
+ digest: digestProjectRegistrationValue({
49171
+ id: taskList.id,
49172
+ project_id: taskList.project_id,
49173
+ slug: taskList.slug,
49174
+ name: taskList.name,
49175
+ description: taskList.description,
49176
+ metadata: taskList.metadata,
49177
+ created_at: taskList.created_at,
49178
+ updated_at: taskList.updated_at
49179
+ })
49180
+ };
49181
+ }
49182
+ function receiptId(input) {
49183
+ return `tpr_${digestProjectRegistrationValue(input).slice(0, 40)}`;
49184
+ }
49185
+ function capabilityMatches(request, capability) {
49186
+ 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;
49187
+ }
49188
+ function authorityScope(capability) {
49189
+ return {
49190
+ authority_id: capability.authority_id,
49191
+ tenant_id: capability.tenant_id,
49192
+ corpus_id: capability.corpus_id
49193
+ };
49194
+ }
49195
+ function assertCapabilityRequest(request, capability) {
49196
+ if (!capabilityMatches(request, capability)) {
49197
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH", "registration request does not match this authority capability identity");
49198
+ }
49199
+ }
49200
+ function normalizedCallDigest(request) {
49201
+ return digestProjectRegistrationValue({
49202
+ authority_route: request.authority_route,
49203
+ package_version: request.package_version,
49204
+ authority_id: request.authority_id,
49205
+ tenant_id: request.tenant_id,
49206
+ corpus_id: request.corpus_id,
49207
+ operation_id: request.operation_id,
49208
+ step_id: request.step_id,
49209
+ resource_kind: request.resource_kind,
49210
+ direction: request.direction,
49211
+ target_selector: request.target_selector,
49212
+ idempotency_key: request.idempotency_key,
49213
+ request_digest: request.request_digest,
49214
+ precondition_digest: request.precondition_digest,
49215
+ project_id: request.project_id,
49216
+ project_slug: request.project_slug,
49217
+ project_name: request.project_name,
49218
+ desired: request.desired,
49219
+ accepted_receipt_id: request.accepted_receipt?.receipt_id ?? null
49220
+ });
49221
+ }
49222
+ function assertCommonRequest(request, capability) {
49223
+ assertBounds(request);
49224
+ assertResourceKind(request.resource_kind);
49225
+ assertDirection(request.direction);
49226
+ assertCapabilityRequest(request, capability);
49227
+ requireString(request.operation_id, "operation_id", {
49228
+ min: 8,
49229
+ max: 128,
49230
+ pattern: OPERATION_PATTERN
49231
+ });
49232
+ requireString(request.step_id, "step_id", {
49233
+ min: 3,
49234
+ max: 128,
49235
+ pattern: STEP_PATTERN
49236
+ });
49237
+ requireString(request.target_selector, "target_selector", { max: 512 });
49238
+ requireString(request.project_id, "project_id", {
49239
+ min: 16,
49240
+ max: 128,
49241
+ pattern: WORKSPACE_ID_PATTERN
49242
+ });
49243
+ requireString(request.project_name, "project_name", { max: 256 });
49244
+ requireString(request.project_slug, "project_slug", { max: 128 });
49245
+ requireString(request.request_digest, "request_digest", {
49246
+ min: 64,
49247
+ max: 64,
49248
+ pattern: SHA256_PATTERN
49249
+ });
49250
+ requireString(request.precondition_digest, "precondition_digest", {
49251
+ min: 64,
49252
+ max: 64,
49253
+ pattern: SHA256_PATTERN
49254
+ });
49255
+ requireString(request.idempotency_key, "idempotency_key", {
49256
+ min: 52,
49257
+ max: 52,
49258
+ pattern: IDEMPOTENCY_PATTERN
49259
+ });
49260
+ if (!request.desired || typeof request.desired !== "object" || Array.isArray(request.desired)) {
49261
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "desired must be a JSON object");
49262
+ }
49263
+ const expectedKey = deriveTodosProjectRegistrationIdempotencyKey({
49264
+ operation_id: request.operation_id,
49265
+ step_id: request.step_id,
49266
+ direction: request.direction,
49267
+ target_selector: request.target_selector,
49268
+ request_digest: request.request_digest,
49269
+ precondition_digest: request.precondition_digest
49270
+ });
49271
+ if (request.idempotency_key !== expectedKey) {
49272
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_IDEMPOTENCY_MISMATCH", "idempotency_key does not match the deterministic operation/step/direction payload", { expected: expectedKey });
49273
+ }
49274
+ taskListSlug(request.project_slug);
49275
+ }
49276
+ function assertForwardRequest(request, capability) {
49277
+ assertCommonRequest(request, capability);
49278
+ if (request.direction !== "forward") {
49279
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "create requires direction=forward");
49280
+ }
49281
+ const expectedRequestDigest = digestProjectRegistrationValue(request.desired);
49282
+ const expectedPreconditionDigest = digestProjectRegistrationValue({
49283
+ target_selector: request.target_selector,
49284
+ expected: "absent"
49285
+ });
49286
+ if (request.request_digest !== expectedRequestDigest || request.precondition_digest !== expectedPreconditionDigest) {
49287
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "request_digest or precondition_digest does not match normalized forward semantics", {
49288
+ expected_request_digest: expectedRequestDigest,
49289
+ expected_precondition_digest: expectedPreconditionDigest
49290
+ });
49291
+ }
49292
+ if (request.resource_kind === "project") {
49293
+ exactKeys(request.desired, ["source_project_id", "source_project_slug", "name"], "project desired");
49294
+ 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) {
49295
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "project desired state and target selector must match the complete Projects identity");
49296
+ }
49297
+ return;
49298
+ }
49299
+ if (request.resource_kind === "task_list") {
49300
+ exactKeys(request.desired, ["todos_project_id", "source_project_id", "name"], "task-list desired");
49301
+ const todosProjectId = request.desired["todos_project_id"];
49302
+ if (typeof todosProjectId !== "string" || !UUID_PATTERN.test(todosProjectId)) {
49303
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED", "task-list create requires the exact full Todos project UUID");
49304
+ }
49305
+ if (request.target_selector !== `${todosProjectId}:default` || request.desired["source_project_id"] !== request.project_id || request.desired["name"] !== request.project_name) {
49306
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "task-list desired state must bind the exact Todos project id and Projects identity");
49307
+ }
49308
+ return;
49309
+ }
49310
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "unsupported registration resource kind");
49311
+ }
49312
+ function assertInverseRequest(request, capability) {
49313
+ assertCommonRequest(request, capability);
49314
+ if (request.direction !== "inverse") {
49315
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "compensate requires direction=inverse");
49316
+ }
49317
+ const accepted = request.accepted_receipt;
49318
+ 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) {
49319
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "inverse requires the complete accepted forward receipt created by this operation");
49320
+ }
49321
+ exactKeys(request.desired, ["accepted_receipt_id", "target_id"], "inverse desired");
49322
+ const expectedDesired = {
49323
+ accepted_receipt_id: accepted.receipt_id,
49324
+ target_id: accepted.target_id
49325
+ };
49326
+ const expectedPrecondition = {
49327
+ expected_revision: accepted.result_revision,
49328
+ expected_digest: accepted.result_digest
49329
+ };
49330
+ const expectedRequestDigest = digestProjectRegistrationValue(expectedDesired);
49331
+ const expectedPreconditionDigest = digestProjectRegistrationValue(expectedPrecondition);
49332
+ if (canonicalProjectRegistrationJson(request.desired) !== canonicalProjectRegistrationJson(expectedDesired) || request.request_digest !== expectedRequestDigest || request.precondition_digest !== expectedPreconditionDigest || request.target_selector !== accepted.target_id) {
49333
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "inverse request does not match the accepted receipt and exact readback precondition");
49334
+ }
49335
+ return accepted;
49336
+ }
49337
+ function makeReceipt(input, createdAt2) {
49338
+ return {
49339
+ ...input,
49340
+ receipt_id: receiptId(input),
49341
+ created_at: createdAt2
49342
+ };
49343
+ }
49344
+ function receiptBase(request, callDigest, capability) {
49345
+ return {
49346
+ authority: "todos",
49347
+ route: capability.route,
49348
+ package_version: capability.package_version,
49349
+ authority_id: capability.authority_id,
49350
+ tenant_id: capability.tenant_id,
49351
+ corpus_id: capability.corpus_id,
49352
+ operation_id: request.operation_id,
49353
+ step_id: request.step_id,
49354
+ resource_kind: request.resource_kind,
49355
+ direction: request.direction,
49356
+ target_selector: request.target_selector,
49357
+ idempotency_key: request.idempotency_key,
49358
+ request_digest: request.request_digest,
49359
+ precondition_digest: request.precondition_digest,
49360
+ normalized_call_digest: callDigest
49361
+ };
49362
+ }
49363
+ function makeAcceptedReceipt(request, callDigest, capability, record, createdAt2) {
49364
+ return makeReceipt({
49365
+ ...receiptBase(request, callDigest, capability),
49366
+ outcome: "accepted",
49367
+ reason: null,
49368
+ target_id: record.target_id,
49369
+ result_revision: record.revision,
49370
+ result_digest: record.digest,
49371
+ duplicate_of_receipt_id: null,
49372
+ accepted_receipt_id: request.direction === "inverse" ? request.accepted_receipt.receipt_id : null,
49373
+ created_by_operation: true
49374
+ }, createdAt2);
49375
+ }
49376
+ function makeDuplicateReceipt(request, callDigest, capability, accepted, createdAt2) {
49377
+ return makeReceipt({
49378
+ ...receiptBase(request, callDigest, capability),
49379
+ outcome: "duplicate_of_accepted",
49380
+ reason: null,
49381
+ target_id: accepted.target_id,
49382
+ result_revision: accepted.result_revision,
49383
+ result_digest: accepted.result_digest,
49384
+ duplicate_of_receipt_id: accepted.receipt_id,
49385
+ accepted_receipt_id: null,
49386
+ created_by_operation: false
49387
+ }, createdAt2);
49388
+ }
49389
+ function makeTerminalReceipt(request, callDigest, capability, reason, createdAt2, options = {}) {
49390
+ return makeReceipt({
49391
+ ...receiptBase(request, callDigest, capability),
49392
+ outcome: "terminal_nonacceptance",
49393
+ reason,
49394
+ target_id: options.targetId ?? null,
49395
+ result_revision: null,
49396
+ result_digest: null,
49397
+ duplicate_of_receipt_id: null,
49398
+ accepted_receipt_id: options.acceptedReceiptId ?? null,
49399
+ created_by_operation: false
49400
+ }, createdAt2);
49401
+ }
49402
+ async function insertDeterministicReceipt(transaction, receipt) {
49403
+ if (await transaction.insertReceipt(receipt))
49404
+ return receipt;
49405
+ const existing = await transaction.getReceiptById(receipt.receipt_id);
49406
+ const { created_at: _existingCreatedAt, ...existingContent } = existing ?? {};
49407
+ const { created_at: _receiptCreatedAt, ...receiptContent } = receipt;
49408
+ if (!existing || canonicalProjectRegistrationJson(existingContent) !== canonicalProjectRegistrationJson(receiptContent)) {
49409
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "deterministic receipt id is occupied by different immutable content", { receipt_id: receipt.receipt_id });
49410
+ }
49411
+ return existing;
49412
+ }
49413
+ function bindingFor(request, callDigest, timestamp4, capability) {
49414
+ return {
49415
+ ...authorityScope(capability),
49416
+ resource_kind: request.resource_kind,
49417
+ target_selector: request.target_selector,
49418
+ operation_id: request.operation_id,
49419
+ step_id: request.step_id,
49420
+ direction: "forward",
49421
+ idempotency_key: request.idempotency_key,
49422
+ request_digest: request.request_digest,
49423
+ precondition_digest: request.precondition_digest,
49424
+ normalized_call_digest: callDigest,
49425
+ state: "pending",
49426
+ target_id: null,
49427
+ accepted_receipt_id: null,
49428
+ result_revision: null,
49429
+ result_digest: null,
49430
+ removed_receipt_id: null,
49431
+ created_at: timestamp4,
49432
+ updated_at: timestamp4
49433
+ };
49434
+ }
49435
+
49436
+ class PackageOwnedTodosProjectRegistrationAuthority {
49437
+ backend;
49438
+ authority = "todos";
49439
+ capabilityValue;
49440
+ now;
49441
+ faultInjector;
49442
+ constructor(backend, options = {}) {
49443
+ this.backend = backend;
49444
+ this.capabilityValue = {
49445
+ authority: "todos",
49446
+ route: TODOS_PROJECT_REGISTRATION_ROUTE,
49447
+ package_version: options.packageVersion ?? getPackageVersion(import.meta.url),
49448
+ authority_id: options.authorityId ?? "todos",
49449
+ tenant_id: options.tenantId ?? backend.kind,
49450
+ corpus_id: options.corpusId ?? `todos:${backend.kind}`,
49451
+ supported_resources: ["project", "task_list"],
49452
+ conditional_create: true,
49453
+ immutable_receipts: true,
49454
+ exact_terminal_lookup: true,
49455
+ exact_readback: true,
49456
+ conditional_inverse: true,
49457
+ ambiguous_outcome_reconciliation: true
49458
+ };
49459
+ this.now = options.now ?? (() => new Date().toISOString());
49460
+ this.faultInjector = options.faultInjector;
49461
+ }
49462
+ async capability() {
49463
+ return {
49464
+ ...this.capabilityValue,
49465
+ supported_resources: [...this.capabilityValue.supported_resources]
49466
+ };
49467
+ }
49468
+ async fault(point, request) {
49469
+ if (!this.faultInjector)
49470
+ return;
49471
+ try {
49472
+ await this.faultInjector(point, {
49473
+ operation_id: request.operation_id,
49474
+ step_id: request.step_id,
49475
+ resource_kind: request.resource_kind,
49476
+ direction: request.direction
49477
+ });
49478
+ } catch (cause) {
49479
+ throw new WriteBoundaryError(point, cause);
49480
+ }
49481
+ }
49482
+ async afterCommit(request) {
49483
+ await this.faultInjector?.("after_commit", {
49484
+ operation_id: request.operation_id,
49485
+ step_id: request.step_id,
49486
+ resource_kind: request.resource_kind,
49487
+ direction: request.direction
49488
+ });
49489
+ }
49490
+ async duplicateFor(transaction, request, callDigest, accepted) {
49491
+ const duplicate = makeDuplicateReceipt(request, callDigest, this.capabilityValue, accepted, this.now());
49492
+ return insertDeterministicReceipt(transaction, duplicate);
49493
+ }
49494
+ async terminalFor(transaction, request, callDigest, reason, options = {}) {
49495
+ return insertDeterministicReceipt(transaction, makeTerminalReceipt(request, callDigest, this.capabilityValue, reason, this.now(), options));
49496
+ }
49497
+ async existingForwardResolution(transaction, request, callDigest) {
49498
+ const exact = await transaction.getReceiptForLookup({
49499
+ ...authorityScope(this.capabilityValue),
49500
+ operation_id: request.operation_id,
49501
+ step_id: request.step_id,
49502
+ resource_kind: request.resource_kind,
49503
+ direction: request.direction,
49504
+ idempotency_key: request.idempotency_key,
49505
+ target_selector: request.target_selector
49506
+ });
49507
+ if (exact) {
49508
+ if (exact.outcome === "terminal_nonacceptance")
49509
+ return exact;
49510
+ const accepted2 = exact.outcome === "accepted" ? exact : await transaction.getReceiptById(exact.duplicate_of_receipt_id);
49511
+ if (!accepted2) {
49512
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "duplicate receipt points to a missing accepted receipt");
49513
+ }
49514
+ if (accepted2.normalized_call_digest !== callDigest) {
49515
+ return this.terminalFor(transaction, request, callDigest, "operation_step_semantics_changed", { targetId: accepted2.target_id });
49516
+ }
49517
+ return this.duplicateFor(transaction, request, callDigest, accepted2);
49518
+ }
49519
+ const accepted = await transaction.getAcceptedReceiptForStep({
49520
+ ...authorityScope(this.capabilityValue),
49521
+ operation_id: request.operation_id,
49522
+ step_id: request.step_id,
49523
+ resource_kind: request.resource_kind,
49524
+ direction: "forward"
49525
+ });
49526
+ if (!accepted)
49527
+ return null;
49528
+ if (accepted.normalized_call_digest === callDigest) {
49529
+ return this.duplicateFor(transaction, request, callDigest, accepted);
49530
+ }
49531
+ return this.terminalFor(transaction, request, callDigest, "operation_step_semantics_changed", { targetId: accepted.target_id });
49532
+ }
49533
+ async createObject(transaction, request) {
49534
+ if (request.resource_kind === "project") {
49535
+ const path = projectRegistrationPath(request.project_id);
49536
+ const slug2 = taskListSlug(request.project_slug);
49537
+ const conflict2 = await transaction.findProjectConflict(path, slug2);
49538
+ if (conflict2) {
49539
+ return this.terminalFor(transaction, request, normalizedCallDigest(request), "target_already_exists", { targetId: conflict2.id });
49540
+ }
49541
+ await this.fault("before_object_write", request);
49542
+ const project = await transaction.createProject({
49543
+ name: request.project_name,
49544
+ path,
49545
+ description: `Registered from Projects workspace ${request.project_id}`,
49546
+ task_list_id: slug2,
49547
+ task_prefix: deterministicTaskPrefix(request.project_slug)
49548
+ });
49549
+ await this.fault("after_object_write", request);
49550
+ return projectRecord(project);
49551
+ }
49552
+ const todosProjectId = String(request.desired["todos_project_id"]);
49553
+ const sourceBinding = await transaction.getBinding(authorityScope(this.capabilityValue), "project", request.project_id);
49554
+ if (!sourceBinding || sourceBinding.state !== "accepted" || sourceBinding.target_id !== todosProjectId) {
49555
+ return this.terminalFor(transaction, request, normalizedCallDigest(request), "exact_parent_registration_missing", { targetId: todosProjectId });
49556
+ }
49557
+ const parent = await transaction.getProject(todosProjectId);
49558
+ if (!parent) {
49559
+ return this.terminalFor(transaction, request, normalizedCallDigest(request), "exact_parent_project_missing", { targetId: todosProjectId });
49560
+ }
49561
+ const slug = taskListSlug(request.project_slug);
49562
+ const conflict = await transaction.findTaskListConflict(todosProjectId, slug);
49563
+ if (conflict) {
49564
+ return this.terminalFor(transaction, request, normalizedCallDigest(request), "target_already_exists", { targetId: conflict.id });
49565
+ }
49566
+ await this.fault("before_object_write", request);
49567
+ const taskList = await transaction.createTaskList({
49568
+ name: request.project_name,
49569
+ slug,
49570
+ project_id: todosProjectId,
49571
+ metadata: {
49572
+ source_project_id: request.project_id,
49573
+ registration_authority: "todos"
49574
+ }
49575
+ });
49576
+ await this.fault("after_object_write", request);
49577
+ if (taskList.project_id !== todosProjectId) {
49578
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "task-list create did not preserve the exact full Todos project id");
49579
+ }
49580
+ return taskListRecord(taskList);
49581
+ }
49582
+ async create(request) {
49583
+ const startedAt = Date.now();
49584
+ assertForwardRequest(request, this.capabilityValue);
49585
+ const callDigest = normalizedCallDigest(request);
49586
+ try {
49587
+ const row = await this.backend.transaction(async (transaction) => {
49588
+ await transaction.lockStep({
49589
+ ...authorityScope(this.capabilityValue),
49590
+ operation_id: request.operation_id,
49591
+ step_id: request.step_id,
49592
+ resource_kind: request.resource_kind,
49593
+ direction: request.direction
49594
+ });
49595
+ const resolved = await this.existingForwardResolution(transaction, request, callDigest);
49596
+ if (resolved)
49597
+ return resolved;
49598
+ const timestamp4 = this.now();
49599
+ const claimed = await transaction.claimBinding(bindingFor(request, callDigest, timestamp4, this.capabilityValue));
49600
+ if (!claimed) {
49601
+ const binding = await transaction.getBinding(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector);
49602
+ if (binding?.state === "accepted" && binding.normalized_call_digest === callDigest && binding.accepted_receipt_id) {
49603
+ const accepted2 = await transaction.getReceiptById(binding.accepted_receipt_id);
49604
+ if (accepted2) {
49605
+ return this.duplicateFor(transaction, request, callDigest, accepted2);
49606
+ }
49607
+ }
49608
+ return this.terminalFor(transaction, request, callDigest, binding?.state === "removed" ? "target_registration_was_removed" : "target_already_registered", { targetId: binding?.target_id ?? null });
49609
+ }
49610
+ const recordOrTerminal = await this.createObject(transaction, request);
49611
+ if ("outcome" in recordOrTerminal) {
49612
+ await transaction.setBindingTerminal(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector, this.now());
49613
+ return recordOrTerminal;
49614
+ }
49615
+ const accepted = makeAcceptedReceipt(request, callDigest, this.capabilityValue, recordOrTerminal, this.now());
49616
+ await this.fault("before_receipt_write", request);
49617
+ const stored = await insertDeterministicReceipt(transaction, accepted);
49618
+ await this.fault("after_receipt_write", request);
49619
+ await transaction.setBindingAccepted(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector, {
49620
+ target_id: recordOrTerminal.target_id,
49621
+ accepted_receipt_id: stored.receipt_id,
49622
+ result_revision: recordOrTerminal.revision,
49623
+ result_digest: recordOrTerminal.digest,
49624
+ updated_at: this.now()
49625
+ });
49626
+ return stored;
49627
+ });
49628
+ await this.afterCommit(request);
49629
+ const receipt = publicReceipt(row);
49630
+ assertWithinBounds(receipt, request, startedAt);
49631
+ return receipt;
49632
+ } catch (error) {
49633
+ if (!(error instanceof WriteBoundaryError))
49634
+ throw error;
49635
+ const terminal = await this.recordWriteFailure(request, callDigest, error.point);
49636
+ const receipt = publicReceipt(terminal);
49637
+ assertWithinBounds(receipt, request, startedAt);
49638
+ return receipt;
49639
+ }
49640
+ }
49641
+ async recordWriteFailure(request, callDigest, point) {
49642
+ return this.backend.transaction(async (transaction) => {
49643
+ await transaction.lockStep({
49644
+ ...authorityScope(this.capabilityValue),
49645
+ operation_id: request.operation_id,
49646
+ step_id: request.step_id,
49647
+ resource_kind: request.resource_kind,
49648
+ direction: request.direction
49649
+ });
49650
+ const exact = await transaction.getReceiptForLookup({
49651
+ ...authorityScope(this.capabilityValue),
49652
+ operation_id: request.operation_id,
49653
+ step_id: request.step_id,
49654
+ resource_kind: request.resource_kind,
49655
+ direction: request.direction,
49656
+ idempotency_key: request.idempotency_key,
49657
+ target_selector: request.target_selector
49658
+ });
49659
+ if (exact)
49660
+ return exact;
49661
+ const accepted = await transaction.getAcceptedReceiptForStep({
49662
+ ...authorityScope(this.capabilityValue),
49663
+ operation_id: request.operation_id,
49664
+ step_id: request.step_id,
49665
+ resource_kind: request.resource_kind,
49666
+ direction: request.direction
49667
+ });
49668
+ if (accepted) {
49669
+ 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 });
49670
+ }
49671
+ const timestamp4 = this.now();
49672
+ const claimed = await transaction.claimBinding(bindingFor(request, callDigest, timestamp4, this.capabilityValue));
49673
+ const terminal = await this.terminalFor(transaction, request, callDigest, `write_failed:${point}`);
49674
+ if (claimed) {
49675
+ await transaction.setBindingTerminal(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector, this.now());
49676
+ }
49677
+ return terminal;
49678
+ });
49679
+ }
49680
+ async readExact(request) {
49681
+ const startedAt = Date.now();
49682
+ assertBounds(request);
49683
+ assertResourceKind(request.resource_kind);
49684
+ if (!UUID_PATTERN.test(request.target_id)) {
49685
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED", "exact readback requires a complete Todos object UUID");
49686
+ }
49687
+ 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);
49688
+ if (!record) {
49689
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_RECORD_NOT_FOUND", `registered ${request.resource_kind} was not found by exact id`, { target_id: request.target_id });
49690
+ }
49691
+ assertWithinBounds(record, request, startedAt);
49692
+ return record;
49693
+ }
49694
+ async lookupReceipt(request) {
49695
+ const startedAt = Date.now();
49696
+ assertBounds(request);
49697
+ assertResourceKind(request.resource_kind);
49698
+ assertDirection(request.direction);
49699
+ if (request.max_items !== 1) {
49700
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "max_items must be exactly 1 for terminal receipt lookup");
49701
+ }
49702
+ 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) {
49703
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH", "receipt lookup does not match this authority capability identity");
49704
+ }
49705
+ requireString(request.operation_id, "operation_id", {
49706
+ min: 8,
49707
+ max: 128,
49708
+ pattern: OPERATION_PATTERN
49709
+ });
49710
+ requireString(request.step_id, "step_id", {
49711
+ min: 3,
49712
+ max: 128,
49713
+ pattern: STEP_PATTERN
49714
+ });
49715
+ requireString(request.target_selector, "target_selector", { max: 512 });
49716
+ requireString(request.idempotency_key, "idempotency_key", {
49717
+ min: 52,
49718
+ max: 52,
49719
+ pattern: IDEMPOTENCY_PATTERN
49720
+ });
49721
+ if (request.target_id !== undefined && !UUID_PATTERN.test(request.target_id)) {
49722
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED", "receipt lookup target_id must be a complete Todos object UUID");
49723
+ }
49724
+ const receipt = await this.backend.getReceiptForLookup({
49725
+ ...authorityScope(this.capabilityValue),
49726
+ operation_id: request.operation_id,
49727
+ step_id: request.step_id,
49728
+ resource_kind: request.resource_kind,
49729
+ direction: request.direction,
49730
+ idempotency_key: request.idempotency_key,
49731
+ target_selector: request.target_selector
49732
+ });
49733
+ if (!receipt || request.target_id && receipt.target_id !== request.target_id) {
49734
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND", "no exact terminal receipt matched the bounded lookup");
49735
+ }
49736
+ return withResponseControl({ receipt: publicReceipt(receipt) }, request, startedAt);
49737
+ }
49738
+ async storedAcceptedReceipt(request, supplied) {
49739
+ const stored = await this.backend.getReceiptById(supplied.receipt_id);
49740
+ if (!stored || stored.outcome !== "accepted" || !stored.created_by_operation || canonicalProjectRegistrationJson(publicReceipt(stored)) !== canonicalProjectRegistrationJson(supplied)) {
49741
+ 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 });
49742
+ }
49743
+ 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) {
49744
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND", "accepted receipt does not own this exact operation step and target");
49745
+ }
49746
+ return stored;
49747
+ }
49748
+ async compensate(request) {
49749
+ const startedAt = Date.now();
49750
+ const suppliedAccepted = assertInverseRequest(request, this.capabilityValue);
49751
+ const accepted = await this.storedAcceptedReceipt(request, suppliedAccepted);
49752
+ const callDigest = normalizedCallDigest(request);
49753
+ try {
49754
+ const row = await this.backend.transaction(async (transaction) => {
49755
+ await transaction.lockStep({
49756
+ ...authorityScope(this.capabilityValue),
49757
+ operation_id: request.operation_id,
49758
+ step_id: request.step_id,
49759
+ resource_kind: request.resource_kind,
49760
+ direction: request.direction
49761
+ });
49762
+ const exact = await transaction.getReceiptForLookup({
49763
+ ...authorityScope(this.capabilityValue),
49764
+ operation_id: request.operation_id,
49765
+ step_id: request.step_id,
49766
+ resource_kind: request.resource_kind,
49767
+ direction: "inverse",
49768
+ idempotency_key: request.idempotency_key,
49769
+ target_selector: request.target_selector
49770
+ });
49771
+ if (exact)
49772
+ return exact;
49773
+ const storedAccepted = await transaction.getReceiptById(accepted.receipt_id);
49774
+ if (!storedAccepted || storedAccepted.outcome !== "accepted" || !storedAccepted.created_by_operation) {
49775
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND", "accepted receipt disappeared before conditional inverse");
49776
+ }
49777
+ const binding = await transaction.getBinding(authorityScope(this.capabilityValue), accepted.resource_kind, accepted.target_selector);
49778
+ if (!binding || binding.state !== "accepted" || binding.accepted_receipt_id !== accepted.receipt_id || binding.target_id !== accepted.target_id) {
49779
+ return this.terminalFor(transaction, request, callDigest, "target_not_owned_by_receipt", {
49780
+ targetId: accepted.target_id,
49781
+ acceptedReceiptId: accepted.receipt_id
49782
+ });
49783
+ }
49784
+ await transaction.lockCompensationWrites();
49785
+ const object = request.resource_kind === "project" ? await transaction.getProject(accepted.target_id) : await transaction.getTaskList(accepted.target_id);
49786
+ if (!object) {
49787
+ return this.terminalFor(transaction, request, callDigest, "target_missing_before_inverse", {
49788
+ targetId: accepted.target_id,
49789
+ acceptedReceiptId: accepted.receipt_id
49790
+ });
49791
+ }
49792
+ const current = request.resource_kind === "project" ? projectRecord(object) : taskListRecord(object);
49793
+ if (current.revision !== accepted.result_revision || current.digest !== accepted.result_digest) {
49794
+ return this.terminalFor(transaction, request, callDigest, "target_drifted", {
49795
+ targetId: accepted.target_id,
49796
+ acceptedReceiptId: accepted.receipt_id
49797
+ });
49798
+ }
49799
+ if (await transaction.hasDependents(request.resource_kind, accepted.target_id)) {
49800
+ return this.terminalFor(transaction, request, callDigest, "target_has_dependents", {
49801
+ targetId: accepted.target_id,
49802
+ acceptedReceiptId: accepted.receipt_id
49803
+ });
49804
+ }
49805
+ await this.fault("before_object_write", request);
49806
+ const deleted = request.resource_kind === "project" ? await transaction.deleteProject(accepted.target_id) : await transaction.deleteTaskList(accepted.target_id);
49807
+ if (!deleted) {
49808
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "conditional inverse could not delete the exact accepted target");
49809
+ }
49810
+ await this.fault("after_object_write", request);
49811
+ const inverseRecord = {
49812
+ target_id: accepted.target_id,
49813
+ revision: "absent",
49814
+ digest: digestProjectRegistrationValue({
49815
+ target_id: accepted.target_id,
49816
+ accepted_receipt_id: accepted.receipt_id,
49817
+ absent: true
49818
+ })
49819
+ };
49820
+ const inverse = makeAcceptedReceipt(request, callDigest, this.capabilityValue, inverseRecord, this.now());
49821
+ await this.fault("before_receipt_write", request);
49822
+ const stored = await insertDeterministicReceipt(transaction, inverse);
49823
+ await this.fault("after_receipt_write", request);
49824
+ await transaction.setBindingRemoved(authorityScope(this.capabilityValue), accepted.resource_kind, accepted.target_selector, stored.receipt_id, this.now());
49825
+ return stored;
49826
+ });
49827
+ await this.afterCommit(request);
49828
+ const receipt = publicReceipt(row);
49829
+ assertWithinBounds(receipt, request, startedAt);
49830
+ return receipt;
49831
+ } catch (error) {
49832
+ if (!(error instanceof WriteBoundaryError))
49833
+ throw error;
49834
+ const terminal = await this.backend.transaction(async (transaction) => {
49835
+ await transaction.lockStep({
49836
+ ...authorityScope(this.capabilityValue),
49837
+ operation_id: request.operation_id,
49838
+ step_id: request.step_id,
49839
+ resource_kind: request.resource_kind,
49840
+ direction: request.direction
49841
+ });
49842
+ return this.terminalFor(transaction, request, callDigest, `write_failed:${error.point}`, {
49843
+ targetId: accepted.target_id,
49844
+ acceptedReceiptId: accepted.receipt_id
49845
+ });
49846
+ });
49847
+ const receipt = publicReceipt(terminal);
49848
+ assertWithinBounds(receipt, request, startedAt);
49849
+ return receipt;
49850
+ }
49851
+ }
49852
+ async verifyInverse(request) {
49853
+ const startedAt = Date.now();
49854
+ const accepted = assertInverseRequest(request, this.capabilityValue);
49855
+ await this.storedAcceptedReceipt(request, accepted);
49856
+ const receipt = await this.backend.getReceiptForLookup({
49857
+ ...authorityScope(this.capabilityValue),
49858
+ operation_id: request.operation_id,
49859
+ step_id: request.step_id,
49860
+ resource_kind: request.resource_kind,
49861
+ direction: "inverse",
49862
+ idempotency_key: request.idempotency_key,
49863
+ target_selector: request.target_selector
49864
+ });
49865
+ if (!receipt || receipt.outcome !== "accepted" || receipt.accepted_receipt_id !== accepted.receipt_id || receipt.result_revision !== "absent" || !receipt.result_digest) {
49866
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND", "accepted conditional inverse receipt was not found");
49867
+ }
49868
+ const object = request.resource_kind === "project" ? await this.backend.getProject(accepted.target_id) : await this.backend.getTaskList(accepted.target_id);
49869
+ if (object) {
49870
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "inverse verification found the accepted target still present");
49871
+ }
49872
+ const verification = {
49873
+ target_id: accepted.target_id,
49874
+ accepted_receipt_id: accepted.receipt_id,
49875
+ absent: true,
49876
+ digest: digestProjectRegistrationValue({
49877
+ target_id: accepted.target_id,
49878
+ accepted_receipt_id: accepted.receipt_id,
49879
+ absent: true
49880
+ })
49881
+ };
49882
+ assertWithinBounds(verification, request, startedAt);
49883
+ if (verification.digest !== receipt.result_digest) {
49884
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "inverse verification digest does not match the immutable receipt");
49885
+ }
49886
+ return verification;
49887
+ }
49888
+ }
49889
+ function createPostgresTodosProjectRegistrationAuthority(client, options = {}) {
49890
+ const {
49891
+ service,
49892
+ tableName,
49893
+ cursorTableName,
49894
+ ...authorityOptions
49895
+ } = options;
49896
+ return new PackageOwnedTodosProjectRegistrationAuthority(new PostgresTodosProjectRegistrationBackend(client, {
49897
+ service,
49898
+ tableName,
49899
+ cursorTableName
49900
+ }), authorityOptions);
49901
+ }
49902
+ var UUID_PATTERN, WORKSPACE_ID_PATTERN, OPERATION_PATTERN, STEP_PATTERN, SHA256_PATTERN, IDEMPOTENCY_PATTERN, WriteBoundaryError;
49903
+ var init_authority = __esm(() => {
49904
+ init_package_version();
49905
+ init_postgres2();
49906
+ init_sqlite();
49907
+ init_types4();
49908
+ 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;
49909
+ WORKSPACE_ID_PATTERN = /^wks_[A-Za-z0-9][A-Za-z0-9_-]{11,}$/;
49910
+ OPERATION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
49911
+ STEP_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$/;
49912
+ SHA256_PATTERN = /^[0-9a-f]{64}$/;
49913
+ IDEMPOTENCY_PATTERN = /^prk_[0-9a-f]{48}$/;
49914
+ WriteBoundaryError = class WriteBoundaryError extends Error {
49915
+ point;
49916
+ cause;
49917
+ constructor(point, cause) {
49918
+ super(`Todos project registration failed at ${point}`);
49919
+ this.point = point;
49920
+ this.cause = cause;
49921
+ }
49922
+ };
49923
+ });
49924
+
49925
+ // src/project-registration/http.ts
49926
+ function json(body, status = 200) {
49927
+ return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS });
49928
+ }
49929
+ function errorStatus(error) {
49930
+ switch (error.code) {
49931
+ case "TODOS_PROJECT_REGISTRATION_INVALID_INPUT":
49932
+ case "TODOS_PROJECT_REGISTRATION_INVALID_BOUNDS":
49933
+ case "TODOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH":
49934
+ case "TODOS_PROJECT_REGISTRATION_DIGEST_MISMATCH":
49935
+ case "TODOS_PROJECT_REGISTRATION_IDEMPOTENCY_MISMATCH":
49936
+ case "TODOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED":
49937
+ return 400;
49938
+ case "TODOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND":
49939
+ case "TODOS_PROJECT_REGISTRATION_RECORD_NOT_FOUND":
49940
+ case "TODOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND":
49941
+ return 404;
49942
+ case "TODOS_PROJECT_REGISTRATION_RESPONSE_TOO_LARGE":
49943
+ return 413;
49944
+ case "TODOS_PROJECT_REGISTRATION_TIME_BUDGET_EXCEEDED":
49945
+ return 408;
49946
+ case "TODOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE":
49947
+ return 503;
49948
+ default:
49949
+ return 409;
49950
+ }
49951
+ }
49952
+ async function readJson(req) {
49953
+ try {
49954
+ const value = await req.json();
49955
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
49956
+ } catch {
49957
+ return null;
49958
+ }
49959
+ }
49960
+ async function handleTodosProjectRegistrationHttpRequest(req, url, authority, basePath = "/v1/project-registration") {
49961
+ const path = url.pathname;
49962
+ if (path !== basePath && !path.startsWith(`${basePath}/`))
49963
+ return null;
49964
+ const action = path.slice(basePath.length).split("/").filter(Boolean).join("/");
49965
+ const method = req.method.toUpperCase();
49966
+ try {
49967
+ if ((action === "" || action === "capability") && method === "GET") {
49968
+ return json({ capability: await authority.capability() });
49969
+ }
49970
+ if (method !== "POST")
49971
+ return json({ error: "method not allowed" }, 405);
49972
+ const body = await readJson(req);
49973
+ if (!body) {
49974
+ return json({
49975
+ error: "invalid JSON body",
49976
+ code: "TODOS_PROJECT_REGISTRATION_INVALID_INPUT"
49977
+ }, 400);
49978
+ }
49979
+ if (action === "create") {
49980
+ return json({
49981
+ receipt: await authority.create(body)
49982
+ }, 201);
49983
+ }
49984
+ if (action === "receipts/lookup") {
49985
+ return json(await authority.lookupReceipt(body));
49986
+ }
49987
+ if (action === "read-exact") {
49988
+ return json({
49989
+ record: await authority.readExact(body)
49990
+ });
49991
+ }
49992
+ if (action === "compensate") {
49993
+ return json({
49994
+ receipt: await authority.compensate(body)
49995
+ }, 201);
49996
+ }
49997
+ if (action === "verify-inverse") {
49998
+ return json({
49999
+ verification: await authority.verifyInverse(body)
50000
+ });
50001
+ }
50002
+ return json({
50003
+ error: "unknown Todos project-registration route",
50004
+ code: "TODOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND"
50005
+ }, 404);
50006
+ } catch (cause) {
50007
+ if (cause instanceof TodosProjectRegistrationError) {
50008
+ return json({
50009
+ error: cause.message,
50010
+ code: cause.code,
50011
+ details: cause.details,
50012
+ authoritative: true
50013
+ }, errorStatus(cause));
50014
+ }
50015
+ return json({
50016
+ error: cause instanceof Error ? cause.message : "internal registration error",
50017
+ code: "TODOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE"
50018
+ }, 500);
50019
+ }
50020
+ }
50021
+ var JSON_HEADERS;
50022
+ var init_http2 = __esm(() => {
50023
+ init_types4();
50024
+ JSON_HEADERS = { "Content-Type": "application/json" };
50025
+ });
50026
+
50027
+ // src/project-registration/index.ts
50028
+ var init_project_registration = __esm(() => {
50029
+ init_authority();
50030
+ init_http2();
50031
+ init_postgres2();
50032
+ init_sqlite();
50033
+ init_types4();
50034
+ });
50035
+
47726
50036
  // src/storage/comment-redaction-backfill.ts
47727
50037
  function assertSafeIdentifier2(value) {
47728
50038
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
@@ -47831,6 +50141,7 @@ __export(exports_cloud, {
47831
50141
  isCloudModeEnabled: () => isCloudModeEnabled,
47832
50142
  getCloudVerifier: () => getCloudVerifier,
47833
50143
  getCloudStorageAdapter: () => getCloudStorageAdapter,
50144
+ getCloudProjectRegistrationAuthority: () => getCloudProjectRegistrationAuthority,
47834
50145
  getCloudPrGroupLedger: () => getCloudPrGroupLedger,
47835
50146
  getApiKeyStore: () => getApiKeyStore,
47836
50147
  ensureCloudTaskShortIdIndex: () => ensureCloudTaskShortIdIndex,
@@ -47879,6 +50190,17 @@ function getCloudPrGroupLedger() {
47879
50190
  cachedPrGroupLedger = new PrGroupLedger(new PostgresPrGroupLedgerPersistence(getClient()));
47880
50191
  return cachedPrGroupLedger;
47881
50192
  }
50193
+ function getCloudProjectRegistrationAuthority() {
50194
+ if (cachedProjectRegistrationAuthority)
50195
+ return cachedProjectRegistrationAuthority;
50196
+ cachedProjectRegistrationAuthority = createPostgresTodosProjectRegistrationAuthority(getClient(), {
50197
+ service: TODOS_APP_SLUG,
50198
+ authorityId: TODOS_APP_SLUG,
50199
+ tenantId: process.env.HASNA_TODOS_TENANT_ID ?? "default",
50200
+ corpusId: process.env.HASNA_TODOS_CORPUS_ID ?? `${TODOS_APP_SLUG}:postgresql`
50201
+ });
50202
+ return cachedProjectRegistrationAuthority;
50203
+ }
47882
50204
  function authClient() {
47883
50205
  const client = getClient();
47884
50206
  return {
@@ -47927,6 +50249,9 @@ async function ensureCloudSchema() {
47927
50249
  for (const sql of postgresPrGroupSchemaSql()) {
47928
50250
  await client.query(sql);
47929
50251
  }
50252
+ for (const sql of postgresTodosProjectRegistrationSchemaSql()) {
50253
+ await client.query(sql);
50254
+ }
47930
50255
  await getApiKeyStore().ensureSchema();
47931
50256
  })();
47932
50257
  return schemaEnsured;
@@ -47967,14 +50292,16 @@ async function closeCloud() {
47967
50292
  cachedStore = null;
47968
50293
  cachedVerifier = null;
47969
50294
  cachedPrGroupLedger = null;
50295
+ cachedProjectRegistrationAuthority = null;
47970
50296
  schemaEnsured = null;
47971
50297
  }
47972
- var TODOS_APP_SLUG = "todos", cachedClient = null, cachedAdapter = null, cachedStore = null, cachedVerifier = null, cachedPrGroupLedger = null, schemaEnsured = null;
50298
+ var TODOS_APP_SLUG = "todos", cachedClient = null, cachedAdapter = null, cachedStore = null, cachedVerifier = null, cachedPrGroupLedger = null, cachedProjectRegistrationAuthority = null, schemaEnsured = null;
47973
50299
  var init_cloud = __esm(() => {
47974
50300
  init_cloud_client();
47975
50301
  init_postgres_adapter();
47976
50302
  init_ledger();
47977
50303
  init_postgres();
50304
+ init_project_registration();
47978
50305
  init_postgres_sync();
47979
50306
  init_comment_redaction_backfill();
47980
50307
  });
@@ -48333,9 +50660,9 @@ function parseBoundedLimit(value, fallback, max) {
48333
50660
  return fallback;
48334
50661
  return Math.min(parsed, max);
48335
50662
  }
48336
- function mapTaskError(e, json2) {
50663
+ function mapTaskError(e, json3) {
48337
50664
  if (e instanceof VersionConflictError) {
48338
- return json2({
50665
+ return json3({
48339
50666
  error: e.message,
48340
50667
  code: VersionConflictError.code,
48341
50668
  expected_version: e.expectedVersion,
@@ -48343,23 +50670,23 @@ function mapTaskError(e, json2) {
48343
50670
  }, 409);
48344
50671
  }
48345
50672
  if (e instanceof TaskNotFoundError) {
48346
- return json2({ error: e.message, code: TaskNotFoundError.code }, 404);
50673
+ return json3({ error: e.message, code: TaskNotFoundError.code }, 404);
48347
50674
  }
48348
50675
  if (e instanceof LockError) {
48349
- return json2({ error: e.message, code: LockError.code }, 409);
50676
+ return json3({ error: e.message, code: LockError.code }, 409);
48350
50677
  }
48351
50678
  if (e instanceof CompletionGuardError) {
48352
- return json2({
50679
+ return json3({
48353
50680
  error: e.message,
48354
50681
  code: CompletionGuardError.code,
48355
50682
  retry_after: e.retryAfterSeconds ?? null
48356
50683
  }, 409);
48357
50684
  }
48358
50685
  if (e instanceof TaskNotStartableError) {
48359
- return json2({ error: e.message, code: TaskNotStartableError.code }, 409);
50686
+ return json3({ error: e.message, code: TaskNotStartableError.code }, 409);
48360
50687
  }
48361
50688
  if (e instanceof Error && / is blocked by /.test(e.message)) {
48362
- return json2({ error: e.message, code: "TASK_NOT_STARTABLE" }, 409);
50689
+ return json3({ error: e.message, code: "TASK_NOT_STARTABLE" }, 409);
48363
50690
  }
48364
50691
  return null;
48365
50692
  }
@@ -48444,11 +50771,11 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
48444
50771
  }
48445
50772
  });
48446
50773
  }
48447
- function handleHealth(_ctx, json2) {
50774
+ function handleHealth(_ctx, json3) {
48448
50775
  const stats2 = getTaskStats();
48449
50776
  const staleCount = getStaleTasks(30).length;
48450
50777
  const overdueRecurring = getOverdueTasks().filter((t) => t.recurrence_rule && t.status === "pending").length;
48451
- return json2({
50778
+ return json3({
48452
50779
  status: staleCount === 0 && overdueRecurring === 0 ? "ok" : "warn",
48453
50780
  tasks: stats2.total,
48454
50781
  stale: staleCount,
@@ -48456,18 +50783,18 @@ function handleHealth(_ctx, json2) {
48456
50783
  timestamp: new Date().toISOString()
48457
50784
  });
48458
50785
  }
48459
- function handleHeadlessBoundary(_ctx, json2) {
50786
+ function handleHeadlessBoundary(_ctx, json3) {
48460
50787
  const { getHeadlessBoundaryManifest: getHeadlessBoundaryManifest2 } = (init_headless_boundaries(), __toCommonJS(exports_headless_boundaries));
48461
- return json2(getHeadlessBoundaryManifest2());
50788
+ return json3(getHeadlessBoundaryManifest2());
48462
50789
  }
48463
- function handleStats(_ctx, json2) {
50790
+ function handleStats(_ctx, json3) {
48464
50791
  const stats2 = getTaskStats();
48465
50792
  const byStatus = stats2.by_status;
48466
50793
  const projects = listProjects();
48467
50794
  const agents = listAgents();
48468
50795
  const staleCount = getStaleTasks(30).length;
48469
50796
  const overdueRecurring = getOverdueTasks().filter((t) => t.recurrence_rule && t.status === "pending").length;
48470
- return json2({
50797
+ return json3({
48471
50798
  total_tasks: stats2.total,
48472
50799
  pending: byStatus["pending"] ?? 0,
48473
50800
  in_progress: byStatus["in_progress"] ?? 0,
@@ -48490,10 +50817,10 @@ function taskStatusQueryParam(url) {
48490
50817
  return { ok: false, message: result.message };
48491
50818
  return { ok: true, value: collapseEnumValues(result.values) };
48492
50819
  }
48493
- async function handleListTasks(_req, url, _ctx, json2, taskToSummary2) {
50820
+ async function handleListTasks(_req, url, _ctx, json3, taskToSummary2) {
48494
50821
  const statusParam = taskStatusQueryParam(url);
48495
50822
  if (!statusParam.ok)
48496
- return json2({ error: statusParam.message }, 400);
50823
+ return json3({ error: statusParam.message }, 400);
48497
50824
  const projectId = url.searchParams.get("project_id") || undefined;
48498
50825
  const sessionId = url.searchParams.get("session_id") || undefined;
48499
50826
  const agentId = url.searchParams.get("agent_id") || undefined;
@@ -48508,13 +50835,13 @@ async function handleListTasks(_req, url, _ctx, json2, taskToSummary2) {
48508
50835
  limit: limitParam ? parseInt(limitParam, 10) : undefined,
48509
50836
  offset: offsetParam ? parseInt(offsetParam, 10) : undefined
48510
50837
  });
48511
- return json2(tasks.map((t) => taskToSummary2(t, fields)));
50838
+ return json3(tasks.map((t) => taskToSummary2(t, fields)));
48512
50839
  }
48513
- async function handleCreateTask(req, ctx, json2, taskToSummary2) {
50840
+ async function handleCreateTask(req, ctx, json3, taskToSummary2) {
48514
50841
  try {
48515
50842
  const body = await req.json();
48516
50843
  if (!body.title)
48517
- return json2({ error: "Missing 'title'" }, 400);
50844
+ return json3({ error: "Missing 'title'" }, 400);
48518
50845
  const createdBy = body.created_by ?? body.agent_id ?? "dashboard";
48519
50846
  const task2 = createTask({
48520
50847
  title: body.title,
@@ -48526,19 +50853,19 @@ async function handleCreateTask(req, ctx, json2, taskToSummary2) {
48526
50853
  ...body.assigned_to ? { assigned_to: body.assigned_to } : {}
48527
50854
  });
48528
50855
  ctx.broadcastEvent({ type: "task", task_id: task2.id, action: "created", agent_id: task2.agent_id, project_id: task2.project_id });
48529
- return json2(taskToSummary2(task2), 201);
50856
+ return json3(taskToSummary2(task2), 201);
48530
50857
  } catch (e) {
48531
- return json2({ error: e instanceof Error ? e.message : "Failed to create task" }, 500);
50858
+ return json3({ error: e instanceof Error ? e.message : "Failed to create task" }, 500);
48532
50859
  }
48533
50860
  }
48534
- async function handleUpsertTask(req, ctx, json2, taskToSummary2) {
50861
+ async function handleUpsertTask(req, ctx, json3, taskToSummary2) {
48535
50862
  try {
48536
50863
  const body = await req.json();
48537
50864
  if (typeof body["fingerprint"] !== "string" || body["fingerprint"].trim() === "") {
48538
- return json2({ error: "Missing 'fingerprint'" }, 400);
50865
+ return json3({ error: "Missing 'fingerprint'" }, 400);
48539
50866
  }
48540
50867
  if (typeof body["title"] !== "string" || body["title"].trim() === "") {
48541
- return json2({ error: "Missing 'title'" }, 400);
50868
+ return json3({ error: "Missing 'title'" }, 400);
48542
50869
  }
48543
50870
  const metadata = body["metadata"] && typeof body["metadata"] === "object" && !Array.isArray(body["metadata"]) ? { ...body["metadata"] } : {};
48544
50871
  for (const key of ["expectation_id", "expectation_fingerprint", "evidence_paths", "origin_loop_id", "origin_run_id", "expected", "observed", "acceptance"]) {
@@ -48559,9 +50886,9 @@ async function handleUpsertTask(req, ctx, json2, taskToSummary2) {
48559
50886
  metadata
48560
50887
  });
48561
50888
  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 json2({ created: result.created, task: taskToSummary2(result.task) }, result.created ? 201 : 200);
50889
+ return json3({ created: result.created, task: taskToSummary2(result.task) }, result.created ? 201 : 200);
48563
50890
  } catch (e) {
48564
- return json2({ error: e instanceof Error ? e.message : "Failed to upsert task" }, 500);
50891
+ return json3({ error: e instanceof Error ? e.message : "Failed to upsert task" }, 500);
48565
50892
  }
48566
50893
  }
48567
50894
  function handleTasksExport(_req, url, _ctx, _json, taskToSummary2) {
@@ -48613,11 +50940,11 @@ function handleTasksExport(_req, url, _ctx, _json, taskToSummary2) {
48613
50940
  }
48614
50941
  });
48615
50942
  }
48616
- async function handleTasksBulk(req, _ctx, json2) {
50943
+ async function handleTasksBulk(req, _ctx, json3) {
48617
50944
  try {
48618
50945
  const body = await req.json();
48619
50946
  if (!body.ids?.length || !body.action)
48620
- return json2({ error: "Missing ids or action" }, 400);
50947
+ return json3({ error: "Missing ids or action" }, 400);
48621
50948
  const results = [];
48622
50949
  for (const id of body.ids) {
48623
50950
  try {
@@ -48635,66 +50962,66 @@ async function handleTasksBulk(req, _ctx, json2) {
48635
50962
  results.push({ id, success: false, error: e instanceof Error ? e.message : "Failed" });
48636
50963
  }
48637
50964
  }
48638
- return json2({ results, succeeded: results.filter((r) => r.success).length, failed: results.filter((r) => !r.success).length });
50965
+ return json3({ results, succeeded: results.filter((r) => r.success).length, failed: results.filter((r) => !r.success).length });
48639
50966
  } catch (e) {
48640
- return json2({ error: e instanceof Error ? e.message : "Failed" }, 500);
50967
+ return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
48641
50968
  }
48642
50969
  }
48643
- function handleTasksStatus(_req, url, _ctx, json2) {
50970
+ function handleTasksStatus(_req, url, _ctx, json3) {
48644
50971
  try {
48645
50972
  const projectId = url.searchParams.get("project_id") || undefined;
48646
50973
  const agentId = url.searchParams.get("agent_id") || undefined;
48647
50974
  const status = getStatus(projectId ? { project_id: projectId } : undefined, agentId);
48648
- return json2(status);
50975
+ return json3(status);
48649
50976
  } catch (e) {
48650
- return json2({ error: e instanceof Error ? e.message : "Failed" }, 500);
50977
+ return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
48651
50978
  }
48652
50979
  }
48653
- function handleTasksNext(_req, url, _ctx, json2, taskToSummary2) {
50980
+ function handleTasksNext(_req, url, _ctx, json3, taskToSummary2) {
48654
50981
  try {
48655
50982
  const projectId = url.searchParams.get("project_id") || undefined;
48656
50983
  const agentId = url.searchParams.get("agent_id") || undefined;
48657
50984
  const fields = parseFieldsParam(url);
48658
50985
  const task2 = getNextTask(agentId, projectId ? { project_id: projectId } : undefined);
48659
- return json2({ task: task2 ? taskToSummary2(task2, fields) : null });
50986
+ return json3({ task: task2 ? taskToSummary2(task2, fields) : null });
48660
50987
  } catch (e) {
48661
- return json2({ error: e instanceof Error ? e.message : "Failed" }, 500);
50988
+ return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
48662
50989
  }
48663
50990
  }
48664
- function handleTasksActive(_req, url, _ctx, json2) {
50991
+ function handleTasksActive(_req, url, _ctx, json3) {
48665
50992
  try {
48666
50993
  const projectId = url.searchParams.get("project_id") || undefined;
48667
50994
  const work = getActiveWork(projectId ? { project_id: projectId } : undefined);
48668
- return json2({ active: work, count: work.length });
50995
+ return json3({ active: work, count: work.length });
48669
50996
  } catch (e) {
48670
- return json2({ error: e instanceof Error ? e.message : "Failed" }, 500);
50997
+ return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
48671
50998
  }
48672
50999
  }
48673
- function handleTasksStale(_req, url, _ctx, json2, taskToSummary2) {
51000
+ function handleTasksStale(_req, url, _ctx, json3, taskToSummary2) {
48674
51001
  try {
48675
51002
  const projectId = url.searchParams.get("project_id") || undefined;
48676
51003
  const minutes2 = parseInt(url.searchParams.get("minutes") || "30", 10);
48677
51004
  const fields = parseFieldsParam(url);
48678
51005
  const tasks = getStaleTasks(minutes2, projectId ? { project_id: projectId } : undefined);
48679
- return json2({ tasks: tasks.map((t) => taskToSummary2(t, fields)), count: tasks.length });
51006
+ return json3({ tasks: tasks.map((t) => taskToSummary2(t, fields)), count: tasks.length });
48680
51007
  } catch (e) {
48681
- return json2({ error: e instanceof Error ? e.message : "Failed" }, 500);
51008
+ return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
48682
51009
  }
48683
51010
  }
48684
- function handleTasksChanged(_req, url, _ctx, json2, taskToSummary2) {
51011
+ function handleTasksChanged(_req, url, _ctx, json3, taskToSummary2) {
48685
51012
  try {
48686
51013
  const since = url.searchParams.get("since");
48687
51014
  if (!since)
48688
- return json2({ error: "since parameter required (ISO date string)" }, 400);
51015
+ return json3({ error: "since parameter required (ISO date string)" }, 400);
48689
51016
  const projectId = url.searchParams.get("project_id") || undefined;
48690
51017
  const fields = parseFieldsParam(url);
48691
51018
  const tasks = getTasksChangedSince(since, projectId ? { project_id: projectId } : undefined);
48692
- return json2({ tasks: tasks.map((t) => taskToSummary2(t, fields)), count: tasks.length, since });
51019
+ return json3({ tasks: tasks.map((t) => taskToSummary2(t, fields)), count: tasks.length, since });
48693
51020
  } catch (e) {
48694
- return json2({ error: e instanceof Error ? e.message : "Failed" }, 500);
51021
+ return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
48695
51022
  }
48696
51023
  }
48697
- function handleTasksContext(_req, url, _ctx, json2, taskToSummary2) {
51024
+ function handleTasksContext(_req, url, _ctx, json3, taskToSummary2) {
48698
51025
  const agentId = url.searchParams.get("agent_id") || undefined;
48699
51026
  const projectId = url.searchParams.get("project_id") || undefined;
48700
51027
  const format = url.searchParams.get("format") || "text";
@@ -48703,7 +51030,7 @@ function handleTasksContext(_req, url, _ctx, json2, taskToSummary2) {
48703
51030
  const status = getStatus(filters, agentId);
48704
51031
  const next = getNextTask(agentId, filters);
48705
51032
  if (format === "json") {
48706
- return json2({ status, next_task: next ? taskToSummary2(next, fields) : null });
51033
+ return json3({ status, next_task: next ? taskToSummary2(next, fields) : null });
48707
51034
  }
48708
51035
  const lines = [];
48709
51036
  lines.push(`Tasks: ${status.pending} pending | ${status.in_progress} active | ${status.completed} done`);
@@ -48720,18 +51047,18 @@ function handleTasksContext(_req, url, _ctx, json2, taskToSummary2) {
48720
51047
  `);
48721
51048
  return new Response(text2, { headers: { "Content-Type": "text/plain" } });
48722
51049
  }
48723
- function handleTaskAttachments(id, _ctx, json2) {
51050
+ function handleTaskAttachments(id, _ctx, json3) {
48724
51051
  const task2 = getTask(id);
48725
51052
  if (!task2)
48726
- return json2({ error: "Task not found" }, 404);
51053
+ return json3({ error: "Task not found" }, 404);
48727
51054
  const evidence = task2.metadata?._evidence || {};
48728
51055
  const attachmentIds = evidence.attachments || [];
48729
- return json2({ 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 });
51056
+ 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
51057
  }
48731
- async function handleTaskProgress(id, req, method, _ctx, json2, url) {
51058
+ async function handleTaskProgress(id, req, method, _ctx, json3, url) {
48732
51059
  const task2 = getTask(id);
48733
51060
  if (!task2)
48734
- return json2({ error: "Task not found" }, 404);
51061
+ return json3({ error: "Task not found" }, 404);
48735
51062
  if (method === "GET") {
48736
51063
  const all = listComments(id);
48737
51064
  const progress = all.filter((c) => c.type === "progress");
@@ -48739,7 +51066,7 @@ async function handleTaskProgress(id, req, method, _ctx, json2, url) {
48739
51066
  const format = url?.searchParams.get("format") || "compact";
48740
51067
  const limit = parseBoundedLimit(url?.searchParams.get("limit") || null, 20, 200);
48741
51068
  const progressEntries = format === "full" ? progress : progress.slice(-limit);
48742
- return json2({
51069
+ return json3({
48743
51070
  task_id: id,
48744
51071
  progress_entries: progressEntries,
48745
51072
  latest,
@@ -48756,27 +51083,27 @@ async function handleTaskProgress(id, req, method, _ctx, json2, url) {
48756
51083
  try {
48757
51084
  const body = await req.json();
48758
51085
  if (!body.message)
48759
- return json2({ error: "message required" }, 400);
51086
+ return json3({ error: "message required" }, 400);
48760
51087
  const comment = logProgress(id, body.message, body.pct_complete, body.agent_id);
48761
- return json2(comment, 201);
51088
+ return json3(comment, 201);
48762
51089
  } catch (e) {
48763
- return json2({ error: e instanceof Error ? e.message : "Failed to log progress" }, 500);
51090
+ return json3({ error: e instanceof Error ? e.message : "Failed to log progress" }, 500);
48764
51091
  }
48765
51092
  }
48766
51093
  return null;
48767
51094
  }
48768
- function handleGetTask(id, _ctx, json2, taskToSummary2, url) {
51095
+ function handleGetTask(id, _ctx, json3, taskToSummary2, url) {
48769
51096
  const task2 = getTask(id);
48770
51097
  if (!task2)
48771
- return json2({ error: "Task not found" }, 404);
48772
- return json2(taskToSummary2(task2, url ? parseFieldsParam(url) : undefined));
51098
+ return json3({ error: "Task not found" }, 404);
51099
+ return json3(taskToSummary2(task2, url ? parseFieldsParam(url) : undefined));
48773
51100
  }
48774
- async function handlePatchTask(id, req, _ctx, json2, taskToSummary2) {
51101
+ async function handlePatchTask(id, req, _ctx, json3, taskToSummary2) {
48775
51102
  try {
48776
51103
  const body = await req.json();
48777
51104
  const task2 = getTask(id);
48778
51105
  if (!task2)
48779
- return json2({ error: "Task not found" }, 404);
51106
+ return json3({ error: "Task not found" }, 404);
48780
51107
  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
51108
  const safeBody = {};
48782
51109
  for (const [key, value] of Object.entries(body)) {
@@ -48788,85 +51115,85 @@ async function handlePatchTask(id, req, _ctx, json2, taskToSummary2) {
48788
51115
  ...safeBody,
48789
51116
  version: clientVersion
48790
51117
  });
48791
- return json2(taskToSummary2(updated));
51118
+ return json3(taskToSummary2(updated));
48792
51119
  } catch (e) {
48793
- const mapped = mapTaskError(e, json2);
51120
+ const mapped = mapTaskError(e, json3);
48794
51121
  if (mapped)
48795
51122
  return mapped;
48796
- return json2({ error: e instanceof Error ? e.message : "Failed to update task" }, 500);
51123
+ return json3({ error: e instanceof Error ? e.message : "Failed to update task" }, 500);
48797
51124
  }
48798
51125
  }
48799
- function handleDeleteTask(id, _ctx, json2) {
51126
+ function handleDeleteTask(id, _ctx, json3) {
48800
51127
  const deleted = deleteTask(id);
48801
51128
  if (!deleted)
48802
- return json2({ error: "Task not found" }, 404);
48803
- return json2({ success: true });
51129
+ return json3({ error: "Task not found" }, 404);
51130
+ return json3({ success: true });
48804
51131
  }
48805
- function handleStartTask(id, ctx, json2, taskToSummary2) {
51132
+ function handleStartTask(id, ctx, json3, taskToSummary2) {
48806
51133
  try {
48807
51134
  const task2 = startTask(id, "dashboard");
48808
51135
  ctx.broadcastEvent({ type: "task", task_id: task2.id, action: "started", agent_id: "dashboard", project_id: task2.project_id });
48809
- return json2(taskToSummary2(task2));
51136
+ return json3(taskToSummary2(task2));
48810
51137
  } catch (e) {
48811
- const mapped = mapTaskError(e, json2);
51138
+ const mapped = mapTaskError(e, json3);
48812
51139
  if (mapped)
48813
51140
  return mapped;
48814
- return json2({ error: e instanceof Error ? e.message : "Failed to start task" }, 500);
51141
+ return json3({ error: e instanceof Error ? e.message : "Failed to start task" }, 500);
48815
51142
  }
48816
51143
  }
48817
- async function handleFailTask(id, req, ctx, json2, taskToSummary2) {
51144
+ async function handleFailTask(id, req, ctx, json3, taskToSummary2) {
48818
51145
  try {
48819
51146
  const body = await req.json().catch(() => ({}));
48820
51147
  const result = failTask(id, body.agent_id, body.reason, { retry: body.retry, error_code: body.error_code });
48821
51148
  ctx.broadcastEvent({ type: "task", task_id: id, action: "failed", agent_id: body.agent_id || null, project_id: result.task.project_id });
48822
- return json2({ task: taskToSummary2(result.task), retry_task: result.retryTask ? taskToSummary2(result.retryTask) : null });
51149
+ return json3({ task: taskToSummary2(result.task), retry_task: result.retryTask ? taskToSummary2(result.retryTask) : null });
48823
51150
  } catch (e) {
48824
- return json2({ error: e instanceof Error ? e.message : "Failed to fail task" }, 500);
51151
+ return json3({ error: e instanceof Error ? e.message : "Failed to fail task" }, 500);
48825
51152
  }
48826
51153
  }
48827
- function handleCompleteTask(id, ctx, json2, taskToSummary2) {
51154
+ function handleCompleteTask(id, ctx, json3, taskToSummary2) {
48828
51155
  try {
48829
51156
  const task2 = completeTask(id, "dashboard");
48830
51157
  ctx.broadcastEvent({ type: "task", task_id: task2.id, action: "completed", agent_id: "dashboard", project_id: task2.project_id });
48831
- return json2(taskToSummary2(task2));
51158
+ return json3(taskToSummary2(task2));
48832
51159
  } catch (e) {
48833
- const mapped = mapTaskError(e, json2);
51160
+ const mapped = mapTaskError(e, json3);
48834
51161
  if (mapped)
48835
51162
  return mapped;
48836
- return json2({ error: e instanceof Error ? e.message : "Failed to complete task" }, 500);
51163
+ return json3({ error: e instanceof Error ? e.message : "Failed to complete task" }, 500);
48837
51164
  }
48838
51165
  }
48839
- function handleListProjects(url, _ctx, json2) {
51166
+ function handleListProjects(url, _ctx, json3) {
48840
51167
  const pFieldsParam = url.searchParams.get("fields");
48841
51168
  const pFields = pFieldsParam ? pFieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
48842
51169
  const projects = listProjects();
48843
- return json2(pFields ? projects.map((p) => Object.fromEntries(pFields.map((f) => [f, p[f] ?? null]))) : projects);
51170
+ return json3(pFields ? projects.map((p) => Object.fromEntries(pFields.map((f) => [f, p[f] ?? null]))) : projects);
48844
51171
  }
48845
- async function handleCreateProject(req, _ctx, json2) {
51172
+ async function handleCreateProject(req, _ctx, json3) {
48846
51173
  try {
48847
51174
  const body = await req.json();
48848
51175
  if (!body.name || !body.path)
48849
- return json2({ error: "Missing name or path" }, 400);
51176
+ return json3({ error: "Missing name or path" }, 400);
48850
51177
  const project = createProject({ name: body.name, path: body.path, description: body.description });
48851
- return json2(project, 201);
51178
+ return json3(project, 201);
48852
51179
  } catch (e) {
48853
- return json2({ error: e instanceof Error ? e.message : "Failed to create project" }, 500);
51180
+ return json3({ error: e instanceof Error ? e.message : "Failed to create project" }, 500);
48854
51181
  }
48855
51182
  }
48856
- function handleDeleteProject(id, _ctx, json2) {
51183
+ function handleDeleteProject(id, _ctx, json3) {
48857
51184
  const deleted = deleteProject(id);
48858
51185
  if (!deleted)
48859
- return json2({ error: "Project not found" }, 404);
48860
- return json2({ success: true });
51186
+ return json3({ error: "Project not found" }, 404);
51187
+ return json3({ success: true });
48861
51188
  }
48862
- async function handleAgentMe(_req, url, _ctx, json2, taskToSummary2) {
51189
+ async function handleAgentMe(_req, url, _ctx, json3, taskToSummary2) {
48863
51190
  try {
48864
51191
  const name = url.searchParams.get("name");
48865
51192
  if (!name)
48866
- return json2({ error: "Missing name param" }, 400);
51193
+ return json3({ error: "Missing name param" }, 400);
48867
51194
  const agentResult = registerAgent({ name });
48868
51195
  if (isAgentConflict(agentResult))
48869
- return json2({ error: agentResult.message, conflict: true }, 409);
51196
+ return json3({ error: agentResult.message, conflict: true }, 409);
48870
51197
  const agent = agentResult;
48871
51198
  const tasks = listTasks({ assigned_to: agent.name });
48872
51199
  const agentIdTasks = listTasks({ agent_id: agent.id });
@@ -48874,7 +51201,7 @@ async function handleAgentMe(_req, url, _ctx, json2, taskToSummary2) {
48874
51201
  const pending = allTasks.filter((t) => t.status === "pending");
48875
51202
  const inProgress = allTasks.filter((t) => t.status === "in_progress");
48876
51203
  const completed = allTasks.filter((t) => t.status === "completed");
48877
- return json2({
51204
+ return json3({
48878
51205
  agent,
48879
51206
  pending_tasks: pending.map((t) => taskToSummary2(t)),
48880
51207
  in_progress_tasks: inProgress.map((t) => taskToSummary2(t)),
@@ -48888,132 +51215,132 @@ async function handleAgentMe(_req, url, _ctx, json2, taskToSummary2) {
48888
51215
  });
48889
51216
  } catch (e) {
48890
51217
  if (e instanceof InvalidAgentNameError)
48891
- return json2({ error: e.message, suggestions: e.suggestions }, 400);
48892
- return json2({ error: e instanceof Error ? e.message : "Failed to get agent profile" }, 500);
51218
+ return json3({ error: e.message, suggestions: e.suggestions }, 400);
51219
+ return json3({ error: e instanceof Error ? e.message : "Failed to get agent profile" }, 500);
48893
51220
  }
48894
51221
  }
48895
- function handleAgentQueue(agentId, _ctx, json2, taskToSummary2) {
51222
+ function handleAgentQueue(agentId, _ctx, json3, taskToSummary2) {
48896
51223
  const aliasSet = assignedToAliasSet(getDatabase(), agentId);
48897
51224
  const pending = listTasks({ status: "pending" });
48898
51225
  const queue = pending.filter((t) => aliasSet.has((t.assigned_to ?? "").toLowerCase()) || t.agent_id === agentId || !t.assigned_to && !t.locked_by);
48899
51226
  const order = { critical: 0, high: 1, medium: 2, low: 3 };
48900
51227
  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 json2(queue.map((t) => taskToSummary2(t)));
51228
+ return json3(queue.map((t) => taskToSummary2(t)));
48902
51229
  }
48903
- async function handleClaimTask(req, _ctx, json2, taskToSummary2) {
51230
+ async function handleClaimTask(req, _ctx, json3, taskToSummary2) {
48904
51231
  try {
48905
51232
  const body = await req.json();
48906
51233
  const agentId = body.agent_id || "anonymous";
48907
51234
  const task2 = claimNextTask(agentId, body.project_id ? { project_id: body.project_id } : undefined);
48908
- return json2({ task: task2 ? taskToSummary2(task2) : null });
51235
+ return json3({ task: task2 ? taskToSummary2(task2) : null });
48909
51236
  } catch (e) {
48910
- return json2({ error: e instanceof Error ? e.message : "Failed to claim" }, 500);
51237
+ return json3({ error: e instanceof Error ? e.message : "Failed to claim" }, 500);
48911
51238
  }
48912
51239
  }
48913
- function handleListOrgs(_ctx, json2) {
48914
- return json2(listOrgs());
51240
+ function handleListOrgs(_ctx, json3) {
51241
+ return json3(listOrgs());
48915
51242
  }
48916
- async function handleCreateOrg(req, _ctx, json2) {
51243
+ async function handleCreateOrg(req, _ctx, json3) {
48917
51244
  try {
48918
51245
  const body = await req.json();
48919
51246
  if (!body.name)
48920
- return json2({ error: "Missing name" }, 400);
48921
- return json2(createOrg(body), 201);
51247
+ return json3({ error: "Missing name" }, 400);
51248
+ return json3(createOrg(body), 201);
48922
51249
  } catch (e) {
48923
- return json2({ error: e instanceof Error ? e.message : "Failed" }, 500);
51250
+ return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
48924
51251
  }
48925
51252
  }
48926
- async function handleUpdateOrg(id, req, _ctx, json2) {
51253
+ async function handleUpdateOrg(id, req, _ctx, json3) {
48927
51254
  try {
48928
51255
  const body = await req.json();
48929
- return json2(updateOrg(id, body));
51256
+ return json3(updateOrg(id, body));
48930
51257
  } catch (e) {
48931
- return json2({ error: e instanceof Error ? e.message : "Failed" }, 500);
51258
+ return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
48932
51259
  }
48933
51260
  }
48934
- function handleDeleteOrg(id, _ctx, json2) {
51261
+ function handleDeleteOrg(id, _ctx, json3) {
48935
51262
  const deleted = deleteOrg(id);
48936
- return json2(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
51263
+ return json3(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
48937
51264
  }
48938
- function handleOrgChart(_ctx, json2) {
48939
- return json2(getOrgChart());
51265
+ function handleOrgChart(_ctx, json3) {
51266
+ return json3(getOrgChart());
48940
51267
  }
48941
- function handleAgentTeam(agentId, _ctx, json2) {
48942
- return json2(getDirectReports(decodeURIComponent(agentId)));
51268
+ function handleAgentTeam(agentId, _ctx, json3) {
51269
+ return json3(getDirectReports(decodeURIComponent(agentId)));
48943
51270
  }
48944
- function handleListAgents(url, _ctx, json2) {
51271
+ function handleListAgents(url, _ctx, json3) {
48945
51272
  const aFieldsParam = url.searchParams.get("fields");
48946
51273
  const aFields = aFieldsParam ? aFieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
48947
51274
  const agents = listAgents();
48948
- return json2(aFields ? agents.map((a) => Object.fromEntries(aFields.map((f) => [f, a[f] ?? null]))) : agents);
51275
+ return json3(aFields ? agents.map((a) => Object.fromEntries(aFields.map((f) => [f, a[f] ?? null]))) : agents);
48949
51276
  }
48950
- async function handleRegisterAgent(req, _ctx, json2) {
51277
+ async function handleRegisterAgent(req, _ctx, json3) {
48951
51278
  try {
48952
51279
  const body = await req.json();
48953
51280
  if (!body.name)
48954
- return json2({ error: "Missing name" }, 400);
51281
+ return json3({ error: "Missing name" }, 400);
48955
51282
  const result = registerAgent({ name: body.name, description: body.description, session_id: body.session_id, working_dir: body.working_dir });
48956
51283
  if (isAgentConflict(result))
48957
- return json2({ error: result.message, conflict: true }, 409);
48958
- return json2(result, 201);
51284
+ return json3({ error: result.message, conflict: true }, 409);
51285
+ return json3(result, 201);
48959
51286
  } catch (e) {
48960
51287
  if (e instanceof InvalidAgentNameError)
48961
- return json2({ error: e.message, suggestions: e.suggestions }, 400);
48962
- return json2({ error: e instanceof Error ? e.message : "Failed to register agent" }, 500);
51288
+ return json3({ error: e.message, suggestions: e.suggestions }, 400);
51289
+ return json3({ error: e instanceof Error ? e.message : "Failed to register agent" }, 500);
48963
51290
  }
48964
51291
  }
48965
- async function handleUpdateAgent(id, req, _ctx, json2) {
51292
+ async function handleUpdateAgent(id, req, _ctx, json3) {
48966
51293
  try {
48967
51294
  const body = await req.json();
48968
51295
  const agent = updateAgent(id, body);
48969
- return json2(agent);
51296
+ return json3(agent);
48970
51297
  } catch (e) {
48971
51298
  if (e instanceof InvalidAgentNameError)
48972
- return json2({ error: e.message, suggestions: e.suggestions }, 400);
48973
- return json2({ error: e instanceof Error ? e.message : "Failed to update agent" }, 500);
51299
+ return json3({ error: e.message, suggestions: e.suggestions }, 400);
51300
+ return json3({ error: e instanceof Error ? e.message : "Failed to update agent" }, 500);
48974
51301
  }
48975
51302
  }
48976
- function handleDeleteAgent(id, _ctx, json2) {
51303
+ function handleDeleteAgent(id, _ctx, json3) {
48977
51304
  const deleted = deleteAgent(id);
48978
51305
  if (!deleted)
48979
- return json2({ error: "Agent not found" }, 404);
48980
- return json2({ success: true });
51306
+ return json3({ error: "Agent not found" }, 404);
51307
+ return json3({ success: true });
48981
51308
  }
48982
- async function handleBulkDeleteAgents(req, _ctx, json2) {
51309
+ async function handleBulkDeleteAgents(req, _ctx, json3) {
48983
51310
  try {
48984
51311
  const body = await req.json();
48985
51312
  if (!body.ids?.length || body.action !== "delete")
48986
- return json2({ error: "Missing ids or invalid action" }, 400);
51313
+ return json3({ error: "Missing ids or invalid action" }, 400);
48987
51314
  let succeeded = 0;
48988
51315
  for (const id of body.ids) {
48989
51316
  if (deleteAgent(id))
48990
51317
  succeeded++;
48991
51318
  }
48992
- return json2({ succeeded, failed: body.ids.length - succeeded });
51319
+ return json3({ succeeded, failed: body.ids.length - succeeded });
48993
51320
  } catch (e) {
48994
- return json2({ error: e instanceof Error ? e.message : "Failed" }, 500);
51321
+ return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
48995
51322
  }
48996
51323
  }
48997
- async function handleBulkDeleteProjects(req, _ctx, json2) {
51324
+ async function handleBulkDeleteProjects(req, _ctx, json3) {
48998
51325
  try {
48999
51326
  const body = await req.json();
49000
51327
  if (!body.ids?.length || body.action !== "delete")
49001
- return json2({ error: "Missing ids or invalid action" }, 400);
51328
+ return json3({ error: "Missing ids or invalid action" }, 400);
49002
51329
  let succeeded = 0;
49003
51330
  for (const id of body.ids) {
49004
51331
  if (deleteProject(id))
49005
51332
  succeeded++;
49006
51333
  }
49007
- return json2({ succeeded, failed: body.ids.length - succeeded });
51334
+ return json3({ succeeded, failed: body.ids.length - succeeded });
49008
51335
  } catch (e) {
49009
- return json2({ error: e instanceof Error ? e.message : "Failed" }, 500);
51336
+ return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
49010
51337
  }
49011
51338
  }
49012
- function handleDoctor(_ctx, json2) {
51339
+ function handleDoctor(_ctx, json3) {
49013
51340
  const { runTodosDoctor: runTodosDoctor2 } = (init_doctor(), __toCommonJS(exports_doctor));
49014
- return json2(runTodosDoctor2({ apply: false }));
51341
+ return json3(runTodosDoctor2({ apply: false }));
49015
51342
  }
49016
- function handleReport(_req, url, _ctx, json2) {
51343
+ function handleReport(_req, url, _ctx, json3) {
49017
51344
  const days = parseInt(url.searchParams.get("days") || "7", 10);
49018
51345
  const projectId = url.searchParams.get("project_id") || undefined;
49019
51346
  const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
@@ -49029,62 +51356,62 @@ function handleReport(_req, url, _ctx, json2) {
49029
51356
  byDay[day] = (byDay[day] || 0) + 1;
49030
51357
  }
49031
51358
  const completionRate = changed.length > 0 ? Math.round(completed.length / changed.length * 100) : 0;
49032
- return json2({ days, period_since: since, total: all.length, stats: stats2, changed: changed.length, completed: completed.length, failed: failed.length, completion_rate: completionRate, by_day: byDay });
51359
+ 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
51360
  }
49034
- function handleActivity(_req, url, _ctx, json2) {
51361
+ function handleActivity(_req, url, _ctx, json3) {
49035
51362
  const limit = parseInt(url.searchParams.get("limit") || "50", 10);
49036
- return json2(getRecentActivity(limit));
51363
+ return json3(getRecentActivity(limit));
49037
51364
  }
49038
- function handleTaskHistory(id, _ctx, json2, url) {
51365
+ function handleTaskHistory(id, _ctx, json3, url) {
49039
51366
  const history = getTaskHistory(id);
49040
51367
  const format = url?.searchParams.get("format") || "compact";
49041
51368
  const limit = parseBoundedLimit(url?.searchParams.get("limit") || null, 20, 500);
49042
- return json2(format === "full" ? history : history.slice(0, limit));
51369
+ return json3(format === "full" ? history : history.slice(0, limit));
49043
51370
  }
49044
- function handleListWebhooks(_ctx, json2) {
49045
- return json2(listWebhooks());
51371
+ function handleListWebhooks(_ctx, json3) {
51372
+ return json3(listWebhooks());
49046
51373
  }
49047
- async function handleCreateWebhook(req, _ctx, json2) {
51374
+ async function handleCreateWebhook(req, _ctx, json3) {
49048
51375
  try {
49049
51376
  const body = await req.json();
49050
51377
  if (!body.url)
49051
- return json2({ error: "Missing url" }, 400);
49052
- return json2(createWebhook(body), 201);
51378
+ return json3({ error: "Missing url" }, 400);
51379
+ return json3(createWebhook(body), 201);
49053
51380
  } catch (e) {
49054
- return json2({ error: e instanceof Error ? e.message : "Failed" }, 500);
51381
+ return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
49055
51382
  }
49056
51383
  }
49057
- function handleDeleteWebhook(id, _ctx, json2) {
51384
+ function handleDeleteWebhook(id, _ctx, json3) {
49058
51385
  const deleted = deleteWebhook(id);
49059
- return json2(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
51386
+ return json3(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
49060
51387
  }
49061
- function handleListTemplates(_ctx, json2) {
49062
- return json2(listTemplates());
51388
+ function handleListTemplates(_ctx, json3) {
51389
+ return json3(listTemplates());
49063
51390
  }
49064
- async function handleCreateTemplate(req, _ctx, json2) {
51391
+ async function handleCreateTemplate(req, _ctx, json3) {
49065
51392
  try {
49066
51393
  const body = await req.json();
49067
51394
  if (!body.name || !body.title_pattern)
49068
- return json2({ error: "Missing name or title_pattern" }, 400);
49069
- return json2(createTemplate(body), 201);
51395
+ return json3({ error: "Missing name or title_pattern" }, 400);
51396
+ return json3(createTemplate(body), 201);
49070
51397
  } catch (e) {
49071
- return json2({ error: e instanceof Error ? e.message : "Failed" }, 500);
51398
+ return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
49072
51399
  }
49073
51400
  }
49074
- function handleDeleteTemplate(id, _ctx, json2) {
51401
+ function handleDeleteTemplate(id, _ctx, json3) {
49075
51402
  const deleted = deleteTemplate(id);
49076
- return json2(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
51403
+ return json3(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
49077
51404
  }
49078
- function handleListPlans(url, _ctx, json2) {
51405
+ function handleListPlans(url, _ctx, json3) {
49079
51406
  const projectId = url.searchParams.get("project_id") || undefined;
49080
51407
  const plans = listPlans(projectId);
49081
- return json2(plans);
51408
+ return json3(plans);
49082
51409
  }
49083
- async function handleCreatePlan(req, _ctx, json2) {
51410
+ async function handleCreatePlan(req, _ctx, json3) {
49084
51411
  try {
49085
51412
  const body = await req.json();
49086
51413
  if (!body.name)
49087
- return json2({ error: "Missing 'name'" }, 400);
51414
+ return json3({ error: "Missing 'name'" }, 400);
49088
51415
  const plan = createPlan({
49089
51416
  name: body.name,
49090
51417
  slug: body.slug,
@@ -49094,49 +51421,49 @@ async function handleCreatePlan(req, _ctx, json2) {
49094
51421
  agent_id: body.agent_id,
49095
51422
  status: body.status
49096
51423
  });
49097
- return json2(plan, 201);
51424
+ return json3(plan, 201);
49098
51425
  } catch (e) {
49099
- return json2({ error: e instanceof Error ? e.message : "Failed to create plan" }, 500);
51426
+ return json3({ error: e instanceof Error ? e.message : "Failed to create plan" }, 500);
49100
51427
  }
49101
51428
  }
49102
- async function handleBulkDeletePlans(req, _ctx, json2) {
51429
+ async function handleBulkDeletePlans(req, _ctx, json3) {
49103
51430
  try {
49104
51431
  const body = await req.json();
49105
51432
  if (!body.ids?.length || body.action !== "delete")
49106
- return json2({ error: "Missing ids or invalid action" }, 400);
51433
+ return json3({ error: "Missing ids or invalid action" }, 400);
49107
51434
  let succeeded = 0;
49108
51435
  for (const id of body.ids) {
49109
51436
  if (deletePlan(id))
49110
51437
  succeeded++;
49111
51438
  }
49112
- return json2({ succeeded, failed: body.ids.length - succeeded });
51439
+ return json3({ succeeded, failed: body.ids.length - succeeded });
49113
51440
  } catch (e) {
49114
- return json2({ error: e instanceof Error ? e.message : "Failed" }, 500);
51441
+ return json3({ error: e instanceof Error ? e.message : "Failed" }, 500);
49115
51442
  }
49116
51443
  }
49117
- function handleGetPlan(id, _ctx, json2, taskToSummary2) {
51444
+ function handleGetPlan(id, _ctx, json3, taskToSummary2) {
49118
51445
  const plan = getPlan(id);
49119
51446
  if (!plan)
49120
- return json2({ error: "Plan not found" }, 404);
51447
+ return json3({ error: "Plan not found" }, 404);
49121
51448
  const tasks = listTasks({ plan_id: id });
49122
- return json2({ ...plan, tasks: tasks.map((t) => taskToSummary2(t)) });
51449
+ return json3({ ...plan, tasks: tasks.map((t) => taskToSummary2(t)) });
49123
51450
  }
49124
- async function handleUpdatePlan(id, req, _ctx, json2) {
51451
+ async function handleUpdatePlan(id, req, _ctx, json3) {
49125
51452
  try {
49126
51453
  const body = await req.json();
49127
51454
  const plan = updatePlan(id, body);
49128
- return json2(plan);
51455
+ return json3(plan);
49129
51456
  } catch (e) {
49130
- return json2({ error: e instanceof Error ? e.message : "Failed to update plan" }, 500);
51457
+ return json3({ error: e instanceof Error ? e.message : "Failed to update plan" }, 500);
49131
51458
  }
49132
51459
  }
49133
- function handleDeletePlan(id, _ctx, json2) {
51460
+ function handleDeletePlan(id, _ctx, json3) {
49134
51461
  const deleted = deletePlan(id);
49135
51462
  if (!deleted)
49136
- return json2({ error: "Plan not found" }, 404);
49137
- return json2({ success: true });
51463
+ return json3({ error: "Plan not found" }, 404);
51464
+ return json3({ success: true });
49138
51465
  }
49139
- function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
51466
+ function handleStaticFiles(path, method, ctx, json3, serveStaticFile2) {
49140
51467
  if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
49141
51468
  return null;
49142
51469
  if (path !== "/") {
@@ -49144,7 +51471,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
49144
51471
  const resolvedFile = resolve16(filePath);
49145
51472
  const resolvedBase = resolve16(ctx.dashboardDir);
49146
51473
  if (!resolvedFile.startsWith(resolvedBase + sep3) && resolvedFile !== resolvedBase) {
49147
- return json2({ error: "Forbidden" }, 403);
51474
+ return json3({ error: "Forbidden" }, 403);
49148
51475
  }
49149
51476
  const res2 = serveStaticFile2(filePath);
49150
51477
  if (res2)
@@ -49194,6 +51521,9 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
49194
51521
  Task: taskSchema,
49195
51522
  Project: projectSchema,
49196
51523
  TaskList: taskListSchema,
51524
+ ProjectTaskListEnsureReceipt: projectTaskListEnsureReceiptSchema,
51525
+ ProjectTaskListEnsureResult: projectTaskListEnsureResultSchema,
51526
+ ProjectTaskListRollbackResult: projectTaskListRollbackResultSchema,
49197
51527
  TaskComment: taskCommentSchema,
49198
51528
  Plan: planSchema,
49199
51529
  Template: templateSchema,
@@ -49271,6 +51601,29 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
49271
51601
  name: { type: "string", minLength: 1 }
49272
51602
  }
49273
51603
  },
51604
+ ProjectTaskListEnsureApplyInput: {
51605
+ type: "object",
51606
+ additionalProperties: false,
51607
+ required: ["expected_project_revision"],
51608
+ properties: {
51609
+ expected_project_revision: { type: "string", minLength: 1 },
51610
+ idempotency_key: {
51611
+ type: "string",
51612
+ minLength: 8,
51613
+ maxLength: 128,
51614
+ pattern: "^[A-Za-z0-9._:-]+$"
51615
+ }
51616
+ }
51617
+ },
51618
+ ProjectTaskListRollbackInput: {
51619
+ type: "object",
51620
+ additionalProperties: false,
51621
+ required: ["receipt_id", "expected_task_list_revision"],
51622
+ properties: {
51623
+ receipt_id: { type: "string", minLength: 1 },
51624
+ expected_task_list_revision: { type: "string", minLength: 1 }
51625
+ }
51626
+ },
49274
51627
  ErrorResponse: {
49275
51628
  type: "object",
49276
51629
  required: ["error"],
@@ -50373,6 +52726,51 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
50373
52726
  responses: { "200": { content: { "application/json": { schema: { type: "object", properties: { deleted: { type: "boolean" }, id: { type: "string" } } } } } } }
50374
52727
  }
50375
52728
  },
52729
+ "/v1/projects/{id}/task-list/ensure": {
52730
+ get: {
52731
+ operationId: "planProjectTaskListEnsure",
52732
+ summary: "Plan a non-mutating repair of a project's declared task list",
52733
+ parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
52734
+ responses: {
52735
+ "200": { content: { "application/json": { schema: { $ref: "#/components/schemas/ProjectTaskListEnsureResult" } } } },
52736
+ "404": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
52737
+ "409": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
52738
+ }
52739
+ },
52740
+ post: {
52741
+ operationId: "ensureProjectTaskList",
52742
+ summary: "Idempotently create an existing project's declared task list",
52743
+ parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
52744
+ requestBody: {
52745
+ required: true,
52746
+ content: { "application/json": { schema: { $ref: "#/components/schemas/ProjectTaskListEnsureApplyInput" } } }
52747
+ },
52748
+ responses: {
52749
+ "200": { content: { "application/json": { schema: { $ref: "#/components/schemas/ProjectTaskListEnsureResult" } } } },
52750
+ "201": { content: { "application/json": { schema: { $ref: "#/components/schemas/ProjectTaskListEnsureResult" } } } },
52751
+ "400": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
52752
+ "404": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
52753
+ "409": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
52754
+ }
52755
+ }
52756
+ },
52757
+ "/v1/projects/{id}/task-list/rollback": {
52758
+ post: {
52759
+ operationId: "rollbackProjectTaskListEnsure",
52760
+ summary: "Conditionally remove an unchanged task list created by an accepted ensure receipt",
52761
+ parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
52762
+ requestBody: {
52763
+ required: true,
52764
+ content: { "application/json": { schema: { $ref: "#/components/schemas/ProjectTaskListRollbackInput" } } }
52765
+ },
52766
+ responses: {
52767
+ "200": { content: { "application/json": { schema: { $ref: "#/components/schemas/ProjectTaskListRollbackResult" } } } },
52768
+ "400": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
52769
+ "404": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
52770
+ "409": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
52771
+ }
52772
+ }
52773
+ },
50376
52774
  "/v1/projects/{id}/rename": {
50377
52775
  post: {
50378
52776
  operationId: "renameProject",
@@ -50654,7 +53052,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
50654
53052
  }
50655
53053
  };
50656
53054
  }
50657
- var taskSchema, projectSchema, taskListSchema, taskCommentSchema, planSchema, templateTaskSchema, templateSchema, templateVariableSchema, createTemplateTaskInputSchema;
53055
+ var taskSchema, projectSchema, taskListSchema, projectTaskListEnsureReceiptSchema, projectTaskListEnsureResultSchema, projectTaskListRollbackResultSchema, taskCommentSchema, planSchema, templateTaskSchema, templateSchema, templateVariableSchema, createTemplateTaskInputSchema;
50658
53056
  var init_openapi = __esm(() => {
50659
53057
  init_package_version();
50660
53058
  init_types();
@@ -50702,6 +53100,80 @@ var init_openapi = __esm(() => {
50702
53100
  updated_at: { type: "string" }
50703
53101
  }
50704
53102
  };
53103
+ projectTaskListEnsureReceiptSchema = {
53104
+ type: "object",
53105
+ additionalProperties: false,
53106
+ required: [
53107
+ "schema_version",
53108
+ "receipt_id",
53109
+ "idempotency_key",
53110
+ "project_id",
53111
+ "task_list_id",
53112
+ "slug",
53113
+ "created_by_operation",
53114
+ "result_revision",
53115
+ "result_digest",
53116
+ "rollback_supported",
53117
+ "created_at"
53118
+ ],
53119
+ properties: {
53120
+ schema_version: { type: "string", enum: ["todos.project-task-list-ensure.v1"] },
53121
+ receipt_id: { type: "string" },
53122
+ idempotency_key: { type: "string" },
53123
+ project_id: { type: "string" },
53124
+ task_list_id: { type: "string" },
53125
+ slug: { type: "string" },
53126
+ created_by_operation: { type: "boolean" },
53127
+ result_revision: { type: "string" },
53128
+ result_digest: { type: "string" },
53129
+ rollback_supported: { type: "boolean" },
53130
+ created_at: { type: "string", format: "date-time" }
53131
+ }
53132
+ };
53133
+ projectTaskListEnsureResultSchema = {
53134
+ type: "object",
53135
+ additionalProperties: false,
53136
+ required: ["mode", "action", "project", "task_list", "receipt"],
53137
+ properties: {
53138
+ mode: { type: "string", enum: ["plan", "apply"] },
53139
+ action: { type: "string", enum: ["would_create", "created", "already_present"] },
53140
+ project: { $ref: "#/components/schemas/Project" },
53141
+ task_list: {
53142
+ oneOf: [
53143
+ { $ref: "#/components/schemas/TaskList" },
53144
+ { type: "null" }
53145
+ ]
53146
+ },
53147
+ receipt: {
53148
+ oneOf: [
53149
+ { $ref: "#/components/schemas/ProjectTaskListEnsureReceipt" },
53150
+ { type: "null" }
53151
+ ]
53152
+ }
53153
+ }
53154
+ };
53155
+ projectTaskListRollbackResultSchema = {
53156
+ type: "object",
53157
+ additionalProperties: false,
53158
+ required: [
53159
+ "schema_version",
53160
+ "action",
53161
+ "project_id",
53162
+ "task_list_id",
53163
+ "accepted_receipt_id",
53164
+ "rollback_receipt_id",
53165
+ "removed_at"
53166
+ ],
53167
+ properties: {
53168
+ schema_version: { type: "string", enum: ["todos.project-task-list-ensure.v1"] },
53169
+ action: { type: "string", enum: ["removed"] },
53170
+ project_id: { type: "string" },
53171
+ task_list_id: { type: "string" },
53172
+ accepted_receipt_id: { type: "string" },
53173
+ rollback_receipt_id: { type: "string" },
53174
+ removed_at: { type: "string", format: "date-time" }
53175
+ }
53176
+ };
50705
53177
  taskCommentSchema = {
50706
53178
  type: "object",
50707
53179
  required: ["id", "task_id", "agent_id", "session_id", "content", "type", "progress_pct", "created_at"],
@@ -50805,10 +53277,10 @@ var exports_pr_groups = {};
50805
53277
  __export(exports_pr_groups, {
50806
53278
  handlePrGroupHttpRequest: () => handlePrGroupHttpRequest
50807
53279
  });
50808
- function json2(body, status = 200) {
50809
- return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS });
53280
+ function json3(body, status = 200) {
53281
+ return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS2 });
50810
53282
  }
50811
- function errorStatus(error) {
53283
+ function errorStatus2(error) {
50812
53284
  switch (error.code) {
50813
53285
  case "PR_GROUP_INVALID_INPUT":
50814
53286
  case "PR_GROUP_EXACT_HEAD_REQUIRED":
@@ -50822,7 +53294,7 @@ function errorStatus(error) {
50822
53294
  return 409;
50823
53295
  }
50824
53296
  }
50825
- async function readJson(req) {
53297
+ async function readJson2(req) {
50826
53298
  try {
50827
53299
  const value = await req.json();
50828
53300
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -50840,26 +53312,26 @@ async function handlePrGroupHttpRequest(req, url, ledger, basePath, principal) {
50840
53312
  const method = req.method.toUpperCase();
50841
53313
  try {
50842
53314
  if (!groupId && method === "POST" && action === undefined) {
50843
- return json2({ error: "unknown PR-group route", code: "PR_GROUP_NOT_FOUND" }, 404);
53315
+ return json3({ error: "unknown PR-group route", code: "PR_GROUP_NOT_FOUND" }, 404);
50844
53316
  }
50845
53317
  if (groupId === "admit" && !action) {
50846
53318
  if (method !== "POST")
50847
- return json2({ error: "method not allowed" }, 405);
50848
- const body = await readJson(req);
53319
+ return json3({ error: "method not allowed" }, 405);
53320
+ const body = await readJson2(req);
50849
53321
  if (!body)
50850
- return json2({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
50851
- return json2(await ledger.admit(body), 201);
53322
+ return json3({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
53323
+ return json3(await ledger.admit(body), 201);
50852
53324
  }
50853
53325
  if (!groupId)
50854
- return json2({ error: "PR group id is required", code: "PR_GROUP_INVALID_INPUT" }, 400);
53326
+ return json3({ error: "PR group id is required", code: "PR_GROUP_INVALID_INPUT" }, 400);
50855
53327
  if (!action && method === "GET") {
50856
- return json2({ view: await ledger.get(groupId) });
53328
+ return json3({ view: await ledger.get(groupId) });
50857
53329
  }
50858
53330
  if (action === "events") {
50859
53331
  if (method === "GET") {
50860
53332
  const limit = url.searchParams.has("limit") ? Number(url.searchParams.get("limit")) : undefined;
50861
53333
  const afterSequence = url.searchParams.has("after_sequence") ? Number(url.searchParams.get("after_sequence")) : undefined;
50862
- return json2({
53334
+ return json3({
50863
53335
  history: await ledger.events(groupId, {
50864
53336
  ...limit !== undefined ? { limit } : {},
50865
53337
  ...afterSequence !== undefined ? { after_sequence: afterSequence } : {}
@@ -50867,49 +53339,49 @@ async function handlePrGroupHttpRequest(req, url, ledger, basePath, principal) {
50867
53339
  });
50868
53340
  }
50869
53341
  if (method === "POST") {
50870
- const body = await readJson(req);
53342
+ const body = await readJson2(req);
50871
53343
  if (!body)
50872
- return json2({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
50873
- return json2(await ledger.append({
53344
+ return json3({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
53345
+ return json3(await ledger.append({
50874
53346
  ...body,
50875
53347
  group_id: groupId,
50876
53348
  authenticated_actor_id: principal?.actor_id ?? undefined,
50877
53349
  authenticated_actor_run_id: principal?.actor_run_id ?? undefined
50878
53350
  }), 201);
50879
53351
  }
50880
- return json2({ error: "method not allowed" }, 405);
53352
+ return json3({ error: "method not allowed" }, 405);
50881
53353
  }
50882
53354
  if (action === "recover") {
50883
53355
  if (method !== "POST")
50884
- return json2({ error: "method not allowed" }, 405);
50885
- const body = await readJson(req);
53356
+ return json3({ error: "method not allowed" }, 405);
53357
+ const body = await readJson2(req);
50886
53358
  if (!body)
50887
- return json2({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
50888
- return json2(await ledger.recover({
53359
+ return json3({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
53360
+ return json3(await ledger.recover({
50889
53361
  ...body,
50890
53362
  group_id: groupId
50891
53363
  }), 201);
50892
53364
  }
50893
- return json2({ error: "unknown PR-group route", code: "PR_GROUP_NOT_FOUND" }, 404);
53365
+ return json3({ error: "unknown PR-group route", code: "PR_GROUP_NOT_FOUND" }, 404);
50894
53366
  } catch (cause) {
50895
53367
  if (cause instanceof PrGroupLedgerError) {
50896
- return json2({
53368
+ return json3({
50897
53369
  error: cause.message,
50898
53370
  code: cause.code,
50899
53371
  details: cause.details,
50900
53372
  authoritative: true
50901
- }, errorStatus(cause));
53373
+ }, errorStatus2(cause));
50902
53374
  }
50903
- return json2({
53375
+ return json3({
50904
53376
  error: cause instanceof Error ? cause.message : "internal PR-group error",
50905
53377
  code: "PR_GROUP_ATOMICITY_UNAVAILABLE"
50906
53378
  }, 500);
50907
53379
  }
50908
53380
  }
50909
- var JSON_HEADERS;
53381
+ var JSON_HEADERS2;
50910
53382
  var init_pr_groups = __esm(() => {
50911
53383
  init_types3();
50912
- JSON_HEADERS = { "Content-Type": "application/json" };
53384
+ JSON_HEADERS2 = { "Content-Type": "application/json" };
50913
53385
  });
50914
53386
 
50915
53387
  // src/lib/comment-cursor.ts
@@ -50935,6 +53407,261 @@ function decodeCommentCursor(value) {
50935
53407
  }
50936
53408
  var MAX_COMMENT_CURSOR_LENGTH = 1024;
50937
53409
 
53410
+ // src/lib/project-task-list-ensure.ts
53411
+ import { createHash as createHash15 } from "crypto";
53412
+ function canonicalJson(value) {
53413
+ if (value === null || typeof value !== "object")
53414
+ return JSON.stringify(value);
53415
+ if (Array.isArray(value))
53416
+ return `[${value.map(canonicalJson).join(",")}]`;
53417
+ return `{${Object.entries(value).filter(([, item]) => item !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`).join(",")}}`;
53418
+ }
53419
+ function digest(value) {
53420
+ return createHash15("sha256").update(canonicalJson(value)).digest("hex");
53421
+ }
53422
+ function deriveIdempotencyKey(projectId, slug) {
53423
+ return `ptlk_${digest({ project_id: projectId, slug }).slice(0, 48)}`;
53424
+ }
53425
+ function normalizeIdempotencyKey(value, projectId, slug) {
53426
+ const key = value?.trim() || deriveIdempotencyKey(projectId, slug);
53427
+ if (key.length < 8 || key.length > 128 || !/^[A-Za-z0-9._:-]+$/.test(key)) {
53428
+ throw new ProjectTaskListEnsureError("PROJECT_TASK_LIST_IDEMPOTENCY_KEY_INVALID", "idempotency_key must be 8-128 ASCII letters, digits, dots, underscores, colons, or hyphens");
53429
+ }
53430
+ return key;
53431
+ }
53432
+ function receiptId2(projectId, slug, idempotencyKey) {
53433
+ return `ptlr_${digest({ project_id: projectId, slug, idempotency_key: idempotencyKey }).slice(0, 48)}`;
53434
+ }
53435
+ function semanticListDigest(list) {
53436
+ const metadata = { ...list.metadata ?? {} };
53437
+ delete metadata[RECEIPT_METADATA_KEY];
53438
+ return digest({
53439
+ project_id: list.project_id,
53440
+ slug: list.slug,
53441
+ name: list.name,
53442
+ description: list.description,
53443
+ metadata
53444
+ });
53445
+ }
53446
+ function storedMarker(list) {
53447
+ const value = list.metadata?.[RECEIPT_METADATA_KEY];
53448
+ if (!value || typeof value !== "object" || Array.isArray(value))
53449
+ return null;
53450
+ const marker = value;
53451
+ if (marker.schema_version !== PROJECT_TASK_LIST_ENSURE_SCHEMA_VERSION || typeof marker.receipt_id !== "string" || typeof marker.idempotency_key !== "string" || typeof marker.project_id !== "string" || typeof marker.slug !== "string" || typeof marker.result_digest !== "string" || typeof marker.created_at !== "string")
53452
+ return null;
53453
+ return marker;
53454
+ }
53455
+ function receiptFor(store, project, list, idempotencyKey) {
53456
+ const marker = storedMarker(list);
53457
+ const owned = marker?.project_id === project.id && marker.slug === list.slug;
53458
+ if (owned && marker.idempotency_key !== idempotencyKey) {
53459
+ throw new ProjectTaskListEnsureError("PROJECT_TASK_LIST_IDEMPOTENCY_CONFLICT", "The operation-owned task list was created under a different idempotency key", {
53460
+ project_id: project.id,
53461
+ task_list_id: list.id,
53462
+ receipt_id: marker.receipt_id
53463
+ });
53464
+ }
53465
+ return {
53466
+ schema_version: PROJECT_TASK_LIST_ENSURE_SCHEMA_VERSION,
53467
+ receipt_id: owned ? marker.receipt_id : `ptlr_existing_${digest({ project_id: project.id, task_list_id: list.id }).slice(0, 39)}`,
53468
+ idempotency_key: owned ? marker.idempotency_key : idempotencyKey,
53469
+ project_id: project.id,
53470
+ task_list_id: list.id,
53471
+ slug: list.slug,
53472
+ created_by_operation: owned,
53473
+ result_revision: list.updated_at,
53474
+ result_digest: owned ? marker.result_digest : semanticListDigest(list),
53475
+ rollback_supported: Boolean(owned && semanticListDigest(list) === marker.result_digest && store.taskLists.deleteIfUnchangedAndUnused),
53476
+ created_at: owned ? marker.created_at : list.created_at
53477
+ };
53478
+ }
53479
+ async function exactProjectState(store, projectId) {
53480
+ const project = await store.projects.get(projectId);
53481
+ if (!project) {
53482
+ throw new ProjectTaskListEnsureError("PROJECT_NOT_FOUND", `Project not found: ${projectId}`, { project_id: projectId });
53483
+ }
53484
+ const slug = project.task_list_id?.trim();
53485
+ if (!slug) {
53486
+ throw new ProjectTaskListEnsureError("PROJECT_TASK_LIST_NOT_DECLARED", "Project does not declare a canonical task_list_id slug", { project_id: project.id });
53487
+ }
53488
+ const all = await store.taskLists.list();
53489
+ const scopedMatches = all.filter((list) => list.project_id === project.id && list.slug === slug);
53490
+ if (scopedMatches.length > 1) {
53491
+ throw new ProjectTaskListEnsureError("TASK_LIST_SCOPE_COLLISION", "More than one task list matches the project's exact id and declared slug", { project_id: project.id, slug, task_list_ids: scopedMatches.map((list) => list.id) });
53492
+ }
53493
+ const globalMatches = all.filter((list) => list.project_id === null && list.slug === slug);
53494
+ if (globalMatches.length > 0 && scopedMatches.length === 0) {
53495
+ throw new ProjectTaskListEnsureError("TASK_LIST_SCOPE_COLLISION", "A legacy global task list already owns the declared slug; refusing to create a second locator", { project_id: project.id, slug, task_list_ids: globalMatches.map((list) => list.id) });
53496
+ }
53497
+ return { project, scoped: scopedMatches[0] ?? null, globalCollision: globalMatches[0] ?? null };
53498
+ }
53499
+ async function planProjectTaskListEnsure(store, projectId) {
53500
+ const { project, scoped } = await exactProjectState(store, projectId);
53501
+ return {
53502
+ mode: "plan",
53503
+ action: scoped ? "already_present" : "would_create",
53504
+ project,
53505
+ task_list: scoped,
53506
+ receipt: null
53507
+ };
53508
+ }
53509
+ async function applyProjectTaskListEnsure(store, projectId, options) {
53510
+ const state = await exactProjectState(store, projectId);
53511
+ const { project } = state;
53512
+ if (project.updated_at !== options.expected_project_revision) {
53513
+ throw new ProjectTaskListEnsureError("PROJECT_REVISION_CONFLICT", "Project changed after the ensure plan; fetch a fresh plan before applying", {
53514
+ project_id: project.id,
53515
+ expected_project_revision: options.expected_project_revision,
53516
+ current_project_revision: project.updated_at
53517
+ });
53518
+ }
53519
+ const slug = project.task_list_id;
53520
+ const idempotencyKey = normalizeIdempotencyKey(options.idempotency_key, project.id, slug);
53521
+ if (state.scoped) {
53522
+ return {
53523
+ mode: "apply",
53524
+ action: "already_present",
53525
+ project,
53526
+ task_list: state.scoped,
53527
+ receipt: receiptFor(store, project, state.scoped, idempotencyKey)
53528
+ };
53529
+ }
53530
+ const marker = {
53531
+ schema_version: PROJECT_TASK_LIST_ENSURE_SCHEMA_VERSION,
53532
+ receipt_id: receiptId2(project.id, slug, idempotencyKey),
53533
+ idempotency_key: idempotencyKey,
53534
+ project_id: project.id,
53535
+ slug,
53536
+ result_digest: semanticListDigest({
53537
+ project_id: project.id,
53538
+ slug,
53539
+ name: project.name,
53540
+ description: null,
53541
+ metadata: {}
53542
+ }),
53543
+ created_at: new Date().toISOString()
53544
+ };
53545
+ let list;
53546
+ try {
53547
+ list = await store.taskLists.create({
53548
+ name: project.name,
53549
+ slug,
53550
+ project_id: project.id,
53551
+ metadata: { [RECEIPT_METADATA_KEY]: marker }
53552
+ });
53553
+ } catch (error) {
53554
+ if (!(error instanceof ResourceConflictError))
53555
+ throw error;
53556
+ const raced = await exactProjectState(store, projectId);
53557
+ if (!raced.scoped)
53558
+ throw error;
53559
+ if (raced.project.updated_at !== options.expected_project_revision || raced.project.task_list_id !== slug) {
53560
+ throw new ProjectTaskListEnsureError("PROJECT_REVISION_CONFLICT", "Project changed while the task list was being created; fetch a fresh plan before retrying", {
53561
+ project_id: raced.project.id,
53562
+ expected_project_revision: options.expected_project_revision,
53563
+ current_project_revision: raced.project.updated_at
53564
+ });
53565
+ }
53566
+ return {
53567
+ mode: "apply",
53568
+ action: "already_present",
53569
+ project: raced.project,
53570
+ task_list: raced.scoped,
53571
+ receipt: receiptFor(store, raced.project, raced.scoped, idempotencyKey)
53572
+ };
53573
+ }
53574
+ const projectReadback = await store.projects.get(project.id);
53575
+ if (!projectReadback || projectReadback.updated_at !== options.expected_project_revision || projectReadback.task_list_id !== slug) {
53576
+ let compensated = false;
53577
+ const unchanged = await store.taskLists.get(list.id);
53578
+ const unchangedMarker = unchanged ? storedMarker(unchanged) : null;
53579
+ if (unchanged && unchangedMarker?.receipt_id === marker.receipt_id && semanticListDigest(unchanged) === marker.result_digest && store.taskLists.deleteIfUnchangedAndUnused) {
53580
+ const deletion = await store.taskLists.deleteIfUnchangedAndUnused(list.id, {
53581
+ project_id: unchanged.project_id,
53582
+ slug: unchanged.slug,
53583
+ name: unchanged.name,
53584
+ description: unchanged.description,
53585
+ metadata: unchanged.metadata,
53586
+ updated_at: unchanged.updated_at
53587
+ });
53588
+ compensated = deletion.status === "deleted";
53589
+ }
53590
+ throw new ProjectTaskListEnsureError("PROJECT_REVISION_CONFLICT", compensated ? "Project changed while the task list was being created; the new list was rolled back" : "Project changed while the task list was being created; the new list was retained because safe conditional rollback could not be proven", { project_id: project.id, task_list_id: list.id, compensated });
53591
+ }
53592
+ const readback = await store.taskLists.get(list.id);
53593
+ if (!readback || readback.project_id !== project.id || readback.slug !== slug) {
53594
+ throw new ProjectTaskListEnsureError("TASK_LIST_SCOPE_COLLISION", "Task-list create did not preserve the exact project id and declared slug", { project_id: project.id, task_list_id: list.id, slug });
53595
+ }
53596
+ return {
53597
+ mode: "apply",
53598
+ action: "created",
53599
+ project: projectReadback,
53600
+ task_list: readback,
53601
+ receipt: receiptFor(store, projectReadback, readback, idempotencyKey)
53602
+ };
53603
+ }
53604
+ async function rollbackProjectTaskListEnsure(store, projectId, options) {
53605
+ const conditionalDelete = store.taskLists.deleteIfUnchangedAndUnused;
53606
+ if (!conditionalDelete) {
53607
+ throw new ProjectTaskListEnsureError("PROJECT_TASK_LIST_ROLLBACK_CONFLICT", "This storage backend cannot guarantee atomic conditional rollback; refusing to delete", { project_id: projectId, receipt_id: options.receipt_id });
53608
+ }
53609
+ const project = await store.projects.get(projectId);
53610
+ if (!project) {
53611
+ throw new ProjectTaskListEnsureError("PROJECT_NOT_FOUND", `Project not found: ${projectId}`);
53612
+ }
53613
+ const candidates = (await store.taskLists.list(project.id)).filter((list2) => storedMarker(list2)?.receipt_id === options.receipt_id);
53614
+ if (candidates.length !== 1) {
53615
+ throw new ProjectTaskListEnsureError("PROJECT_TASK_LIST_RECEIPT_NOT_FOUND", "No exact operation-owned task list matches this rollback receipt", { project_id: project.id, receipt_id: options.receipt_id });
53616
+ }
53617
+ const list = candidates[0];
53618
+ const marker = storedMarker(list);
53619
+ if (marker.project_id !== project.id || marker.slug !== list.slug || list.project_id !== project.id || list.updated_at !== options.expected_task_list_revision || semanticListDigest(list) !== marker.result_digest) {
53620
+ throw new ProjectTaskListEnsureError("PROJECT_TASK_LIST_ROLLBACK_CONFLICT", "The operation-owned task list drifted; refusing conditional rollback", { project_id: project.id, task_list_id: list.id, receipt_id: options.receipt_id });
53621
+ }
53622
+ const deletion = await conditionalDelete.call(store.taskLists, list.id, {
53623
+ project_id: list.project_id,
53624
+ slug: list.slug,
53625
+ name: list.name,
53626
+ description: list.description,
53627
+ metadata: list.metadata,
53628
+ updated_at: list.updated_at
53629
+ });
53630
+ if (deletion.status === "has_dependents") {
53631
+ throw new ProjectTaskListEnsureError("PROJECT_TASK_LIST_ROLLBACK_HAS_DEPENDENTS", "The operation-owned task list has dependents; refusing conditional rollback", {
53632
+ task_list_id: list.id,
53633
+ task_dependents: deletion.task_dependents,
53634
+ plan_dependents: deletion.plan_dependents
53635
+ });
53636
+ }
53637
+ if (deletion.status !== "deleted" || await store.taskLists.get(list.id)) {
53638
+ throw new ProjectTaskListEnsureError("PROJECT_TASK_LIST_ROLLBACK_CONFLICT", "Conditional rollback did not remove the exact task list", { task_list_id: list.id });
53639
+ }
53640
+ return {
53641
+ schema_version: PROJECT_TASK_LIST_ENSURE_SCHEMA_VERSION,
53642
+ action: "removed",
53643
+ project_id: project.id,
53644
+ task_list_id: list.id,
53645
+ accepted_receipt_id: options.receipt_id,
53646
+ rollback_receipt_id: `ptlr_inverse_${digest({ accepted_receipt_id: options.receipt_id }).slice(0, 38)}`,
53647
+ removed_at: new Date().toISOString()
53648
+ };
53649
+ }
53650
+ var PROJECT_TASK_LIST_ENSURE_SCHEMA_VERSION = "todos.project-task-list-ensure.v1", RECEIPT_METADATA_KEY = "todos_project_task_list_ensure", ProjectTaskListEnsureError;
53651
+ var init_project_task_list_ensure = __esm(() => {
53652
+ init_types();
53653
+ ProjectTaskListEnsureError = class ProjectTaskListEnsureError extends Error {
53654
+ code;
53655
+ details;
53656
+ constructor(code, message, details = {}) {
53657
+ super(message);
53658
+ this.code = code;
53659
+ this.details = details;
53660
+ this.name = "ProjectTaskListEnsureError";
53661
+ }
53662
+ };
53663
+ });
53664
+
50938
53665
  // src/server/v1.ts
50939
53666
  var exports_v1 = {};
50940
53667
  __export(exports_v1, {
@@ -50942,11 +53669,11 @@ __export(exports_v1, {
50942
53669
  handleV1Request: () => handleV1Request,
50943
53670
  countSnapshotRecords: () => countSnapshotRecords
50944
53671
  });
50945
- function json3(body, status = 200) {
50946
- return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS2 });
53672
+ function json4(body, status = 200) {
53673
+ return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS3 });
50947
53674
  }
50948
53675
  function error(status, message, extra) {
50949
- return json3({ error: message, ...extra ?? {} }, status);
53676
+ return json4({ error: message, ...extra ?? {} }, status);
50950
53677
  }
50951
53678
  function enumQueryParam(url, name, vocabulary) {
50952
53679
  const raw = url.searchParams.get(name);
@@ -51197,7 +53924,7 @@ function validateTemplatePatch(value) {
51197
53924
  ...body.plan_id === null ? { plan_id: null } : {}
51198
53925
  } };
51199
53926
  }
51200
- async function readJson2(req) {
53927
+ async function readJson3(req) {
51201
53928
  try {
51202
53929
  const text2 = await req.text();
51203
53930
  if (!text2)
@@ -51267,6 +53994,9 @@ async function handleV1Request(req, url, dependencies = {}) {
51267
53994
  if (path === "/v1/pr-groups" || path.startsWith("/v1/pr-groups/")) {
51268
53995
  return handlePrGroupHttpRequest(req, url, (dependencies.getPrGroupLedger ?? getCloudPrGroupLedger)(), "/v1/pr-groups", { actor_id: principal.agent, actor_run_id: principal.kid });
51269
53996
  }
53997
+ if (path === "/v1/project-registration" || path.startsWith("/v1/project-registration/")) {
53998
+ return handleTodosProjectRegistrationHttpRequest(req, url, (dependencies.getProjectRegistrationAuthority ?? getCloudProjectRegistrationAuthority)());
53999
+ }
51270
54000
  const store = (dependencies.getStorageAdapter ?? getCloudStorageAdapter)();
51271
54001
  const segments = path.split("/").filter(Boolean);
51272
54002
  const resource = segments[1];
@@ -51278,7 +54008,7 @@ async function handleV1Request(req, url, dependencies = {}) {
51278
54008
  if (id === "exists" && !action) {
51279
54009
  if (method !== "POST")
51280
54010
  return error(405, `method ${method} not allowed on /v1/tasks/exists`);
51281
- const body = await readJson2(req);
54011
+ const body = await readJson3(req);
51282
54012
  const ids2 = Array.isArray(body?.ids) ? Array.from(new Set(body.ids.filter((v) => typeof v === "string" && v.length > 0))) : [];
51283
54013
  if (ids2.length === 0)
51284
54014
  return error(400, "provide a non-empty string array `ids`");
@@ -51288,7 +54018,7 @@ async function handleV1Request(req, url, dependencies = {}) {
51288
54018
  const presentSet = new Set(found.map((t) => t.id));
51289
54019
  const present = ids2.filter((i) => presentSet.has(i));
51290
54020
  const missing = ids2.filter((i) => !presentSet.has(i));
51291
- return json3({
54021
+ return json4({
51292
54022
  requested: ids2.length,
51293
54023
  present_count: present.length,
51294
54024
  missing_count: missing.length,
@@ -51301,7 +54031,7 @@ async function handleV1Request(req, url, dependencies = {}) {
51301
54031
  if (typeof store.tasks.getByFingerprint !== "function") {
51302
54032
  return error(501, "fingerprint upsert is not supported by this storage backend");
51303
54033
  }
51304
- const body = await readJson2(req) ?? {};
54034
+ const body = await readJson3(req) ?? {};
51305
54035
  const fingerprint3 = typeof body.fingerprint === "string" ? body.fingerprint.trim() : "";
51306
54036
  if (!fingerprint3)
51307
54037
  return error(400, "fingerprint is required");
@@ -51338,11 +54068,11 @@ async function handleV1Request(req, url, dependencies = {}) {
51338
54068
  }
51339
54069
  if (!existing) {
51340
54070
  const task2 = await store.tasks.create({ ...fields, title: body.title }, contextFromPrincipal(principal, body));
51341
- return json3({ task: task2, created: true }, 201);
54071
+ return json4({ task: task2, created: true }, 201);
51342
54072
  }
51343
54073
  try {
51344
54074
  const task2 = await store.tasks.update(existing.id, { ...fields, version: existing.version }, contextFromPrincipal(principal, body));
51345
- return json3({ task: task2, created: false });
54075
+ return json4({ task: task2, created: false });
51346
54076
  } catch (e) {
51347
54077
  const msg = e.message || "";
51348
54078
  if (msg.includes("version conflict"))
@@ -51384,15 +54114,15 @@ async function handleV1Request(req, url, dependencies = {}) {
51384
54114
  const tasks = await store.tasks.list(filter);
51385
54115
  const { limit: _l, offset: _o, ...countFilter } = filter;
51386
54116
  const total = await store.tasks.count(countFilter);
51387
- return json3({ tasks, count: tasks.length, total });
54117
+ return json4({ tasks, count: tasks.length, total });
51388
54118
  }
51389
54119
  if (method === "POST") {
51390
- const body = await readJson2(req);
54120
+ const body = await readJson3(req);
51391
54121
  if (!body || typeof body.title !== "string" || !body.title.trim()) {
51392
54122
  return error(400, "title is required");
51393
54123
  }
51394
54124
  const task2 = await store.tasks.create(body, contextFromPrincipal(principal, body));
51395
- return json3({ task: task2 }, 201);
54125
+ return json4({ task: task2 }, 201);
51396
54126
  }
51397
54127
  return error(405, `method ${method} not allowed on /v1/tasks`);
51398
54128
  }
@@ -51409,7 +54139,7 @@ async function handleV1Request(req, url, dependencies = {}) {
51409
54139
  if (legacyPage.length > LEGACY_COMMENT_RESPONSE_LIMIT) {
51410
54140
  return error(426, "task has too many comments for this client; upgrade @hasna/todos to use cursor pagination");
51411
54141
  }
51412
- return json3({
54142
+ return json4({
51413
54143
  comments: legacyPage,
51414
54144
  count: legacyPage.length,
51415
54145
  has_more: false,
@@ -51434,7 +54164,7 @@ async function handleV1Request(req, url, dependencies = {}) {
51434
54164
  const page = (await store.audit.getCommentsPage(id, { limit: limit + 1, ...before ? { before } : {} }, contextFromPrincipal(principal))).map(redactComment3);
51435
54165
  const hasMore = page.length > limit;
51436
54166
  const comments = hasMore ? page.slice(1) : page;
51437
- return json3({
54167
+ return json4({
51438
54168
  comments,
51439
54169
  count: comments.length,
51440
54170
  has_more: hasMore,
@@ -51442,7 +54172,7 @@ async function handleV1Request(req, url, dependencies = {}) {
51442
54172
  });
51443
54173
  }
51444
54174
  if (method === "POST") {
51445
- const body2 = await readJson2(req) ?? {};
54175
+ const body2 = await readJson3(req) ?? {};
51446
54176
  if (typeof body2.content !== "string" || !body2.content.trim()) {
51447
54177
  return error(400, "content is required");
51448
54178
  }
@@ -51457,7 +54187,7 @@ async function handleV1Request(req, url, dependencies = {}) {
51457
54187
  type: body2.type,
51458
54188
  progress_pct: body2.progress_pct
51459
54189
  }, contextFromPrincipal(principal, body2));
51460
- return json3({ comment: redactComment3(comment) }, 201);
54190
+ return json4({ comment: redactComment3(comment) }, 201);
51461
54191
  }
51462
54192
  return error(405, `method ${method} not allowed on /v1/tasks/:id/comments`);
51463
54193
  }
@@ -51467,19 +54197,19 @@ async function handleV1Request(req, url, dependencies = {}) {
51467
54197
  if (!await store.tasks.get(id))
51468
54198
  return error(404, "task not found");
51469
54199
  const history = await store.audit.getTaskHistory(id);
51470
- return json3({ history, count: history.length });
54200
+ return json4({ history, count: history.length });
51471
54201
  }
51472
54202
  if (action === "lock" || action === "unlock") {
51473
54203
  if (method !== "POST")
51474
54204
  return error(405, `method ${method} not allowed on /v1/tasks/:id/${action}`);
51475
- const body2 = await readJson2(req) ?? {};
54205
+ const body2 = await readJson3(req) ?? {};
51476
54206
  if (!await store.tasks.get(id))
51477
54207
  return error(404, "task not found");
51478
54208
  if (action === "lock") {
51479
54209
  if (typeof store.tasks.lock !== "function")
51480
54210
  return error(501, "task locking is not supported by this storage backend");
51481
54211
  const agentId3 = body2.agent_id || principal.agent || "todos-serve";
51482
- return json3({ result: await store.tasks.lock(id, agentId3) });
54212
+ return json4({ result: await store.tasks.lock(id, agentId3) });
51483
54213
  }
51484
54214
  if (typeof store.tasks.unlock !== "function")
51485
54215
  return error(501, "task unlocking is not supported by this storage backend");
@@ -51487,7 +54217,7 @@ async function handleV1Request(req, url, dependencies = {}) {
51487
54217
  if (!principal.scopes.includes("todos:*"))
51488
54218
  return error(403, "force unlock requires todos:* scope");
51489
54219
  const released2 = await store.tasks.unlock(id);
51490
- return json3({ success: released2 });
54220
+ return json4({ success: released2 });
51491
54221
  }
51492
54222
  if (body2.agent_id && body2.agent_id !== principal.agent && !principal.scopes.includes("todos:*")) {
51493
54223
  return error(403, "unlock agent_id must match the authenticated agent");
@@ -51496,7 +54226,7 @@ async function handleV1Request(req, url, dependencies = {}) {
51496
54226
  if (!agentId2)
51497
54227
  return error(403, "unlock requires an agent-bound key or force=true");
51498
54228
  const released = await store.tasks.unlock(id, agentId2);
51499
- return json3({ success: released });
54229
+ return json4({ success: released });
51500
54230
  }
51501
54231
  if (action === "dependencies") {
51502
54232
  if (!store.dependencies)
@@ -51505,16 +54235,16 @@ async function handleV1Request(req, url, dependencies = {}) {
51505
54235
  if (!await store.tasks.get(id))
51506
54236
  return error(404, "task not found");
51507
54237
  const edges = await store.dependencies.list(id);
51508
- return json3(edges);
54238
+ return json4(edges);
51509
54239
  }
51510
54240
  if (method === "POST") {
51511
- const body2 = await readJson2(req) ?? {};
54241
+ const body2 = await readJson3(req) ?? {};
51512
54242
  if (typeof body2.depends_on !== "string" || !body2.depends_on.trim()) {
51513
54243
  return error(400, "depends_on is required");
51514
54244
  }
51515
54245
  try {
51516
54246
  const dependency = await store.dependencies.add(id, body2.depends_on, contextFromPrincipal(principal));
51517
- return json3({ dependency }, 201);
54247
+ return json4({ dependency }, 201);
51518
54248
  } catch (e) {
51519
54249
  const msg = e.message || "";
51520
54250
  if (msg.includes("not found"))
@@ -51528,7 +54258,7 @@ async function handleV1Request(req, url, dependencies = {}) {
51528
54258
  if (!subId)
51529
54259
  return error(400, "dependency target id is required (/v1/tasks/:id/dependencies/:dep)");
51530
54260
  const removed = await store.dependencies.remove(id, subId);
51531
- return json3({ removed });
54261
+ return json4({ removed });
51532
54262
  }
51533
54263
  return error(405, `method ${method} not allowed on /v1/tasks/:id/dependencies`);
51534
54264
  }
@@ -51539,10 +54269,10 @@ async function handleV1Request(req, url, dependencies = {}) {
51539
54269
  if (!await store.tasks.get(id))
51540
54270
  return error(404, "task not found");
51541
54271
  const verifications = await store.verifications.list(id);
51542
- return json3({ verifications, count: verifications.length });
54272
+ return json4({ verifications, count: verifications.length });
51543
54273
  }
51544
54274
  if (method === "POST") {
51545
- const body2 = await readJson2(req) ?? {};
54275
+ const body2 = await readJson3(req) ?? {};
51546
54276
  if (typeof body2.command !== "string" || !body2.command.trim()) {
51547
54277
  return error(400, "command is required");
51548
54278
  }
@@ -51555,7 +54285,7 @@ async function handleV1Request(req, url, dependencies = {}) {
51555
54285
  artifact_path: body2.artifact_path,
51556
54286
  agent_id: body2.agent_id
51557
54287
  }, contextFromPrincipal(principal, body2));
51558
- return json3({ verification }, 201);
54288
+ return json4({ verification }, 201);
51559
54289
  } catch (e) {
51560
54290
  const msg = e.message || "";
51561
54291
  if (msg.includes("not found"))
@@ -51572,10 +54302,10 @@ async function handleV1Request(req, url, dependencies = {}) {
51572
54302
  if (!await store.tasks.get(id))
51573
54303
  return error(404, "task not found");
51574
54304
  const commits = await store.commits.list(id);
51575
- return json3({ commits, count: commits.length });
54305
+ return json4({ commits, count: commits.length });
51576
54306
  }
51577
54307
  if (method === "POST") {
51578
- const body2 = await readJson2(req) ?? {};
54308
+ const body2 = await readJson3(req) ?? {};
51579
54309
  if (typeof body2.sha !== "string" || !body2.sha.trim())
51580
54310
  return error(400, "sha is required");
51581
54311
  try {
@@ -51586,7 +54316,7 @@ async function handleV1Request(req, url, dependencies = {}) {
51586
54316
  author: body2.author,
51587
54317
  files_changed: Array.isArray(body2.files_changed) ? body2.files_changed : undefined
51588
54318
  }, contextFromPrincipal(principal));
51589
- return json3({ commit }, 201);
54319
+ return json4({ commit }, 201);
51590
54320
  } catch (e) {
51591
54321
  const msg = e.message || "";
51592
54322
  if (msg.includes("not found"))
@@ -51603,10 +54333,10 @@ async function handleV1Request(req, url, dependencies = {}) {
51603
54333
  if (!await store.tasks.get(id))
51604
54334
  return error(404, "task not found");
51605
54335
  const refs = await store.gitRefs.list(id);
51606
- return json3({ refs, count: refs.length });
54336
+ return json4({ refs, count: refs.length });
51607
54337
  }
51608
54338
  if (method === "POST") {
51609
- const body2 = await readJson2(req) ?? {};
54339
+ const body2 = await readJson3(req) ?? {};
51610
54340
  const refType = body2.ref_type === "pull_request" || body2.ref_type === "branch" ? body2.ref_type : "branch";
51611
54341
  if (typeof body2.name !== "string" || !body2.name.trim())
51612
54342
  return error(400, "name is required");
@@ -51619,7 +54349,7 @@ async function handleV1Request(req, url, dependencies = {}) {
51619
54349
  provider: body2.provider,
51620
54350
  metadata: body2.metadata
51621
54351
  }, contextFromPrincipal(principal));
51622
- return json3({ ref }, 201);
54352
+ return json4({ ref }, 201);
51623
54353
  } catch (e) {
51624
54354
  const msg = e.message || "";
51625
54355
  if (msg.includes("not found"))
@@ -51635,21 +54365,21 @@ async function handleV1Request(req, url, dependencies = {}) {
51635
54365
  const body = actionJson.value && typeof actionJson.value === "object" && !Array.isArray(actionJson.value) ? actionJson.value : {};
51636
54366
  const agentId = typeof body.agent_id === "string" ? body.agent_id : principal.agent || "todos-serve";
51637
54367
  if (action === "start" && method === "POST") {
51638
- return json3({ task: await store.tasks.start(id, agentId) });
54368
+ return json4({ task: await store.tasks.start(id, agentId) });
51639
54369
  }
51640
54370
  if (action === "complete" && method === "POST") {
51641
54371
  const parsed = validateTaskCompletion(actionJson.value);
51642
54372
  if (!parsed.ok)
51643
54373
  return error(400, parsed.message);
51644
- return json3({
54374
+ return json4({
51645
54375
  task: await store.tasks.complete(id, parsed.agentId || principal.agent || "todos-serve", parsed.options, contextFromPrincipal(principal, body))
51646
54376
  });
51647
54377
  }
51648
54378
  if (action === "fail" && method === "POST") {
51649
- return json3({ result: await store.tasks.fail(id, agentId, typeof body.reason === "string" ? body.reason : "failed", {}) });
54379
+ return json4({ result: await store.tasks.fail(id, agentId, typeof body.reason === "string" ? body.reason : "failed", {}) });
51650
54380
  }
51651
54381
  if (action === "claim" && method === "POST") {
51652
- return json3({ task: await store.tasks.claimNext(agentId, {}) });
54382
+ return json4({ task: await store.tasks.claimNext(agentId, {}) });
51653
54383
  }
51654
54384
  return error(404, `unknown task action: ${action}`);
51655
54385
  }
@@ -51672,10 +54402,10 @@ async function handleV1Request(req, url, dependencies = {}) {
51672
54402
  throw e;
51673
54403
  }
51674
54404
  }
51675
- return task2 ? json3({ task: task2 }) : error(404, "task not found");
54405
+ return task2 ? json4({ task: task2 }) : error(404, "task not found");
51676
54406
  }
51677
54407
  if (method === "PATCH" || method === "PUT") {
51678
- const body = await readJson2(req);
54408
+ const body = await readJson3(req);
51679
54409
  if (!body)
51680
54410
  return error(400, "invalid JSON body");
51681
54411
  const current = await store.tasks.get(id);
@@ -51687,7 +54417,7 @@ async function handleV1Request(req, url, dependencies = {}) {
51687
54417
  };
51688
54418
  try {
51689
54419
  const task2 = await store.tasks.update(id, patch);
51690
- return task2 ? json3({ task: task2 }) : error(404, "task not found");
54420
+ return task2 ? json4({ task: task2 }) : error(404, "task not found");
51691
54421
  } catch (e) {
51692
54422
  const msg = e.message || "";
51693
54423
  if (msg.includes("version conflict"))
@@ -51697,7 +54427,7 @@ async function handleV1Request(req, url, dependencies = {}) {
51697
54427
  }
51698
54428
  if (method === "DELETE") {
51699
54429
  await store.tasks.delete(id, contextFromPrincipal(principal));
51700
- return json3({ deleted: true, id });
54430
+ return json4({ deleted: true, id });
51701
54431
  }
51702
54432
  return error(405, `method ${method} not allowed on /v1/tasks/:id`);
51703
54433
  }
@@ -51705,24 +54435,70 @@ async function handleV1Request(req, url, dependencies = {}) {
51705
54435
  if (!id) {
51706
54436
  if (method === "GET") {
51707
54437
  const projects = await store.projects.list();
51708
- return json3({ projects, count: projects.length });
54438
+ return json4({ projects, count: projects.length });
51709
54439
  }
51710
54440
  if (method === "POST") {
51711
- const body = await readJson2(req);
54441
+ const body = await readJson3(req);
51712
54442
  if (!body)
51713
54443
  return error(400, "invalid JSON body");
51714
54444
  const validated = validateProjectCreate(body);
51715
54445
  if (!validated.ok)
51716
54446
  return error(400, validated.message);
51717
54447
  const project = await store.projects.create(validated.input, contextFromPrincipal(principal));
51718
- return json3({ project }, 201);
54448
+ return json4({ project }, 201);
51719
54449
  }
51720
54450
  return error(405, `method ${method} not allowed on /v1/projects`);
51721
54451
  }
54452
+ if (action === "task-list" && subId === "ensure") {
54453
+ if (method === "GET") {
54454
+ return json4(await planProjectTaskListEnsure(store, id));
54455
+ }
54456
+ if (method !== "POST") {
54457
+ return error(405, `method ${method} not allowed on /v1/projects/:id/task-list/ensure`);
54458
+ }
54459
+ const body = await readJson3(req);
54460
+ if (!body)
54461
+ return error(400, "invalid JSON body");
54462
+ const unknown = Object.keys(body).find((key) => !["expected_project_revision", "idempotency_key"].includes(key));
54463
+ if (unknown)
54464
+ return error(400, `unknown task-list ensure field: ${unknown}`);
54465
+ if (typeof body.expected_project_revision !== "string" || !body.expected_project_revision.trim()) {
54466
+ return error(400, "expected_project_revision must be a non-empty string from a fresh ensure plan");
54467
+ }
54468
+ if (body.idempotency_key !== undefined && typeof body.idempotency_key !== "string") {
54469
+ return error(400, "idempotency_key must be a string");
54470
+ }
54471
+ const result = await applyProjectTaskListEnsure(store, id, {
54472
+ expected_project_revision: body.expected_project_revision,
54473
+ ...typeof body.idempotency_key === "string" ? { idempotency_key: body.idempotency_key } : {}
54474
+ });
54475
+ return json4(result, result.action === "created" ? 201 : 200);
54476
+ }
54477
+ if (action === "task-list" && subId === "rollback") {
54478
+ if (method !== "POST") {
54479
+ return error(405, `method ${method} not allowed on /v1/projects/:id/task-list/rollback`);
54480
+ }
54481
+ const body = await readJson3(req);
54482
+ if (!body)
54483
+ return error(400, "invalid JSON body");
54484
+ const unknown = Object.keys(body).find((key) => !["receipt_id", "expected_task_list_revision"].includes(key));
54485
+ if (unknown)
54486
+ return error(400, `unknown task-list rollback field: ${unknown}`);
54487
+ if (typeof body.receipt_id !== "string" || !body.receipt_id.trim()) {
54488
+ return error(400, "receipt_id must be a non-empty string");
54489
+ }
54490
+ if (typeof body.expected_task_list_revision !== "string" || !body.expected_task_list_revision.trim()) {
54491
+ return error(400, "expected_task_list_revision must be a non-empty string from the accepted receipt");
54492
+ }
54493
+ return json4(await rollbackProjectTaskListEnsure(store, id, {
54494
+ receipt_id: body.receipt_id,
54495
+ expected_task_list_revision: body.expected_task_list_revision
54496
+ }));
54497
+ }
51722
54498
  if (action === "rename") {
51723
54499
  if (method !== "POST")
51724
54500
  return error(405, `method ${method} not allowed on /v1/projects/:id/rename`);
51725
- const body = await readJson2(req);
54501
+ const body = await readJson3(req);
51726
54502
  if (!body || typeof body.new_slug !== "string" || !body.new_slug.trim() || !normalizeSlug(body.new_slug)) {
51727
54503
  return error(400, "new_slug must be a non-empty string");
51728
54504
  }
@@ -51732,14 +54508,14 @@ async function handleV1Request(req, url, dependencies = {}) {
51732
54508
  const unknownField = Object.keys(body).find((key) => !["new_slug", "name"].includes(key));
51733
54509
  if (unknownField)
51734
54510
  return error(400, `unknown project rename field: ${unknownField}`);
51735
- return json3(await store.projects.rename(id, body, contextFromPrincipal(principal)));
54511
+ return json4(await store.projects.rename(id, body, contextFromPrincipal(principal)));
51736
54512
  }
51737
54513
  if (method === "GET") {
51738
54514
  const project = await store.projects.get(id);
51739
- return project ? json3({ project }) : error(404, "project not found");
54515
+ return project ? json4({ project }) : error(404, "project not found");
51740
54516
  }
51741
54517
  if (method === "PATCH" || method === "PUT") {
51742
- const body = await readJson2(req);
54518
+ const body = await readJson3(req);
51743
54519
  if (!body)
51744
54520
  return error(400, "invalid JSON body");
51745
54521
  const validated = validateProjectPatch(body);
@@ -51748,21 +54524,21 @@ async function handleV1Request(req, url, dependencies = {}) {
51748
54524
  if (!await store.projects.get(id))
51749
54525
  return error(404, "project not found");
51750
54526
  const project = await store.projects.update(id, validated.patch);
51751
- return json3({ project });
54527
+ return json4({ project });
51752
54528
  }
51753
54529
  if (method === "DELETE") {
51754
54530
  await store.projects.delete(id, contextFromPrincipal(principal));
51755
- return json3({ deleted: true, id });
54531
+ return json4({ deleted: true, id });
51756
54532
  }
51757
54533
  return error(405, `method ${method} not allowed on /v1/projects/:id`);
51758
54534
  }
51759
54535
  if (resource === "plans") {
51760
54536
  if (!id && method === "GET") {
51761
54537
  const plans = await store.plans.list(url.searchParams.get("project_id") ?? undefined);
51762
- return json3({ plans, count: plans.length });
54538
+ return json4({ plans, count: plans.length });
51763
54539
  }
51764
54540
  if (!id && method === "POST") {
51765
- const body = await readJson2(req);
54541
+ const body = await readJson3(req);
51766
54542
  const validated = validatePlanCreate(body);
51767
54543
  if (!validated.ok)
51768
54544
  return error(400, validated.message);
@@ -51777,14 +54553,14 @@ async function handleV1Request(req, url, dependencies = {}) {
51777
54553
  }
51778
54554
  }
51779
54555
  const plan = await store.plans.create(validated.input, contextFromPrincipal(principal, validated.input));
51780
- return json3({ plan }, 201);
54556
+ return json4({ plan }, 201);
51781
54557
  }
51782
54558
  if (id && method === "GET") {
51783
54559
  const plan = await store.plans.get(id);
51784
- return plan ? json3({ plan }) : error(404, "plan not found");
54560
+ return plan ? json4({ plan }) : error(404, "plan not found");
51785
54561
  }
51786
54562
  if (id && (method === "PATCH" || method === "PUT")) {
51787
- const body = await readJson2(req);
54563
+ const body = await readJson3(req);
51788
54564
  if (!body || Object.keys(body).length === 0)
51789
54565
  return error(400, "plan patch is required");
51790
54566
  const allowed = new Set(["name", "slug", "description", "status", "task_list_id", "agent_id"]);
@@ -51821,12 +54597,12 @@ async function handleV1Request(req, url, dependencies = {}) {
51821
54597
  }
51822
54598
  }
51823
54599
  const plan = await store.plans.update(id, body);
51824
- return json3({ plan });
54600
+ return json4({ plan });
51825
54601
  }
51826
54602
  if (id && method === "DELETE") {
51827
54603
  if (!await store.plans.delete(id, contextFromPrincipal(principal)))
51828
54604
  return error(404, "plan not found");
51829
- return json3({ deleted: true, id });
54605
+ return json4({ deleted: true, id });
51830
54606
  }
51831
54607
  if (id)
51832
54608
  return error(405, `method ${method} not allowed on /v1/plans/:id`);
@@ -51835,50 +54611,50 @@ async function handleV1Request(req, url, dependencies = {}) {
51835
54611
  if (!id && method === "GET") {
51836
54612
  const projectId = url.searchParams.get("project_id");
51837
54613
  const templates = (await store.templates.list()).filter((template) => projectId === null || template.project_id === projectId);
51838
- return json3({ templates, count: templates.length });
54614
+ return json4({ templates, count: templates.length });
51839
54615
  }
51840
54616
  if (!id && method === "POST") {
51841
- const body = await readJson2(req);
54617
+ const body = await readJson3(req);
51842
54618
  const validated = validateTemplateCreate(body);
51843
54619
  if (!validated.ok)
51844
54620
  return error(400, validated.message);
51845
54621
  const template = await store.templates.create(validated.input, contextFromPrincipal(principal));
51846
- return json3({ template: await store.templates.getWithTasks(template.id) }, 201);
54622
+ return json4({ template: await store.templates.getWithTasks(template.id) }, 201);
51847
54623
  }
51848
54624
  if (!id)
51849
54625
  return error(405, `method ${method} not allowed on /v1/templates`);
51850
54626
  if (method === "GET") {
51851
54627
  const template = await store.templates.getWithTasks(id);
51852
- return template ? json3({ template }) : error(404, "template not found");
54628
+ return template ? json4({ template }) : error(404, "template not found");
51853
54629
  }
51854
54630
  if (method === "PATCH" || method === "PUT") {
51855
- const body = await readJson2(req);
54631
+ const body = await readJson3(req);
51856
54632
  const validated = validateTemplatePatch(body);
51857
54633
  if (!validated.ok)
51858
54634
  return error(400, validated.message);
51859
54635
  const template = await store.templates.update(id, validated.patch, contextFromPrincipal(principal));
51860
- return template ? json3({ template: await store.templates.getWithTasks(id) }) : error(404, "template not found");
54636
+ return template ? json4({ template: await store.templates.getWithTasks(id) }) : error(404, "template not found");
51861
54637
  }
51862
54638
  if (method === "DELETE") {
51863
54639
  const deleted = await store.templates.delete(id, contextFromPrincipal(principal));
51864
- return deleted ? json3({ deleted: true, id }) : error(404, "template not found");
54640
+ return deleted ? json4({ deleted: true, id }) : error(404, "template not found");
51865
54641
  }
51866
54642
  return error(405, `method ${method} not allowed on /v1/templates/:id`);
51867
54643
  }
51868
54644
  if (resource === "agents") {
51869
54645
  if (!id && method === "GET") {
51870
54646
  const agents = await store.agents.list();
51871
- return json3({ agents, count: agents.length });
54647
+ return json4({ agents, count: agents.length });
51872
54648
  }
51873
54649
  if (!id && method === "POST") {
51874
- const body = await readJson2(req);
54650
+ const body = await readJson3(req);
51875
54651
  if (!body || typeof body.name !== "string" || !body.name.trim())
51876
54652
  return error(400, "name is required");
51877
54653
  const result = await store.agents.register(body, contextFromPrincipal(principal));
51878
54654
  if (result && typeof result === "object" && "conflict" in result) {
51879
54655
  return error(409, result.message ?? "agent name conflict", { conflict: true });
51880
54656
  }
51881
- return json3({ agent: result }, 201);
54657
+ return json4({ agent: result }, 201);
51882
54658
  }
51883
54659
  if (id && action === "heartbeat") {
51884
54660
  if (method !== "POST")
@@ -51887,7 +54663,7 @@ async function handleV1Request(req, url, dependencies = {}) {
51887
54663
  return error(501, "agent heartbeat is not supported by this storage backend");
51888
54664
  }
51889
54665
  const agent = await store.agents.heartbeat(id, contextFromPrincipal(principal));
51890
- return agent ? json3({ agent }) : error(404, "agent not found");
54666
+ return agent ? json4({ agent }) : error(404, "agent not found");
51891
54667
  }
51892
54668
  if (id && action === "release") {
51893
54669
  if (method !== "POST")
@@ -51895,18 +54671,18 @@ async function handleV1Request(req, url, dependencies = {}) {
51895
54671
  if (typeof store.agents.release !== "function") {
51896
54672
  return error(501, "agent release is not supported by this storage backend");
51897
54673
  }
51898
- const body = await readJson2(req) ?? {};
54674
+ const body = await readJson3(req) ?? {};
51899
54675
  const result = await store.agents.release(id, body.session_id, contextFromPrincipal(principal));
51900
54676
  if (!result)
51901
54677
  return error(404, "agent not found");
51902
54678
  if (!result.released) {
51903
54679
  return error(409, "release denied: session_id does not match agent's current session", { released: false });
51904
54680
  }
51905
- return json3({ agent: result.agent, released: true });
54681
+ return json4({ agent: result.agent, released: true });
51906
54682
  }
51907
54683
  if (id && method === "GET") {
51908
54684
  const agent = await store.agents.get(id);
51909
- return agent ? json3({ agent }) : error(404, "agent not found");
54685
+ return agent ? json4({ agent }) : error(404, "agent not found");
51910
54686
  }
51911
54687
  }
51912
54688
  if (resource === "activity" && !id) {
@@ -51915,16 +54691,16 @@ async function handleV1Request(req, url, dependencies = {}) {
51915
54691
  const limitParam = url.searchParams.get("limit");
51916
54692
  const limit = limitParam ? Math.max(1, Math.min(1e4, Number(limitParam) || 50)) : 50;
51917
54693
  const activity = await store.audit.getRecentActivity(limit);
51918
- return json3({ activity, count: activity.length });
54694
+ return json4({ activity, count: activity.length });
51919
54695
  }
51920
54696
  if (resource === "task-lists") {
51921
54697
  if (!id && method === "GET") {
51922
54698
  const projectId = url.searchParams.get("project_id") ?? undefined;
51923
54699
  const taskLists = await store.taskLists.list(projectId);
51924
- return json3({ task_lists: taskLists, count: taskLists.length });
54700
+ return json4({ task_lists: taskLists, count: taskLists.length });
51925
54701
  }
51926
54702
  if (!id && method === "POST") {
51927
- const body = await readJson2(req);
54703
+ const body = await readJson3(req);
51928
54704
  if (!body || typeof body.name !== "string" || !body.name.trim())
51929
54705
  return error(400, "name is required");
51930
54706
  const unknownField = Object.keys(body).find((key) => !["name", "slug", "project_id", "description", "metadata"].includes(key));
@@ -51943,14 +54719,14 @@ async function handleV1Request(req, url, dependencies = {}) {
51943
54719
  return error(400, "task-list slug must be non-empty kebab-case");
51944
54720
  }
51945
54721
  const taskList = await store.taskLists.create(body, contextFromPrincipal(principal));
51946
- return json3({ task_list: taskList }, 201);
54722
+ return json4({ task_list: taskList }, 201);
51947
54723
  }
51948
54724
  if (id && method === "GET") {
51949
54725
  const taskList = await store.taskLists.get(id);
51950
- return taskList ? json3({ task_list: taskList }) : error(404, "task list not found");
54726
+ return taskList ? json4({ task_list: taskList }) : error(404, "task list not found");
51951
54727
  }
51952
54728
  if (id && (method === "PATCH" || method === "PUT")) {
51953
- const body = await readJson2(req);
54729
+ const body = await readJson3(req);
51954
54730
  if (!body)
51955
54731
  return error(400, "invalid JSON body");
51956
54732
  const unknownField = Object.keys(body).find((key) => !["slug", "name", "description", "metadata"].includes(key));
@@ -51970,11 +54746,11 @@ async function handleV1Request(req, url, dependencies = {}) {
51970
54746
  if (!await store.taskLists.get(id))
51971
54747
  return error(404, "task list not found");
51972
54748
  const taskList = await store.taskLists.update(id, body);
51973
- return json3({ task_list: taskList });
54749
+ return json4({ task_list: taskList });
51974
54750
  }
51975
54751
  if (id && method === "DELETE") {
51976
54752
  const deleted = await store.taskLists.delete(id, contextFromPrincipal(principal));
51977
- return deleted ? json3({ deleted: true, id }) : error(404, "task list not found");
54753
+ return deleted ? json4({ deleted: true, id }) : error(404, "task list not found");
51978
54754
  }
51979
54755
  return error(405, `method ${method} not allowed on /v1/task-lists${id ? "/:id" : ""}`);
51980
54756
  }
@@ -51985,7 +54761,7 @@ async function handleV1Request(req, url, dependencies = {}) {
51985
54761
  return error(501, "dependency edge listing is not supported by this storage backend");
51986
54762
  }
51987
54763
  const dependencies2 = await store.dependencies.listAll();
51988
- return json3({ dependencies: dependencies2, count: dependencies2.length });
54764
+ return json4({ dependencies: dependencies2, count: dependencies2.length });
51989
54765
  }
51990
54766
  if (resource === "commits" && id) {
51991
54767
  if (method !== "GET")
@@ -51993,7 +54769,7 @@ async function handleV1Request(req, url, dependencies = {}) {
51993
54769
  if (!store.commits)
51994
54770
  return error(501, "commit links are not supported by this storage backend");
51995
54771
  const commit = await store.commits.find(id);
51996
- return json3({ commit: commit ?? null });
54772
+ return json4({ commit: commit ?? null });
51997
54773
  }
51998
54774
  if (resource === "refs" && id) {
51999
54775
  if (method !== "GET")
@@ -52007,7 +54783,7 @@ async function handleV1Request(req, url, dependencies = {}) {
52007
54783
  return error(400, "ref path segment has invalid percent encoding");
52008
54784
  }
52009
54785
  const refs = await store.gitRefs.find(decodedRef);
52010
- return json3({ refs, count: refs.length });
54786
+ return json4({ refs, count: refs.length });
52011
54787
  }
52012
54788
  if (resource === "next" && !id) {
52013
54789
  if (method !== "GET")
@@ -52019,7 +54795,7 @@ async function handleV1Request(req, url, dependencies = {}) {
52019
54795
  ...url.searchParams.get("plan_id") ? { plan_id: url.searchParams.get("plan_id") } : {}
52020
54796
  };
52021
54797
  const task2 = await store.tasks.getNext(agent, filters);
52022
- return json3({ task: task2 ?? null });
54798
+ return json4({ task: task2 ?? null });
52023
54799
  }
52024
54800
  if (resource === "stats" && method === "GET") {
52025
54801
  const [tasks, tasksAll, projects] = await Promise.all([
@@ -52027,7 +54803,7 @@ async function handleV1Request(req, url, dependencies = {}) {
52027
54803
  store.tasks.count({ include_subtasks: true }),
52028
54804
  store.projects.list()
52029
54805
  ]);
52030
- return json3({ tasks, tasks_all: tasksAll, subtasks: tasksAll - tasks, projects: projects.length });
54806
+ return json4({ tasks, tasks_all: tasksAll, subtasks: tasksAll - tasks, projects: projects.length });
52031
54807
  }
52032
54808
  if (resource === "integrity" && !id) {
52033
54809
  if (method !== "GET")
@@ -52036,7 +54812,7 @@ async function handleV1Request(req, url, dependencies = {}) {
52036
54812
  return error(501, "referential-integrity reporting is not supported by this storage backend");
52037
54813
  }
52038
54814
  const integrity = await store.integrity.report();
52039
- return json3({ integrity });
54815
+ return json4({ integrity });
52040
54816
  }
52041
54817
  if (resource === "import") {
52042
54818
  if (method !== "POST")
@@ -52044,7 +54820,7 @@ async function handleV1Request(req, url, dependencies = {}) {
52044
54820
  if (typeof store.sync.importSnapshot !== "function") {
52045
54821
  return error(501, "snapshot import is not supported by this storage backend");
52046
54822
  }
52047
- const raw = await readJson2(req);
54823
+ const raw = await readJson3(req);
52048
54824
  if (raw === null)
52049
54825
  return error(400, "invalid JSON body");
52050
54826
  const snapshot = normalizeImportSnapshot(raw);
@@ -52053,10 +54829,14 @@ async function handleV1Request(req, url, dependencies = {}) {
52053
54829
  return error(400, "empty snapshot: provide at least one record array (tasks/projects/plans/...)");
52054
54830
  }
52055
54831
  const result = await store.sync.importSnapshot(snapshot, contextFromPrincipal(principal));
52056
- return json3({ result, received });
54832
+ return json4({ result, received });
52057
54833
  }
52058
54834
  return error(404, `unknown /v1 resource: ${resource ?? "(root)"}`);
52059
54835
  } catch (e) {
54836
+ if (e instanceof ProjectTaskListEnsureError) {
54837
+ const status = e.code === "PROJECT_NOT_FOUND" || e.code === "PROJECT_TASK_LIST_RECEIPT_NOT_FOUND" ? 404 : e.code === "PROJECT_TASK_LIST_IDEMPOTENCY_KEY_INVALID" ? 400 : 409;
54838
+ return error(status, e.message, { code: e.code, conflict: status === 409, ...e.details });
54839
+ }
52060
54840
  if (e instanceof TaskReferenceAmbiguousError) {
52061
54841
  return error(409, e.message, {
52062
54842
  code: TaskReferenceAmbiguousError.code,
@@ -52076,13 +54856,15 @@ async function handleV1Request(req, url, dependencies = {}) {
52076
54856
  return error(500, e.message || "internal error");
52077
54857
  }
52078
54858
  }
52079
- var JSON_HEADERS2, DEFAULT_COMMENT_PAGE_SIZE = 100, MAX_COMMENT_PAGE_SIZE = 500, LEGACY_COMMENT_RESPONSE_LIMIT = 500;
54859
+ var JSON_HEADERS3, DEFAULT_COMMENT_PAGE_SIZE = 100, MAX_COMMENT_PAGE_SIZE = 500, LEGACY_COMMENT_RESPONSE_LIMIT = 500;
52080
54860
  var init_v1 = __esm(() => {
52081
54861
  init_types();
52082
54862
  init_cloud();
52083
54863
  init_pr_groups();
54864
+ init_project_registration();
52084
54865
  init_redaction();
52085
- JSON_HEADERS2 = { "Content-Type": "application/json" };
54866
+ init_project_task_list_ensure();
54867
+ JSON_HEADERS3 = { "Content-Type": "application/json" };
52086
54868
  });
52087
54869
 
52088
54870
  // src/pr-groups/sqlite.ts
@@ -52241,12 +55023,12 @@ class SqlitePrGroupLedgerPersistence {
52241
55023
  this.tx = new SqlitePrGroupTransaction(db);
52242
55024
  }
52243
55025
  async transaction(fn) {
52244
- const previous = sqliteTransactionTails.get(this.db) ?? Promise.resolve();
55026
+ const previous = sqliteTransactionTails2.get(this.db) ?? Promise.resolve();
52245
55027
  let release;
52246
55028
  const current = new Promise((resolve17) => {
52247
55029
  release = resolve17;
52248
55030
  });
52249
- sqliteTransactionTails.set(this.db, current);
55031
+ sqliteTransactionTails2.set(this.db, current);
52250
55032
  await previous;
52251
55033
  try {
52252
55034
  this.db.exec("BEGIN IMMEDIATE");
@@ -52260,8 +55042,8 @@ class SqlitePrGroupLedgerPersistence {
52260
55042
  throw error2;
52261
55043
  } finally {
52262
55044
  release();
52263
- if (sqliteTransactionTails.get(this.db) === current)
52264
- sqliteTransactionTails.delete(this.db);
55045
+ if (sqliteTransactionTails2.get(this.db) === current)
55046
+ sqliteTransactionTails2.delete(this.db);
52265
55047
  }
52266
55048
  }
52267
55049
  getGroup(id) {
@@ -52291,9 +55073,9 @@ class SqlitePrGroupLedgerPersistence {
52291
55073
  return row ? eventFromRow2(row) : null;
52292
55074
  }
52293
55075
  }
52294
- var sqliteTransactionTails;
52295
- var init_sqlite = __esm(() => {
52296
- sqliteTransactionTails = new WeakMap;
55076
+ var sqliteTransactionTails2;
55077
+ var init_sqlite2 = __esm(() => {
55078
+ sqliteTransactionTails2 = new WeakMap;
52297
55079
  });
52298
55080
 
52299
55081
  // src/pr-groups/index.ts
@@ -52323,10 +55105,10 @@ function createLocalPrGroupLedger(db = getDatabase()) {
52323
55105
  var init_pr_groups2 = __esm(() => {
52324
55106
  init_database();
52325
55107
  init_ledger();
52326
- init_sqlite();
55108
+ init_sqlite2();
52327
55109
  init_types3();
52328
55110
  init_ledger();
52329
- init_sqlite();
55111
+ init_sqlite2();
52330
55112
  init_http_client();
52331
55113
  init_postgres();
52332
55114
  });
@@ -52337,7 +55119,7 @@ __export(exports_serve, {
52337
55119
  taskToSummary: () => taskToSummary,
52338
55120
  startServer: () => startServer,
52339
55121
  serveStaticFile: () => serveStaticFile,
52340
- json: () => json,
55122
+ json: () => json2,
52341
55123
  checkAuth: () => checkAuth,
52342
55124
  SECURITY_HEADERS: () => SECURITY_HEADERS,
52343
55125
  MIME_TYPES: () => MIME_TYPES
@@ -52423,7 +55205,7 @@ function checkRateLimit(ip) {
52423
55205
  }
52424
55206
  return { allowed: true };
52425
55207
  }
52426
- function json(data, status = 200, headers) {
55208
+ function json2(data, status = 200, headers) {
52427
55209
  return new Response(JSON.stringify(data), {
52428
55210
  status,
52429
55211
  headers: {
@@ -52568,7 +55350,7 @@ Dashboard not found at: ${dashboardDir}`);
52568
55350
  "Access-Control-Allow-Headers": "Content-Type, X-API-Key, Authorization",
52569
55351
  Vary: "Origin"
52570
55352
  } : undefined;
52571
- const jsonWithCors = (data, status = 200) => json(data, status, corsHeaders);
55353
+ const jsonWithCors = (data, status = 200) => json2(data, status, corsHeaders);
52572
55354
  if (method === "OPTIONS") {
52573
55355
  return new Response(null, {
52574
55356
  headers: corsHeaders || {
@@ -52649,13 +55431,13 @@ Dashboard not found at: ${dashboardDir}`);
52649
55431
  return res;
52650
55432
  }
52651
55433
  if (path === "/api/health" && method === "GET") {
52652
- return handleHealth(ctx, json);
55434
+ return handleHealth(ctx, json2);
52653
55435
  }
52654
55436
  if (path === "/api/headless" && method === "GET") {
52655
- return handleHeadlessBoundary(ctx, json);
55437
+ return handleHeadlessBoundary(ctx, json2);
52656
55438
  }
52657
55439
  if (path === "/api/stats" && method === "GET") {
52658
- return handleStats(ctx, json);
55440
+ return handleStats(ctx, json2);
52659
55441
  }
52660
55442
  if (path === "/api/tasks" && method === "GET") {
52661
55443
  return handleListTasks(req, url, ctx, jsonWithCors, taskToSummary);
@@ -52670,16 +55452,16 @@ Dashboard not found at: ${dashboardDir}`);
52670
55452
  return handleTasksExport(req, url, ctx, jsonWithCors, taskToSummary);
52671
55453
  }
52672
55454
  if (path === "/api/tasks/bulk" && method === "POST") {
52673
- return handleTasksBulk(req, ctx, json);
55455
+ return handleTasksBulk(req, ctx, json2);
52674
55456
  }
52675
55457
  if (path === "/api/tasks/status" && method === "GET") {
52676
- return handleTasksStatus(req, url, ctx, json);
55458
+ return handleTasksStatus(req, url, ctx, json2);
52677
55459
  }
52678
55460
  if (path === "/api/tasks/next" && method === "GET") {
52679
55461
  return handleTasksNext(req, url, ctx, jsonWithCors, taskToSummary);
52680
55462
  }
52681
55463
  if (path === "/api/tasks/active" && method === "GET") {
52682
- return handleTasksActive(req, url, ctx, json);
55464
+ return handleTasksActive(req, url, ctx, json2);
52683
55465
  }
52684
55466
  if (path === "/api/tasks/stale" && method === "GET") {
52685
55467
  return handleTasksStale(req, url, ctx, jsonWithCors, taskToSummary);
@@ -52692,11 +55474,11 @@ Dashboard not found at: ${dashboardDir}`);
52692
55474
  }
52693
55475
  const attachmentsMatch = path.match(/^\/api\/tasks\/([^/]+)\/attachments$/);
52694
55476
  if (attachmentsMatch && method === "GET") {
52695
- return handleTaskAttachments(attachmentsMatch[1], ctx, json);
55477
+ return handleTaskAttachments(attachmentsMatch[1], ctx, json2);
52696
55478
  }
52697
55479
  const progressMatch = path.match(/^\/api\/tasks\/([^/]+)\/progress$/);
52698
55480
  if (progressMatch) {
52699
- const res = await handleTaskProgress(progressMatch[1], req, method, ctx, json, url);
55481
+ const res = await handleTaskProgress(progressMatch[1], req, method, ctx, json2, url);
52700
55482
  if (res !== null)
52701
55483
  return res;
52702
55484
  }
@@ -52710,7 +55492,7 @@ Dashboard not found at: ${dashboardDir}`);
52710
55492
  return handlePatchTask(id, req, ctx, jsonWithCors, taskToSummary);
52711
55493
  }
52712
55494
  if (method === "DELETE") {
52713
- return handleDeleteTask(id, ctx, json);
55495
+ return handleDeleteTask(id, ctx, json2);
52714
55496
  }
52715
55497
  }
52716
55498
  const startMatch = path.match(/^\/api\/tasks\/([^/]+)\/start$/);
@@ -52726,7 +55508,7 @@ Dashboard not found at: ${dashboardDir}`);
52726
55508
  return handleCompleteTask(completeMatch[1], ctx, jsonWithCors, taskToSummary);
52727
55509
  }
52728
55510
  if (path === "/api/projects" && method === "GET") {
52729
- return handleListProjects(url, ctx, json);
55511
+ return handleListProjects(url, ctx, json2);
52730
55512
  }
52731
55513
  if (path === "/api/agents/me" && method === "GET") {
52732
55514
  return handleAgentMe(req, url, ctx, jsonWithCors, taskToSummary);
@@ -52739,92 +55521,92 @@ Dashboard not found at: ${dashboardDir}`);
52739
55521
  return handleClaimTask(req, ctx, jsonWithCors, taskToSummary);
52740
55522
  }
52741
55523
  if (path === "/api/orgs" && method === "GET") {
52742
- return handleListOrgs(ctx, json);
55524
+ return handleListOrgs(ctx, json2);
52743
55525
  }
52744
55526
  if (path === "/api/orgs" && method === "POST") {
52745
- return handleCreateOrg(req, ctx, json);
55527
+ return handleCreateOrg(req, ctx, json2);
52746
55528
  }
52747
55529
  const orgMatch = path.match(/^\/api\/orgs\/([^/]+)$/);
52748
55530
  if (orgMatch && method === "PATCH") {
52749
- return handleUpdateOrg(orgMatch[1], req, ctx, json);
55531
+ return handleUpdateOrg(orgMatch[1], req, ctx, json2);
52750
55532
  }
52751
55533
  if (orgMatch && method === "DELETE") {
52752
- return handleDeleteOrg(orgMatch[1], ctx, json);
55534
+ return handleDeleteOrg(orgMatch[1], ctx, json2);
52753
55535
  }
52754
55536
  if (path === "/api/org" && method === "GET") {
52755
- return handleOrgChart(ctx, json);
55537
+ return handleOrgChart(ctx, json2);
52756
55538
  }
52757
55539
  const teamMatch = path.match(/^\/api\/agents\/([^/]+)\/team$/);
52758
55540
  if (teamMatch && method === "GET") {
52759
- return handleAgentTeam(teamMatch[1], ctx, json);
55541
+ return handleAgentTeam(teamMatch[1], ctx, json2);
52760
55542
  }
52761
55543
  if (path === "/api/agents" && method === "GET") {
52762
- return handleListAgents(url, ctx, json);
55544
+ return handleListAgents(url, ctx, json2);
52763
55545
  }
52764
55546
  if (path === "/api/projects" && method === "POST") {
52765
- return handleCreateProject(req, ctx, json);
55547
+ return handleCreateProject(req, ctx, json2);
52766
55548
  }
52767
55549
  const projectDeleteMatch = path.match(/^\/api\/projects\/([^/]+)$/);
52768
55550
  if (projectDeleteMatch && method === "DELETE") {
52769
- return handleDeleteProject(projectDeleteMatch[1], ctx, json);
55551
+ return handleDeleteProject(projectDeleteMatch[1], ctx, json2);
52770
55552
  }
52771
55553
  if (path === "/api/agents" && method === "POST") {
52772
- return handleRegisterAgent(req, ctx, json);
55554
+ return handleRegisterAgent(req, ctx, json2);
52773
55555
  }
52774
55556
  const agentMatch = path.match(/^\/api\/agents\/([^/]+)$/);
52775
55557
  if (agentMatch && method === "PATCH") {
52776
- return handleUpdateAgent(agentMatch[1], req, ctx, json);
55558
+ return handleUpdateAgent(agentMatch[1], req, ctx, json2);
52777
55559
  }
52778
55560
  if (agentMatch && method === "DELETE") {
52779
- return handleDeleteAgent(agentMatch[1], ctx, json);
55561
+ return handleDeleteAgent(agentMatch[1], ctx, json2);
52780
55562
  }
52781
55563
  if (path === "/api/agents/bulk" && method === "POST") {
52782
- return handleBulkDeleteAgents(req, ctx, json);
55564
+ return handleBulkDeleteAgents(req, ctx, json2);
52783
55565
  }
52784
55566
  if (path === "/api/projects/bulk" && method === "POST") {
52785
- return handleBulkDeleteProjects(req, ctx, json);
55567
+ return handleBulkDeleteProjects(req, ctx, json2);
52786
55568
  }
52787
55569
  if (path === "/api/doctor" && method === "GET") {
52788
- return handleDoctor(ctx, json);
55570
+ return handleDoctor(ctx, json2);
52789
55571
  }
52790
55572
  if (path === "/api/report" && method === "GET") {
52791
- return handleReport(req, url, ctx, json);
55573
+ return handleReport(req, url, ctx, json2);
52792
55574
  }
52793
55575
  if (path === "/api/activity" && method === "GET") {
52794
- return handleActivity(req, url, ctx, json);
55576
+ return handleActivity(req, url, ctx, json2);
52795
55577
  }
52796
55578
  const historyMatch = path.match(/^\/api\/tasks\/([^/]+)\/history$/);
52797
55579
  if (historyMatch && method === "GET") {
52798
- return handleTaskHistory(historyMatch[1], ctx, json, url);
55580
+ return handleTaskHistory(historyMatch[1], ctx, json2, url);
52799
55581
  }
52800
55582
  if (path === "/api/webhooks" && method === "GET") {
52801
- return handleListWebhooks(ctx, json);
55583
+ return handleListWebhooks(ctx, json2);
52802
55584
  }
52803
55585
  if (path === "/api/webhooks" && method === "POST") {
52804
- return handleCreateWebhook(req, ctx, json);
55586
+ return handleCreateWebhook(req, ctx, json2);
52805
55587
  }
52806
55588
  const webhookMatch = path.match(/^\/api\/webhooks\/([^/]+)$/);
52807
55589
  if (webhookMatch && method === "DELETE") {
52808
- return handleDeleteWebhook(webhookMatch[1], ctx, json);
55590
+ return handleDeleteWebhook(webhookMatch[1], ctx, json2);
52809
55591
  }
52810
55592
  if (path === "/api/templates" && method === "GET") {
52811
- return handleListTemplates(ctx, json);
55593
+ return handleListTemplates(ctx, json2);
52812
55594
  }
52813
55595
  if (path === "/api/templates" && method === "POST") {
52814
- return handleCreateTemplate(req, ctx, json);
55596
+ return handleCreateTemplate(req, ctx, json2);
52815
55597
  }
52816
55598
  const templateMatch = path.match(/^\/api\/templates\/([^/]+)$/);
52817
55599
  if (templateMatch && method === "DELETE") {
52818
- return handleDeleteTemplate(templateMatch[1], ctx, json);
55600
+ return handleDeleteTemplate(templateMatch[1], ctx, json2);
52819
55601
  }
52820
55602
  if (path === "/api/plans" && method === "GET") {
52821
- return handleListPlans(url, ctx, json);
55603
+ return handleListPlans(url, ctx, json2);
52822
55604
  }
52823
55605
  if (path === "/api/plans" && method === "POST") {
52824
- return handleCreatePlan(req, ctx, json);
55606
+ return handleCreatePlan(req, ctx, json2);
52825
55607
  }
52826
55608
  if (path === "/api/plans/bulk" && method === "POST") {
52827
- return handleBulkDeletePlans(req, ctx, json);
55609
+ return handleBulkDeletePlans(req, ctx, json2);
52828
55610
  }
52829
55611
  const planMatch = path.match(/^\/api\/plans\/([^/]+)$/);
52830
55612
  if (planMatch) {
@@ -52833,16 +55615,16 @@ Dashboard not found at: ${dashboardDir}`);
52833
55615
  return handleGetPlan(id, ctx, jsonWithCors, taskToSummary);
52834
55616
  }
52835
55617
  if (method === "PATCH") {
52836
- return handleUpdatePlan(id, req, ctx, json);
55618
+ return handleUpdatePlan(id, req, ctx, json2);
52837
55619
  }
52838
55620
  if (method === "DELETE") {
52839
- return handleDeletePlan(id, ctx, json);
55621
+ return handleDeletePlan(id, ctx, json2);
52840
55622
  }
52841
55623
  }
52842
55624
  const staticRes = handleStaticFiles(path, method, ctx, jsonWithCors, serveStaticFile);
52843
55625
  if (staticRes)
52844
55626
  return staticRes;
52845
- return json({ error: "Not found" }, 404);
55627
+ return json2({ error: "Not found" }, 404);
52846
55628
  }
52847
55629
  });
52848
55630
  const shutdown = () => {