@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/index.js CHANGED
@@ -39,6 +39,206 @@ var __export = (target, all) => {
39
39
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
40
40
  var __require = import.meta.require;
41
41
 
42
+ // src/project-registration/schema.ts
43
+ function sqliteTodosProjectRegistrationSchemaSql() {
44
+ return `
45
+ CREATE TABLE IF NOT EXISTS todos_project_registration_receipts (
46
+ receipt_id TEXT PRIMARY KEY,
47
+ authority TEXT NOT NULL CHECK(authority = 'todos'),
48
+ route TEXT NOT NULL,
49
+ package_version TEXT NOT NULL,
50
+ authority_id TEXT NOT NULL,
51
+ tenant_id TEXT NOT NULL,
52
+ corpus_id TEXT NOT NULL,
53
+ operation_id TEXT NOT NULL,
54
+ step_id TEXT NOT NULL,
55
+ resource_kind TEXT NOT NULL CHECK(resource_kind IN ('project', 'task_list')),
56
+ direction TEXT NOT NULL CHECK(direction IN ('forward', 'inverse')),
57
+ target_selector TEXT NOT NULL,
58
+ idempotency_key TEXT NOT NULL,
59
+ request_digest TEXT NOT NULL,
60
+ precondition_digest TEXT NOT NULL,
61
+ normalized_call_digest TEXT NOT NULL,
62
+ outcome TEXT NOT NULL CHECK(outcome IN (
63
+ 'accepted', 'duplicate_of_accepted', 'terminal_nonacceptance'
64
+ )),
65
+ reason TEXT,
66
+ target_id TEXT,
67
+ result_revision TEXT,
68
+ result_digest TEXT,
69
+ duplicate_of_receipt_id TEXT,
70
+ accepted_receipt_id TEXT,
71
+ created_by_operation INTEGER NOT NULL CHECK(created_by_operation IN (0, 1)),
72
+ created_at TEXT NOT NULL
73
+ );
74
+
75
+ CREATE INDEX IF NOT EXISTS idx_todos_project_registration_receipts_lookup
76
+ ON todos_project_registration_receipts (
77
+ authority_id, tenant_id, corpus_id, operation_id, step_id,
78
+ resource_kind, direction, idempotency_key
79
+ );
80
+ CREATE INDEX IF NOT EXISTS idx_todos_project_registration_receipts_step
81
+ ON todos_project_registration_receipts (
82
+ authority_id, tenant_id, corpus_id, operation_id, step_id,
83
+ resource_kind, direction, outcome
84
+ );
85
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_todos_project_registration_receipts_accepted_step
86
+ ON todos_project_registration_receipts (
87
+ authority_id, tenant_id, corpus_id, operation_id, step_id,
88
+ resource_kind, direction
89
+ )
90
+ WHERE outcome = 'accepted';
91
+ CREATE INDEX IF NOT EXISTS idx_todos_project_registration_receipts_target
92
+ ON todos_project_registration_receipts (
93
+ authority_id, tenant_id, corpus_id, resource_kind, target_id
94
+ );
95
+
96
+ CREATE TABLE IF NOT EXISTS todos_project_registration_bindings (
97
+ authority_id TEXT NOT NULL,
98
+ tenant_id TEXT NOT NULL,
99
+ corpus_id TEXT NOT NULL,
100
+ resource_kind TEXT NOT NULL CHECK(resource_kind IN ('project', 'task_list')),
101
+ target_selector TEXT NOT NULL,
102
+ operation_id TEXT NOT NULL,
103
+ step_id TEXT NOT NULL,
104
+ direction TEXT NOT NULL CHECK(direction = 'forward'),
105
+ idempotency_key TEXT NOT NULL,
106
+ request_digest TEXT NOT NULL,
107
+ precondition_digest TEXT NOT NULL,
108
+ normalized_call_digest TEXT NOT NULL,
109
+ state TEXT NOT NULL CHECK(state IN (
110
+ 'pending', 'accepted', 'terminal_nonacceptance', 'removed'
111
+ )),
112
+ target_id TEXT,
113
+ accepted_receipt_id TEXT,
114
+ result_revision TEXT,
115
+ result_digest TEXT,
116
+ removed_receipt_id TEXT,
117
+ created_at TEXT NOT NULL,
118
+ updated_at TEXT NOT NULL,
119
+ PRIMARY KEY(
120
+ authority_id, tenant_id, corpus_id, resource_kind, target_selector
121
+ ),
122
+ UNIQUE(accepted_receipt_id)
123
+ );
124
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_todos_project_registration_binding_target
125
+ ON todos_project_registration_bindings(
126
+ authority_id, tenant_id, corpus_id, resource_kind, target_id
127
+ )
128
+ WHERE target_id IS NOT NULL;
129
+
130
+ CREATE TRIGGER IF NOT EXISTS todos_project_registration_receipts_immutable_update
131
+ BEFORE UPDATE ON todos_project_registration_receipts
132
+ BEGIN
133
+ SELECT RAISE(ABORT, 'todos project registration receipts are immutable');
134
+ END;
135
+
136
+ CREATE TRIGGER IF NOT EXISTS todos_project_registration_receipts_immutable_delete
137
+ BEFORE DELETE ON todos_project_registration_receipts
138
+ BEGIN
139
+ SELECT RAISE(ABORT, 'todos project registration receipts are immutable');
140
+ END;
141
+ `;
142
+ }
143
+ function postgresTodosProjectRegistrationSchemaSql() {
144
+ return [
145
+ `CREATE TABLE IF NOT EXISTS todos_project_registration_receipts (
146
+ receipt_id text PRIMARY KEY,
147
+ authority text NOT NULL CHECK(authority = 'todos'),
148
+ route text NOT NULL,
149
+ package_version text NOT NULL,
150
+ authority_id text NOT NULL,
151
+ tenant_id text NOT NULL,
152
+ corpus_id text NOT NULL,
153
+ operation_id text NOT NULL,
154
+ step_id text NOT NULL,
155
+ resource_kind text NOT NULL CHECK(resource_kind IN ('project', 'task_list')),
156
+ direction text NOT NULL CHECK(direction IN ('forward', 'inverse')),
157
+ target_selector text NOT NULL,
158
+ idempotency_key text NOT NULL,
159
+ request_digest text NOT NULL,
160
+ precondition_digest text NOT NULL,
161
+ normalized_call_digest text NOT NULL,
162
+ outcome text NOT NULL CHECK(outcome IN (
163
+ 'accepted', 'duplicate_of_accepted', 'terminal_nonacceptance'
164
+ )),
165
+ reason text,
166
+ target_id text,
167
+ result_revision text,
168
+ result_digest text,
169
+ duplicate_of_receipt_id text,
170
+ accepted_receipt_id text,
171
+ created_by_operation boolean NOT NULL,
172
+ created_at timestamptz NOT NULL
173
+ )`,
174
+ `CREATE INDEX IF NOT EXISTS todos_project_registration_receipts_lookup_idx
175
+ ON todos_project_registration_receipts (
176
+ authority_id, tenant_id, corpus_id, operation_id, step_id,
177
+ resource_kind, direction, idempotency_key
178
+ )`,
179
+ `CREATE INDEX IF NOT EXISTS todos_project_registration_receipts_step_idx
180
+ ON todos_project_registration_receipts (
181
+ authority_id, tenant_id, corpus_id, operation_id, step_id,
182
+ resource_kind, direction, outcome
183
+ )`,
184
+ `CREATE UNIQUE INDEX IF NOT EXISTS todos_project_registration_receipts_accepted_step_uidx
185
+ ON todos_project_registration_receipts (
186
+ authority_id, tenant_id, corpus_id, operation_id, step_id,
187
+ resource_kind, direction
188
+ )
189
+ WHERE outcome = 'accepted'`,
190
+ `CREATE INDEX IF NOT EXISTS todos_project_registration_receipts_target_idx
191
+ ON todos_project_registration_receipts (
192
+ authority_id, tenant_id, corpus_id, resource_kind, target_id
193
+ )`,
194
+ `CREATE TABLE IF NOT EXISTS todos_project_registration_bindings (
195
+ authority_id text NOT NULL,
196
+ tenant_id text NOT NULL,
197
+ corpus_id text NOT NULL,
198
+ resource_kind text NOT NULL CHECK(resource_kind IN ('project', 'task_list')),
199
+ target_selector text NOT NULL,
200
+ operation_id text NOT NULL,
201
+ step_id text NOT NULL,
202
+ direction text NOT NULL CHECK(direction = 'forward'),
203
+ idempotency_key text NOT NULL,
204
+ request_digest text NOT NULL,
205
+ precondition_digest text NOT NULL,
206
+ normalized_call_digest text NOT NULL,
207
+ state text NOT NULL CHECK(state IN (
208
+ 'pending', 'accepted', 'terminal_nonacceptance', 'removed'
209
+ )),
210
+ target_id text,
211
+ accepted_receipt_id text UNIQUE,
212
+ result_revision text,
213
+ result_digest text,
214
+ removed_receipt_id text,
215
+ created_at timestamptz NOT NULL,
216
+ updated_at timestamptz NOT NULL,
217
+ PRIMARY KEY(
218
+ authority_id, tenant_id, corpus_id, resource_kind, target_selector
219
+ )
220
+ )`,
221
+ `CREATE UNIQUE INDEX IF NOT EXISTS todos_project_registration_binding_target_uidx
222
+ ON todos_project_registration_bindings(
223
+ authority_id, tenant_id, corpus_id, resource_kind, target_id
224
+ )
225
+ WHERE target_id IS NOT NULL`,
226
+ `CREATE OR REPLACE FUNCTION todos_project_registration_receipts_immutable()
227
+ RETURNS trigger
228
+ LANGUAGE plpgsql
229
+ AS $$
230
+ BEGIN
231
+ RAISE EXCEPTION 'todos project registration receipts are immutable';
232
+ END;
233
+ $$`,
234
+ `DROP TRIGGER IF EXISTS todos_project_registration_receipts_immutable
235
+ ON todos_project_registration_receipts`,
236
+ `CREATE TRIGGER todos_project_registration_receipts_immutable
237
+ BEFORE UPDATE OR DELETE ON todos_project_registration_receipts
238
+ FOR EACH ROW EXECUTE FUNCTION todos_project_registration_receipts_immutable()`
239
+ ];
240
+ }
241
+
42
242
  // src/db/migrations.ts
43
243
  var MIGRATIONS;
44
244
  var init_migrations = __esm(() => {
@@ -1644,6 +1844,11 @@ var init_migrations = __esm(() => {
1644
1844
  INSERT OR IGNORE INTO _migrations (id) VALUES (68);
1645
1845
  COMMIT;
1646
1846
  PRAGMA foreign_keys = ON;
1847
+ `,
1848
+ `BEGIN;
1849
+ ${sqliteTodosProjectRegistrationSchemaSql()}
1850
+ INSERT OR IGNORE INTO _migrations (id) VALUES (69);
1851
+ COMMIT;
1647
1852
  `
1648
1853
  ];
1649
1854
  });
@@ -2846,6 +3051,7 @@ function ensureSchema(db) {
2846
3051
  )`);
2847
3052
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_api_keys_prefix ON api_keys(prefix)");
2848
3053
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_api_keys_active ON api_keys(revoked_at, expires_at)");
3054
+ db.exec(sqliteTodosProjectRegistrationSchemaSql());
2849
3055
  ensureTable("pr_groups", `
2850
3056
  CREATE TABLE pr_groups (
2851
3057
  schema_version INTEGER NOT NULL DEFAULT 1,
@@ -6541,6 +6747,40 @@ function deleteTaskList(id, db) {
6541
6747
  return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
6542
6748
  })();
6543
6749
  }
6750
+ function deleteTaskListIfUnchangedAndUnused(id, expected, db) {
6751
+ const d = db || getDatabase();
6752
+ return d.transaction(() => {
6753
+ const current = getTaskList(id, d);
6754
+ if (!current) {
6755
+ return { status: "not_found", task_dependents: 0, plan_dependents: 0 };
6756
+ }
6757
+ 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);
6758
+ if (changed) {
6759
+ return { status: "changed", task_dependents: 0, plan_dependents: 0 };
6760
+ }
6761
+ const taskDependents = Number(d.query("SELECT COUNT(*) AS count FROM tasks WHERE task_list_id = ?").get(id).count);
6762
+ const planDependents = Number(d.query("SELECT COUNT(*) AS count FROM plans WHERE task_list_id = ?").get(id).count);
6763
+ if (taskDependents > 0 || planDependents > 0) {
6764
+ return {
6765
+ status: "has_dependents",
6766
+ task_dependents: taskDependents,
6767
+ plan_dependents: planDependents
6768
+ };
6769
+ }
6770
+ recordStorageTombstone({
6771
+ object_type: "task_lists",
6772
+ object_id: id,
6773
+ payload: current
6774
+ }, d);
6775
+ releaseCanonicalSlugClaims("task_list", id, d);
6776
+ const deleted = d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
6777
+ return {
6778
+ status: deleted ? "deleted" : "not_found",
6779
+ task_dependents: 0,
6780
+ plan_dependents: 0
6781
+ };
6782
+ })();
6783
+ }
6544
6784
  function ensureTaskList(name, slug, projectId, db) {
6545
6785
  const d = db || getDatabase();
6546
6786
  const existing = getTaskListBySlug(slug, projectId, d);
@@ -12157,7 +12397,7 @@ var init_dispatches = __esm(() => {
12157
12397
  // package.json
12158
12398
  var package_default = {
12159
12399
  name: "@hasna/todos",
12160
- version: "0.15.6",
12400
+ version: "0.15.9",
12161
12401
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
12162
12402
  type: "module",
12163
12403
  main: "dist/index.js",
@@ -12195,6 +12435,10 @@ var package_default = {
12195
12435
  "./testing": {
12196
12436
  types: "./dist/testing.d.ts",
12197
12437
  import: "./dist/testing.js"
12438
+ },
12439
+ "./project-registration": {
12440
+ types: "./dist/project-registration.d.ts",
12441
+ import: "./dist/project-registration.js"
12198
12442
  }
12199
12443
  },
12200
12444
  workspaces: [
@@ -12207,8 +12451,8 @@ var package_default = {
12207
12451
  "README.md"
12208
12452
  ],
12209
12453
  scripts: {
12210
- 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",
12211
- "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/*'",
12454
+ 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",
12455
+ "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/*'",
12212
12456
  migrate: "bun run src/server/index.ts migrate",
12213
12457
  "backfill:comment-redaction": "bun run src/server/index.ts redact-comments",
12214
12458
  "generate:sdk": "bun run scripts/generate-sdk.ts",
@@ -23851,6 +24095,13 @@ var TODOS_PACKAGE_EXPORTS = [
23851
24095
  types: "./dist/testing.d.ts",
23852
24096
  description: "Test-store isolation helpers that keep a consumer's test suite off a shared todos store.",
23853
24097
  stability: "stable"
24098
+ },
24099
+ {
24100
+ subpath: "./project-registration",
24101
+ import: "./dist/project-registration.js",
24102
+ types: "./dist/project-registration.d.ts",
24103
+ description: "Conditional Projects-to-Todos project and task-list registration authority.",
24104
+ stability: "stable"
23854
24105
  }
23855
24106
  ];
23856
24107
  function source8(version) {
@@ -25479,7 +25730,8 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
25479
25730
  getBySlug: (slug, projectId) => getTaskListBySlug(slug, projectId, database()),
25480
25731
  list: (projectId) => listTaskLists(projectId, database()),
25481
25732
  update: (id, input) => updateTaskList(id, input, database()),
25482
- delete: (id) => deleteTaskList(id, database())
25733
+ delete: (id) => deleteTaskList(id, database()),
25734
+ deleteIfUnchangedAndUnused: (id, expected) => deleteTaskListIfUnchangedAndUnused(id, expected, database())
25483
25735
  },
25484
25736
  templates: {
25485
25737
  create: (input) => createTemplate(input, database()),
@@ -31896,6 +32148,2115 @@ class PrGroupHttpClient {
31896
32148
  function createLocalPrGroupLedger(db = getDatabase()) {
31897
32149
  return new PrGroupLedger(new SqlitePrGroupLedgerPersistence(db));
31898
32150
  }
32151
+ // src/project-registration/authority.ts
32152
+ import { createHash as createHash12 } from "crypto";
32153
+ // src/project-registration/types.ts
32154
+ var TODOS_PROJECT_REGISTRATION_ROUTE = "todos.project-registration.v1";
32155
+ var TODOS_PROJECT_REGISTRATION_CALLER_ROUTE = "projects.full-registration.v1";
32156
+ var TODOS_PROJECT_REGISTRATION_SCHEMA_VERSION = 1;
32157
+
32158
+ class TodosProjectRegistrationError extends Error {
32159
+ code;
32160
+ details;
32161
+ constructor(code, message, details = {}) {
32162
+ super(message);
32163
+ this.code = code;
32164
+ this.details = details;
32165
+ this.name = "TodosProjectRegistrationError";
32166
+ }
32167
+ }
32168
+
32169
+ // src/project-registration/postgres.ts
32170
+ function safeIdentifier(value, field2) {
32171
+ if (!/^[a-z_][a-z0-9_]*$/.test(value)) {
32172
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field2} must be a safe PostgreSQL identifier`);
32173
+ }
32174
+ return value;
32175
+ }
32176
+ function normalizeTimestamp(value) {
32177
+ return value instanceof Date ? value.toISOString() : new Date(String(value)).toISOString();
32178
+ }
32179
+ function parsePayload3(value) {
32180
+ if (typeof value === "string")
32181
+ return JSON.parse(value);
32182
+ return value;
32183
+ }
32184
+ function receiptFromRow(row) {
32185
+ return {
32186
+ ...row,
32187
+ authority: "todos",
32188
+ created_by_operation: Boolean(row["created_by_operation"]),
32189
+ created_at: normalizeTimestamp(row["created_at"])
32190
+ };
32191
+ }
32192
+ function bindingFromRow(row) {
32193
+ return {
32194
+ ...row,
32195
+ created_at: normalizeTimestamp(row["created_at"]),
32196
+ updated_at: normalizeTimestamp(row["updated_at"])
32197
+ };
32198
+ }
32199
+
32200
+ class PostgresTodosProjectRegistrationTransaction {
32201
+ client;
32202
+ service;
32203
+ tableName;
32204
+ storage;
32205
+ constructor(client, service, tableName, cursorTableName) {
32206
+ this.client = client;
32207
+ this.service = service;
32208
+ this.tableName = tableName;
32209
+ this.storage = createPostgresTodosStorageAdapter({
32210
+ client,
32211
+ service,
32212
+ tableName,
32213
+ cursorTableName
32214
+ });
32215
+ }
32216
+ async lockStep(identity) {
32217
+ const key = [
32218
+ identity.authority_id,
32219
+ identity.tenant_id,
32220
+ identity.corpus_id,
32221
+ identity.operation_id,
32222
+ identity.step_id,
32223
+ identity.resource_kind,
32224
+ identity.direction
32225
+ ].join("\x1F");
32226
+ await this.client.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [key]);
32227
+ }
32228
+ async getReceiptForLookup(identity) {
32229
+ const result = await this.client.query(`
32230
+ SELECT * FROM todos_project_registration_receipts
32231
+ WHERE authority_id = $1 AND tenant_id = $2 AND corpus_id = $3
32232
+ AND operation_id = $4 AND step_id = $5 AND resource_kind = $6
32233
+ AND direction = $7 AND idempotency_key = $8 AND target_selector = $9
32234
+ ORDER BY CASE outcome
32235
+ WHEN 'terminal_nonacceptance' THEN 0
32236
+ WHEN 'duplicate_of_accepted' THEN 1
32237
+ ELSE 2
32238
+ END, created_at DESC, receipt_id DESC
32239
+ LIMIT 1
32240
+ `, [
32241
+ identity.authority_id,
32242
+ identity.tenant_id,
32243
+ identity.corpus_id,
32244
+ identity.operation_id,
32245
+ identity.step_id,
32246
+ identity.resource_kind,
32247
+ identity.direction,
32248
+ identity.idempotency_key,
32249
+ identity.target_selector
32250
+ ]);
32251
+ return result.rows[0] ? receiptFromRow(result.rows[0]) : null;
32252
+ }
32253
+ async getReceiptById(receiptId) {
32254
+ const result = await this.client.query("SELECT * FROM todos_project_registration_receipts WHERE receipt_id = $1 LIMIT 1", [receiptId]);
32255
+ return result.rows[0] ? receiptFromRow(result.rows[0]) : null;
32256
+ }
32257
+ async getAcceptedReceiptForStep(identity) {
32258
+ const result = await this.client.query(`
32259
+ SELECT * FROM todos_project_registration_receipts
32260
+ WHERE authority_id = $1 AND tenant_id = $2 AND corpus_id = $3
32261
+ AND operation_id = $4 AND step_id = $5 AND resource_kind = $6
32262
+ AND direction = $7 AND outcome = 'accepted'
32263
+ ORDER BY created_at ASC, receipt_id ASC
32264
+ LIMIT 1
32265
+ FOR UPDATE
32266
+ `, [
32267
+ identity.authority_id,
32268
+ identity.tenant_id,
32269
+ identity.corpus_id,
32270
+ identity.operation_id,
32271
+ identity.step_id,
32272
+ identity.resource_kind,
32273
+ identity.direction
32274
+ ]);
32275
+ return result.rows[0] ? receiptFromRow(result.rows[0]) : null;
32276
+ }
32277
+ async insertReceipt(receipt) {
32278
+ const result = await this.client.query(`
32279
+ INSERT INTO todos_project_registration_receipts (
32280
+ receipt_id, authority, route, package_version, authority_id, tenant_id,
32281
+ corpus_id, operation_id, step_id, resource_kind, direction,
32282
+ target_selector, idempotency_key, request_digest, precondition_digest,
32283
+ normalized_call_digest, outcome, reason, target_id, result_revision,
32284
+ result_digest, duplicate_of_receipt_id, accepted_receipt_id,
32285
+ created_by_operation, created_at
32286
+ ) VALUES (
32287
+ $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,
32288
+ $19,$20,$21,$22,$23,$24,$25
32289
+ )
32290
+ ON CONFLICT (receipt_id) DO NOTHING
32291
+ RETURNING receipt_id
32292
+ `, [
32293
+ receipt.receipt_id,
32294
+ receipt.authority,
32295
+ receipt.route,
32296
+ receipt.package_version,
32297
+ receipt.authority_id,
32298
+ receipt.tenant_id,
32299
+ receipt.corpus_id,
32300
+ receipt.operation_id,
32301
+ receipt.step_id,
32302
+ receipt.resource_kind,
32303
+ receipt.direction,
32304
+ receipt.target_selector,
32305
+ receipt.idempotency_key,
32306
+ receipt.request_digest,
32307
+ receipt.precondition_digest,
32308
+ receipt.normalized_call_digest,
32309
+ receipt.outcome,
32310
+ receipt.reason,
32311
+ receipt.target_id,
32312
+ receipt.result_revision,
32313
+ receipt.result_digest,
32314
+ receipt.duplicate_of_receipt_id,
32315
+ receipt.accepted_receipt_id,
32316
+ receipt.created_by_operation,
32317
+ receipt.created_at
32318
+ ]);
32319
+ return result.rows.length === 1;
32320
+ }
32321
+ async getBinding(scope, resourceKind, targetSelector) {
32322
+ const result = await this.client.query(`
32323
+ SELECT * FROM todos_project_registration_bindings
32324
+ WHERE authority_id = $1 AND tenant_id = $2 AND corpus_id = $3
32325
+ AND resource_kind = $4 AND target_selector = $5
32326
+ LIMIT 1
32327
+ FOR UPDATE
32328
+ `, [
32329
+ scope.authority_id,
32330
+ scope.tenant_id,
32331
+ scope.corpus_id,
32332
+ resourceKind,
32333
+ targetSelector
32334
+ ]);
32335
+ return result.rows[0] ? bindingFromRow(result.rows[0]) : null;
32336
+ }
32337
+ async claimBinding(binding) {
32338
+ const result = await this.client.query(`
32339
+ INSERT INTO todos_project_registration_bindings (
32340
+ authority_id, tenant_id, corpus_id, resource_kind, target_selector,
32341
+ operation_id, step_id, direction, idempotency_key, request_digest,
32342
+ precondition_digest, normalized_call_digest, state, target_id,
32343
+ accepted_receipt_id, result_revision, result_digest, removed_receipt_id,
32344
+ created_at, updated_at
32345
+ ) VALUES (
32346
+ $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,
32347
+ $18,$19,$20
32348
+ )
32349
+ ON CONFLICT (
32350
+ authority_id, tenant_id, corpus_id, resource_kind, target_selector
32351
+ ) DO NOTHING
32352
+ RETURNING target_selector
32353
+ `, [
32354
+ binding.authority_id,
32355
+ binding.tenant_id,
32356
+ binding.corpus_id,
32357
+ binding.resource_kind,
32358
+ binding.target_selector,
32359
+ binding.operation_id,
32360
+ binding.step_id,
32361
+ binding.direction,
32362
+ binding.idempotency_key,
32363
+ binding.request_digest,
32364
+ binding.precondition_digest,
32365
+ binding.normalized_call_digest,
32366
+ binding.state,
32367
+ binding.target_id,
32368
+ binding.accepted_receipt_id,
32369
+ binding.result_revision,
32370
+ binding.result_digest,
32371
+ binding.removed_receipt_id,
32372
+ binding.created_at,
32373
+ binding.updated_at
32374
+ ]);
32375
+ return result.rows.length === 1;
32376
+ }
32377
+ async setBindingAccepted(scope, resourceKind, targetSelector, update) {
32378
+ const result = await this.client.query(`
32379
+ UPDATE todos_project_registration_bindings
32380
+ SET state = 'accepted', target_id = $1, accepted_receipt_id = $2,
32381
+ result_revision = $3, result_digest = $4, updated_at = $5
32382
+ WHERE authority_id = $6 AND tenant_id = $7 AND corpus_id = $8
32383
+ AND resource_kind = $9 AND target_selector = $10 AND state = 'pending'
32384
+ RETURNING target_selector
32385
+ `, [
32386
+ update.target_id,
32387
+ update.accepted_receipt_id,
32388
+ update.result_revision,
32389
+ update.result_digest,
32390
+ update.updated_at,
32391
+ scope.authority_id,
32392
+ scope.tenant_id,
32393
+ scope.corpus_id,
32394
+ resourceKind,
32395
+ targetSelector
32396
+ ]);
32397
+ if (result.rows.length !== 1) {
32398
+ throw new Error("Todos project registration binding was not pending at acceptance");
32399
+ }
32400
+ }
32401
+ async setBindingTerminal(scope, resourceKind, targetSelector, updatedAt) {
32402
+ await this.client.query(`
32403
+ UPDATE todos_project_registration_bindings
32404
+ SET state = 'terminal_nonacceptance', updated_at = $1
32405
+ WHERE authority_id = $2 AND tenant_id = $3 AND corpus_id = $4
32406
+ AND resource_kind = $5 AND target_selector = $6 AND state = 'pending'
32407
+ `, [
32408
+ updatedAt,
32409
+ scope.authority_id,
32410
+ scope.tenant_id,
32411
+ scope.corpus_id,
32412
+ resourceKind,
32413
+ targetSelector
32414
+ ]);
32415
+ }
32416
+ async setBindingRemoved(scope, resourceKind, targetSelector, removedReceiptId, updatedAt) {
32417
+ const result = await this.client.query(`
32418
+ UPDATE todos_project_registration_bindings
32419
+ SET state = 'removed', removed_receipt_id = $1, updated_at = $2
32420
+ WHERE authority_id = $3 AND tenant_id = $4 AND corpus_id = $5
32421
+ AND resource_kind = $6 AND target_selector = $7 AND state = 'accepted'
32422
+ RETURNING target_selector
32423
+ `, [
32424
+ removedReceiptId,
32425
+ updatedAt,
32426
+ scope.authority_id,
32427
+ scope.tenant_id,
32428
+ scope.corpus_id,
32429
+ resourceKind,
32430
+ targetSelector
32431
+ ]);
32432
+ if (result.rows.length !== 1) {
32433
+ throw new Error("Todos project registration binding was not accepted at removal");
32434
+ }
32435
+ }
32436
+ async findProjectConflict(path, taskListSlug) {
32437
+ const result = await this.client.query(`
32438
+ SELECT payload FROM ${this.tableName}
32439
+ WHERE service = $1 AND object_type = 'projects' AND deleted_at IS NULL
32440
+ AND (payload->>'path' = $2 OR payload->>'task_list_id' = $3)
32441
+ ORDER BY payload->>'created_at' ASC, object_id ASC
32442
+ LIMIT 1
32443
+ `, [this.service, path, taskListSlug]);
32444
+ return result.rows[0] ? parsePayload3(result.rows[0].payload) : null;
32445
+ }
32446
+ async findTaskListConflict(projectId, slug) {
32447
+ const result = await this.client.query(`
32448
+ SELECT payload FROM ${this.tableName}
32449
+ WHERE service = $1 AND object_type = 'task_lists' AND deleted_at IS NULL
32450
+ AND payload->>'project_id' = $2 AND payload->>'slug' = $3
32451
+ ORDER BY payload->>'created_at' ASC, object_id ASC
32452
+ LIMIT 1
32453
+ `, [this.service, projectId, slug]);
32454
+ return result.rows[0] ? parsePayload3(result.rows[0].payload) : null;
32455
+ }
32456
+ async createProject(input) {
32457
+ return await this.storage.projects.create(input);
32458
+ }
32459
+ async createTaskList(input) {
32460
+ return await this.storage.taskLists.create(input);
32461
+ }
32462
+ async getProject(id) {
32463
+ return await this.storage.projects.get(id);
32464
+ }
32465
+ async getTaskList(id) {
32466
+ return await this.storage.taskLists.get(id);
32467
+ }
32468
+ async lockCompensationWrites() {
32469
+ await this.client.query(`LOCK TABLE ${this.tableName} IN SHARE ROW EXCLUSIVE MODE`);
32470
+ }
32471
+ async hasDependents(resourceKind, targetId) {
32472
+ const referencePredicate = resourceKind === "project" ? `(
32473
+ payload->>'project_id' = $2
32474
+ OR payload->>'active_project_id' = $2
32475
+ OR payload->>'assigned_from_project' = $2
32476
+ OR payload->>'external_project_id' = $2
32477
+ )` : "payload->>'task_list_id' = $2";
32478
+ const result = await this.client.query(`
32479
+ SELECT EXISTS (
32480
+ SELECT 1 FROM ${this.tableName}
32481
+ WHERE service = $1 AND deleted_at IS NULL
32482
+ AND ${referencePredicate}
32483
+ LIMIT 1
32484
+ ) AS exists
32485
+ `, [this.service, targetId]);
32486
+ return result.rows[0]?.exists === true;
32487
+ }
32488
+ async deleteProject(id) {
32489
+ return await this.storage.projects.delete(id);
32490
+ }
32491
+ async deleteTaskList(id) {
32492
+ return await this.storage.taskLists.delete(id);
32493
+ }
32494
+ }
32495
+
32496
+ class PostgresTodosProjectRegistrationBackend {
32497
+ client;
32498
+ kind = "postgresql";
32499
+ service;
32500
+ tableName;
32501
+ cursorTableName;
32502
+ schemaReady = null;
32503
+ constructor(client, options = {}) {
32504
+ this.client = client;
32505
+ this.service = options.service ?? "todos";
32506
+ this.tableName = safeIdentifier(options.tableName ?? "todos_sync_records", "tableName");
32507
+ this.cursorTableName = safeIdentifier(options.cursorTableName ?? "todos_sync_cursors", "cursorTableName");
32508
+ }
32509
+ async ensureSchema() {
32510
+ this.schemaReady ??= (async () => {
32511
+ for (const statement of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
32512
+ await this.client.query(statement);
32513
+ }
32514
+ for (const statement of postgresTodosProjectRegistrationSchemaSql()) {
32515
+ await this.client.query(statement);
32516
+ }
32517
+ })();
32518
+ await this.schemaReady;
32519
+ }
32520
+ async transaction(fn) {
32521
+ await this.ensureSchema();
32522
+ if (typeof this.client.transaction !== "function") {
32523
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE", "PostgreSQL project registration requires an authoritative transaction");
32524
+ }
32525
+ return this.client.transaction((transaction) => fn(new PostgresTodosProjectRegistrationTransaction(transaction, this.service, this.tableName, this.cursorTableName)));
32526
+ }
32527
+ async direct() {
32528
+ await this.ensureSchema();
32529
+ return new PostgresTodosProjectRegistrationTransaction(this.client, this.service, this.tableName, this.cursorTableName);
32530
+ }
32531
+ async getReceiptForLookup(identity) {
32532
+ return (await this.direct()).getReceiptForLookup(identity);
32533
+ }
32534
+ async getReceiptById(receiptId) {
32535
+ return (await this.direct()).getReceiptById(receiptId);
32536
+ }
32537
+ async getBinding(scope, resourceKind, targetSelector) {
32538
+ return (await this.direct()).getBinding(scope, resourceKind, targetSelector);
32539
+ }
32540
+ async getProject(id) {
32541
+ return (await this.direct()).getProject(id);
32542
+ }
32543
+ async getTaskList(id) {
32544
+ return (await this.direct()).getTaskList(id);
32545
+ }
32546
+ }
32547
+
32548
+ // src/project-registration/sqlite.ts
32549
+ init_database();
32550
+ init_storage_tombstones();
32551
+ var sqliteTransactionTails2 = new WeakMap;
32552
+ var PROJECT_REFERENCE_COLUMNS = new Set([
32553
+ "project_id",
32554
+ "active_project_id",
32555
+ "assigned_from_project",
32556
+ "external_project_id"
32557
+ ]);
32558
+ var TASK_LIST_REFERENCE_COLUMNS = new Set(["task_list_id"]);
32559
+ var SQLITE_TRANSACTION_RETRY_LIMIT = 8;
32560
+
32561
+ class SqliteRegistrationOptimisticConflict extends Error {
32562
+ constructor(message, options = {}) {
32563
+ super(message, options);
32564
+ this.name = "SqliteRegistrationOptimisticConflict";
32565
+ }
32566
+ }
32567
+ function sameSqliteValue(left, right) {
32568
+ return JSON.stringify(left) === JSON.stringify(right);
32569
+ }
32570
+ function taskListFromRow(row) {
32571
+ return {
32572
+ ...row,
32573
+ metadata: JSON.parse(row.metadata || "{}")
32574
+ };
32575
+ }
32576
+ function selectProject(db, id) {
32577
+ return db.query("SELECT * FROM projects WHERE id = ? LIMIT 1").get(id);
32578
+ }
32579
+ function selectTaskList(db, id) {
32580
+ const row = db.query("SELECT * FROM task_lists WHERE id = ? LIMIT 1").get(id);
32581
+ return row ? taskListFromRow(row) : null;
32582
+ }
32583
+ function selectProjectConflict(db, path, taskListSlug) {
32584
+ return db.query(`
32585
+ SELECT * FROM projects
32586
+ WHERE path = ? OR task_list_id = ?
32587
+ ORDER BY created_at ASC, id ASC
32588
+ LIMIT 1
32589
+ `).get(path, taskListSlug);
32590
+ }
32591
+ function selectTaskListConflict(db, projectId, slug) {
32592
+ const row = db.query(`
32593
+ SELECT * FROM task_lists
32594
+ WHERE project_id = ? AND slug = ?
32595
+ LIMIT 1
32596
+ `).get(projectId, slug);
32597
+ return row ? taskListFromRow(row) : null;
32598
+ }
32599
+ function quoteSqliteIdentifier(value) {
32600
+ return `"${value.replaceAll('"', '""')}"`;
32601
+ }
32602
+ function hasSqliteDependents(db, resourceKind, targetId) {
32603
+ const targetTable = resourceKind === "project" ? "projects" : "task_lists";
32604
+ const semanticColumns = resourceKind === "project" ? PROJECT_REFERENCE_COLUMNS : TASK_LIST_REFERENCE_COLUMNS;
32605
+ const tables = db.query(`
32606
+ SELECT name FROM sqlite_schema
32607
+ WHERE type = 'table' AND name NOT LIKE 'sqlite_%'
32608
+ ORDER BY name
32609
+ `).all();
32610
+ for (const { name: tableName } of tables) {
32611
+ const quotedTable = quoteSqliteIdentifier(tableName);
32612
+ const columns = db.query(`PRAGMA table_info(${quotedTable})`).all();
32613
+ const foreignKeys = db.query(`PRAGMA foreign_key_list(${quotedTable})`).all();
32614
+ const referenceColumns = columns.map((column) => column.name).filter((columnName) => semanticColumns.has(columnName) || foreignKeys.some((foreignKey) => foreignKey.from === columnName && foreignKey.table === targetTable));
32615
+ for (const columnName of referenceColumns) {
32616
+ const row = db.query(`
32617
+ SELECT 1 AS found
32618
+ FROM ${quotedTable}
32619
+ WHERE ${quoteSqliteIdentifier(columnName)} = ?
32620
+ LIMIT 1
32621
+ `).get(targetId);
32622
+ if (row)
32623
+ return true;
32624
+ }
32625
+ }
32626
+ return false;
32627
+ }
32628
+ function receiptFromRow2(row) {
32629
+ return {
32630
+ ...row,
32631
+ authority: "todos",
32632
+ created_by_operation: Number(row["created_by_operation"]) === 1
32633
+ };
32634
+ }
32635
+ function bindingFromRow2(row) {
32636
+ return row;
32637
+ }
32638
+
32639
+ class SqliteTodosProjectRegistrationTransaction {
32640
+ db;
32641
+ storage;
32642
+ constructor(db) {
32643
+ this.db = db;
32644
+ this.storage = createLocalSqliteTodosStorageAdapter({ db });
32645
+ }
32646
+ async lockStep(_identity) {}
32647
+ async getReceiptForLookup(identity) {
32648
+ const row = this.db.query(`
32649
+ SELECT * FROM todos_project_registration_receipts
32650
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
32651
+ AND operation_id = ? AND step_id = ? AND resource_kind = ?
32652
+ AND direction = ? AND idempotency_key = ? AND target_selector = ?
32653
+ ORDER BY CASE outcome
32654
+ WHEN 'terminal_nonacceptance' THEN 0
32655
+ WHEN 'duplicate_of_accepted' THEN 1
32656
+ ELSE 2
32657
+ END, created_at DESC, receipt_id DESC
32658
+ LIMIT 1
32659
+ `).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);
32660
+ return row ? receiptFromRow2(row) : null;
32661
+ }
32662
+ async getReceiptById(receiptId) {
32663
+ const row = this.db.query("SELECT * FROM todos_project_registration_receipts WHERE receipt_id = ? LIMIT 1").get(receiptId);
32664
+ return row ? receiptFromRow2(row) : null;
32665
+ }
32666
+ async getAcceptedReceiptForStep(identity) {
32667
+ const row = this.db.query(`
32668
+ SELECT * FROM todos_project_registration_receipts
32669
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
32670
+ AND operation_id = ? AND step_id = ? AND resource_kind = ?
32671
+ AND direction = ? AND outcome = 'accepted'
32672
+ ORDER BY created_at ASC, receipt_id ASC
32673
+ LIMIT 1
32674
+ `).get(identity.authority_id, identity.tenant_id, identity.corpus_id, identity.operation_id, identity.step_id, identity.resource_kind, identity.direction);
32675
+ return row ? receiptFromRow2(row) : null;
32676
+ }
32677
+ async insertReceipt(receipt) {
32678
+ const result = this.db.query(`
32679
+ INSERT OR IGNORE INTO todos_project_registration_receipts (
32680
+ receipt_id, authority, route, package_version, authority_id, tenant_id,
32681
+ corpus_id, operation_id, step_id, resource_kind, direction,
32682
+ target_selector, idempotency_key, request_digest, precondition_digest,
32683
+ normalized_call_digest, outcome, reason, target_id, result_revision,
32684
+ result_digest, duplicate_of_receipt_id, accepted_receipt_id,
32685
+ created_by_operation, created_at
32686
+ ) VALUES (
32687
+ ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?
32688
+ )
32689
+ `).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);
32690
+ return result.changes === 1;
32691
+ }
32692
+ async getBinding(scope, resourceKind, targetSelector) {
32693
+ const row = this.db.query(`
32694
+ SELECT * FROM todos_project_registration_bindings
32695
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
32696
+ AND resource_kind = ? AND target_selector = ?
32697
+ LIMIT 1
32698
+ `).get(scope.authority_id, scope.tenant_id, scope.corpus_id, resourceKind, targetSelector);
32699
+ return row ? bindingFromRow2(row) : null;
32700
+ }
32701
+ async claimBinding(binding) {
32702
+ const result = this.db.query(`
32703
+ INSERT OR IGNORE INTO todos_project_registration_bindings (
32704
+ authority_id, tenant_id, corpus_id, resource_kind, target_selector,
32705
+ operation_id, step_id, direction, idempotency_key, request_digest,
32706
+ precondition_digest, normalized_call_digest, state, target_id,
32707
+ accepted_receipt_id, result_revision, result_digest, removed_receipt_id,
32708
+ created_at, updated_at
32709
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
32710
+ `).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);
32711
+ return result.changes === 1;
32712
+ }
32713
+ async setBindingAccepted(scope, resourceKind, targetSelector, update) {
32714
+ const result = this.db.query(`
32715
+ UPDATE todos_project_registration_bindings
32716
+ SET state = 'accepted', target_id = ?, accepted_receipt_id = ?,
32717
+ result_revision = ?, result_digest = ?, updated_at = ?
32718
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
32719
+ AND resource_kind = ? AND target_selector = ? AND state = 'pending'
32720
+ `).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);
32721
+ if (result.changes !== 1) {
32722
+ throw new Error("Todos project registration binding was not pending at acceptance");
32723
+ }
32724
+ }
32725
+ async setBindingTerminal(scope, resourceKind, targetSelector, updatedAt) {
32726
+ this.db.query(`
32727
+ UPDATE todos_project_registration_bindings
32728
+ SET state = 'terminal_nonacceptance', updated_at = ?
32729
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
32730
+ AND resource_kind = ? AND target_selector = ? AND state = 'pending'
32731
+ `).run(updatedAt, scope.authority_id, scope.tenant_id, scope.corpus_id, resourceKind, targetSelector);
32732
+ }
32733
+ async setBindingRemoved(scope, resourceKind, targetSelector, removedReceiptId, updatedAt) {
32734
+ const result = this.db.query(`
32735
+ UPDATE todos_project_registration_bindings
32736
+ SET state = 'removed', removed_receipt_id = ?, updated_at = ?
32737
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
32738
+ AND resource_kind = ? AND target_selector = ? AND state = 'accepted'
32739
+ `).run(removedReceiptId, updatedAt, scope.authority_id, scope.tenant_id, scope.corpus_id, resourceKind, targetSelector);
32740
+ if (result.changes !== 1) {
32741
+ throw new Error("Todos project registration binding was not accepted at removal");
32742
+ }
32743
+ }
32744
+ async findProjectConflict(path, taskListSlug) {
32745
+ const row = this.db.query(`
32746
+ SELECT * FROM projects
32747
+ WHERE path = ? OR task_list_id = ?
32748
+ ORDER BY created_at ASC, id ASC
32749
+ LIMIT 1
32750
+ `).get(path, taskListSlug);
32751
+ return row ?? null;
32752
+ }
32753
+ async findTaskListConflict(projectId, slug) {
32754
+ return await this.storage.taskLists.getBySlug(slug, projectId);
32755
+ }
32756
+ async createProject(input) {
32757
+ return await this.storage.projects.create(input);
32758
+ }
32759
+ async createTaskList(input) {
32760
+ return await this.storage.taskLists.create(input);
32761
+ }
32762
+ async getProject(id) {
32763
+ return await this.storage.projects.get(id);
32764
+ }
32765
+ async getTaskList(id) {
32766
+ return await this.storage.taskLists.get(id);
32767
+ }
32768
+ async lockCompensationWrites() {}
32769
+ async hasDependents(resourceKind, targetId) {
32770
+ return hasSqliteDependents(this.db, resourceKind, targetId);
32771
+ }
32772
+ async deleteProject(id) {
32773
+ return await this.storage.projects.delete(id);
32774
+ }
32775
+ async deleteTaskList(id) {
32776
+ return await this.storage.taskLists.delete(id);
32777
+ }
32778
+ }
32779
+
32780
+ class StagedSqliteTodosProjectRegistrationTransaction {
32781
+ db;
32782
+ direct;
32783
+ validators = [];
32784
+ mutations = [];
32785
+ receipts = new Map;
32786
+ bindings = new Map;
32787
+ projects = new Map;
32788
+ taskLists = new Map;
32789
+ constructor(db) {
32790
+ this.db = db;
32791
+ this.direct = new SqliteTodosProjectRegistrationTransaction(db);
32792
+ }
32793
+ commit() {
32794
+ this.db.exec("BEGIN IMMEDIATE");
32795
+ try {
32796
+ for (const validate of this.validators) {
32797
+ if (!validate()) {
32798
+ throw new SqliteRegistrationOptimisticConflict("Todos project registration input changed before SQLite commit");
32799
+ }
32800
+ }
32801
+ for (const mutate of this.mutations)
32802
+ mutate();
32803
+ this.db.exec("COMMIT");
32804
+ } catch (error) {
32805
+ try {
32806
+ this.db.exec("ROLLBACK");
32807
+ } catch {}
32808
+ throw error;
32809
+ }
32810
+ }
32811
+ async lockStep(_identity) {}
32812
+ async getReceiptForLookup(identity) {
32813
+ 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);
32814
+ const stored = await this.direct.getReceiptForLookup(identity);
32815
+ if (stored)
32816
+ staged.push(stored);
32817
+ const outcomeRank = (receipt) => receipt.outcome === "terminal_nonacceptance" ? 0 : receipt.outcome === "duplicate_of_accepted" ? 1 : 2;
32818
+ 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;
32819
+ }
32820
+ async getReceiptById(receiptId) {
32821
+ return this.receipts.get(receiptId) ?? this.direct.getReceiptById(receiptId);
32822
+ }
32823
+ async getAcceptedReceiptForStep(identity) {
32824
+ 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");
32825
+ const stored = await this.direct.getAcceptedReceiptForStep(identity);
32826
+ if (stored)
32827
+ staged.push(stored);
32828
+ return staged.sort((left, right) => left.created_at.localeCompare(right.created_at) || left.receipt_id.localeCompare(right.receipt_id))[0] ?? null;
32829
+ }
32830
+ async insertReceipt(receipt) {
32831
+ if (this.receipts.has(receipt.receipt_id))
32832
+ return false;
32833
+ if (await this.direct.getReceiptById(receipt.receipt_id))
32834
+ return false;
32835
+ const planned = { ...receipt };
32836
+ this.receipts.set(planned.receipt_id, planned);
32837
+ this.mutations.push(() => {
32838
+ try {
32839
+ const result = this.db.query(`
32840
+ INSERT OR IGNORE INTO todos_project_registration_receipts (
32841
+ receipt_id, authority, route, package_version, authority_id, tenant_id,
32842
+ corpus_id, operation_id, step_id, resource_kind, direction,
32843
+ target_selector, idempotency_key, request_digest, precondition_digest,
32844
+ normalized_call_digest, outcome, reason, target_id, result_revision,
32845
+ result_digest, duplicate_of_receipt_id, accepted_receipt_id,
32846
+ created_by_operation, created_at
32847
+ ) VALUES (
32848
+ ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?
32849
+ )
32850
+ `).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);
32851
+ if (result.changes !== 1) {
32852
+ throw new SqliteRegistrationOptimisticConflict("Todos project registration receipt changed before SQLite commit");
32853
+ }
32854
+ } catch (error) {
32855
+ if (error instanceof SqliteRegistrationOptimisticConflict)
32856
+ throw error;
32857
+ throw new SqliteRegistrationOptimisticConflict("Todos project registration receipt conflicted at SQLite commit", { cause: error });
32858
+ }
32859
+ });
32860
+ return true;
32861
+ }
32862
+ async getBinding(scope, resourceKind, targetSelector) {
32863
+ const key = this.bindingKey(scope, resourceKind, targetSelector);
32864
+ return this.bindings.get(key) ?? this.direct.getBinding(scope, resourceKind, targetSelector);
32865
+ }
32866
+ async claimBinding(binding) {
32867
+ const key = this.bindingKey(binding, binding.resource_kind, binding.target_selector);
32868
+ if (this.bindings.has(key))
32869
+ return false;
32870
+ if (await this.direct.getBinding(binding, binding.resource_kind, binding.target_selector)) {
32871
+ return false;
32872
+ }
32873
+ const planned = { ...binding };
32874
+ this.bindings.set(key, planned);
32875
+ this.mutations.push(() => {
32876
+ try {
32877
+ const result = this.db.query(`
32878
+ INSERT OR IGNORE INTO todos_project_registration_bindings (
32879
+ authority_id, tenant_id, corpus_id, resource_kind, target_selector,
32880
+ operation_id, step_id, direction, idempotency_key, request_digest,
32881
+ precondition_digest, normalized_call_digest, state, target_id,
32882
+ accepted_receipt_id, result_revision, result_digest, removed_receipt_id,
32883
+ created_at, updated_at
32884
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
32885
+ `).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);
32886
+ if (result.changes !== 1) {
32887
+ throw new SqliteRegistrationOptimisticConflict("Todos project registration binding changed before SQLite commit");
32888
+ }
32889
+ } catch (error) {
32890
+ if (error instanceof SqliteRegistrationOptimisticConflict)
32891
+ throw error;
32892
+ throw new SqliteRegistrationOptimisticConflict("Todos project registration binding conflicted at SQLite commit", { cause: error });
32893
+ }
32894
+ });
32895
+ return true;
32896
+ }
32897
+ async setBindingAccepted(scope, resourceKind, targetSelector, update) {
32898
+ const binding = await this.requireBinding(scope, resourceKind, targetSelector, "pending");
32899
+ this.bindings.set(this.bindingKey(scope, resourceKind, targetSelector), {
32900
+ ...binding,
32901
+ state: "accepted",
32902
+ target_id: update.target_id,
32903
+ accepted_receipt_id: update.accepted_receipt_id,
32904
+ result_revision: update.result_revision,
32905
+ result_digest: update.result_digest,
32906
+ updated_at: update.updated_at
32907
+ });
32908
+ this.mutations.push(() => {
32909
+ const result = this.db.query(`
32910
+ UPDATE todos_project_registration_bindings
32911
+ SET state = 'accepted', target_id = ?, accepted_receipt_id = ?,
32912
+ result_revision = ?, result_digest = ?, updated_at = ?
32913
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
32914
+ AND resource_kind = ? AND target_selector = ? AND state = 'pending'
32915
+ `).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);
32916
+ if (result.changes !== 1) {
32917
+ throw new SqliteRegistrationOptimisticConflict("Todos project registration binding was no longer pending at SQLite commit");
32918
+ }
32919
+ });
32920
+ }
32921
+ async setBindingTerminal(scope, resourceKind, targetSelector, updatedAt) {
32922
+ const binding = await this.requireBinding(scope, resourceKind, targetSelector, "pending");
32923
+ this.bindings.set(this.bindingKey(scope, resourceKind, targetSelector), {
32924
+ ...binding,
32925
+ state: "terminal_nonacceptance",
32926
+ updated_at: updatedAt
32927
+ });
32928
+ this.mutations.push(() => {
32929
+ const result = this.db.query(`
32930
+ UPDATE todos_project_registration_bindings
32931
+ SET state = 'terminal_nonacceptance', updated_at = ?
32932
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
32933
+ AND resource_kind = ? AND target_selector = ? AND state = 'pending'
32934
+ `).run(updatedAt, scope.authority_id, scope.tenant_id, scope.corpus_id, resourceKind, targetSelector);
32935
+ if (result.changes !== 1) {
32936
+ throw new SqliteRegistrationOptimisticConflict("Todos project registration binding was no longer pending at SQLite commit");
32937
+ }
32938
+ });
32939
+ }
32940
+ async setBindingRemoved(scope, resourceKind, targetSelector, removedReceiptId, updatedAt) {
32941
+ const binding = await this.requireBinding(scope, resourceKind, targetSelector, "accepted");
32942
+ this.bindings.set(this.bindingKey(scope, resourceKind, targetSelector), {
32943
+ ...binding,
32944
+ state: "removed",
32945
+ removed_receipt_id: removedReceiptId,
32946
+ updated_at: updatedAt
32947
+ });
32948
+ this.mutations.push(() => {
32949
+ const result = this.db.query(`
32950
+ UPDATE todos_project_registration_bindings
32951
+ SET state = 'removed', removed_receipt_id = ?, updated_at = ?
32952
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
32953
+ AND resource_kind = ? AND target_selector = ? AND state = 'accepted'
32954
+ `).run(removedReceiptId, updatedAt, scope.authority_id, scope.tenant_id, scope.corpus_id, resourceKind, targetSelector);
32955
+ if (result.changes !== 1) {
32956
+ throw new SqliteRegistrationOptimisticConflict("Todos project registration binding was no longer accepted at SQLite commit");
32957
+ }
32958
+ });
32959
+ }
32960
+ async findProjectConflict(path, taskListSlug) {
32961
+ const planned = [...this.projects.values()].find((project) => project?.path === path || project?.task_list_id === taskListSlug);
32962
+ if (planned)
32963
+ return planned;
32964
+ const observed = selectProjectConflict(this.db, path, taskListSlug);
32965
+ this.validators.push(() => sameSqliteValue(selectProjectConflict(this.db, path, taskListSlug), observed));
32966
+ return observed;
32967
+ }
32968
+ async findTaskListConflict(projectId, slug) {
32969
+ const planned = [...this.taskLists.values()].find((taskList) => taskList?.project_id === projectId && taskList.slug === slug);
32970
+ if (planned)
32971
+ return planned;
32972
+ const observed = selectTaskListConflict(this.db, projectId, slug);
32973
+ this.validators.push(() => sameSqliteValue(selectTaskListConflict(this.db, projectId, slug), observed));
32974
+ return observed;
32975
+ }
32976
+ async createProject(input) {
32977
+ const derivedSlug = normalizeSlug(input.name);
32978
+ const taskListId = input.task_list_id === undefined ? `todos-${derivedSlug}` : normalizeSlug(input.task_list_id);
32979
+ if (!derivedSlug || !taskListId) {
32980
+ throw new Error("Project name and task-list slug must be non-empty");
32981
+ }
32982
+ const project = {
32983
+ id: uuid(),
32984
+ name: input.name,
32985
+ path: input.path,
32986
+ description: input.description || null,
32987
+ task_list_id: taskListId,
32988
+ task_prefix: input.task_prefix ?? this.availableProjectPrefix(input.name),
32989
+ task_counter: 0,
32990
+ created_at: now(),
32991
+ updated_at: now(),
32992
+ machine_id: currentStorageMachineId(this.db)
32993
+ };
32994
+ project.updated_at = project.created_at;
32995
+ this.projects.set(project.id, project);
32996
+ this.mutations.push(() => {
32997
+ try {
32998
+ const result = this.db.run(`INSERT INTO projects (
32999
+ id, name, path, description, task_list_id, task_prefix,
33000
+ task_counter, created_at, updated_at, machine_id
33001
+ ) VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)`, [
33002
+ project.id,
33003
+ project.name,
33004
+ project.path,
33005
+ project.description,
33006
+ project.task_list_id,
33007
+ project.task_prefix,
33008
+ project.created_at,
33009
+ project.updated_at,
33010
+ project.machine_id ?? null
33011
+ ]);
33012
+ if (result.changes < 1) {
33013
+ throw new SqliteRegistrationOptimisticConflict("Todos project changed before SQLite registration commit");
33014
+ }
33015
+ } catch (error) {
33016
+ if (error instanceof SqliteRegistrationOptimisticConflict)
33017
+ throw error;
33018
+ throw new SqliteRegistrationOptimisticConflict("Todos project conflicted at SQLite registration commit", { cause: error });
33019
+ }
33020
+ });
33021
+ return project;
33022
+ }
33023
+ async createTaskList(input) {
33024
+ const slug = normalizeSlug(input.slug === undefined ? input.name : input.slug);
33025
+ if (!slug)
33026
+ throw new Error("Invalid task-list slug \u2014 must be non-empty kebab-case");
33027
+ const taskList = {
33028
+ id: uuid(),
33029
+ project_id: input.project_id || null,
33030
+ slug,
33031
+ name: input.name,
33032
+ description: input.description || null,
33033
+ metadata: input.metadata ?? {},
33034
+ created_at: now(),
33035
+ updated_at: now(),
33036
+ machine_id: currentStorageMachineId(this.db)
33037
+ };
33038
+ taskList.updated_at = taskList.created_at;
33039
+ this.taskLists.set(taskList.id, taskList);
33040
+ this.mutations.push(() => {
33041
+ try {
33042
+ const result = this.db.run(`INSERT INTO task_lists (
33043
+ id, project_id, slug, name, description, metadata,
33044
+ created_at, updated_at, machine_id
33045
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
33046
+ taskList.id,
33047
+ taskList.project_id,
33048
+ taskList.slug,
33049
+ taskList.name,
33050
+ taskList.description,
33051
+ JSON.stringify(taskList.metadata),
33052
+ taskList.created_at,
33053
+ taskList.updated_at,
33054
+ taskList.machine_id ?? null
33055
+ ]);
33056
+ if (result.changes < 1) {
33057
+ throw new SqliteRegistrationOptimisticConflict("Todos task list changed before SQLite registration commit");
33058
+ }
33059
+ } catch (error) {
33060
+ if (error instanceof SqliteRegistrationOptimisticConflict)
33061
+ throw error;
33062
+ throw new SqliteRegistrationOptimisticConflict("Todos task list conflicted at SQLite registration commit", { cause: error });
33063
+ }
33064
+ });
33065
+ return taskList;
33066
+ }
33067
+ async getProject(id) {
33068
+ if (this.projects.has(id))
33069
+ return this.projects.get(id) ?? null;
33070
+ const observed = selectProject(this.db, id);
33071
+ this.validators.push(() => sameSqliteValue(selectProject(this.db, id), observed));
33072
+ return observed;
33073
+ }
33074
+ async getTaskList(id) {
33075
+ if (this.taskLists.has(id))
33076
+ return this.taskLists.get(id) ?? null;
33077
+ const observed = selectTaskList(this.db, id);
33078
+ this.validators.push(() => sameSqliteValue(selectTaskList(this.db, id), observed));
33079
+ return observed;
33080
+ }
33081
+ async lockCompensationWrites() {}
33082
+ async hasDependents(resourceKind, targetId) {
33083
+ const observed = hasSqliteDependents(this.db, resourceKind, targetId);
33084
+ this.validators.push(() => hasSqliteDependents(this.db, resourceKind, targetId) === observed);
33085
+ return observed;
33086
+ }
33087
+ async deleteProject(id) {
33088
+ const project = await this.getProject(id);
33089
+ if (!project)
33090
+ return false;
33091
+ this.projects.set(id, null);
33092
+ this.mutations.push(() => {
33093
+ recordStorageTombstone({
33094
+ object_type: "projects",
33095
+ object_id: id,
33096
+ payload: project
33097
+ }, this.db);
33098
+ if (this.db.run("DELETE FROM projects WHERE id = ?", [id]).changes < 1) {
33099
+ throw new SqliteRegistrationOptimisticConflict("Todos project changed before SQLite compensation commit");
33100
+ }
33101
+ });
33102
+ return true;
33103
+ }
33104
+ async deleteTaskList(id) {
33105
+ const taskList = await this.getTaskList(id);
33106
+ if (!taskList)
33107
+ return false;
33108
+ this.taskLists.set(id, null);
33109
+ this.mutations.push(() => {
33110
+ recordStorageTombstone({
33111
+ object_type: "task_lists",
33112
+ object_id: id,
33113
+ payload: taskList
33114
+ }, this.db);
33115
+ if (this.db.run("DELETE FROM task_lists WHERE id = ?", [id]).changes < 1) {
33116
+ throw new SqliteRegistrationOptimisticConflict("Todos task list changed before SQLite compensation commit");
33117
+ }
33118
+ });
33119
+ return true;
33120
+ }
33121
+ bindingKey(scope, resourceKind, targetSelector) {
33122
+ return JSON.stringify([
33123
+ scope.authority_id,
33124
+ scope.tenant_id,
33125
+ scope.corpus_id,
33126
+ resourceKind,
33127
+ targetSelector
33128
+ ]);
33129
+ }
33130
+ async requireBinding(scope, resourceKind, targetSelector, state) {
33131
+ const binding = await this.getBinding(scope, resourceKind, targetSelector);
33132
+ if (!binding || binding.state !== state) {
33133
+ throw new Error(`Todos project registration binding was not ${state}`);
33134
+ }
33135
+ return binding;
33136
+ }
33137
+ availableProjectPrefix(name) {
33138
+ const words = name.replace(/[^a-zA-Z0-9\s]/g, "").trim().split(/\s+/);
33139
+ 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();
33140
+ let candidate = prefix;
33141
+ let suffix = 1;
33142
+ while (this.db.query("SELECT id FROM projects WHERE task_prefix = ? LIMIT 1").get(candidate) || [...this.projects.values()].some((project) => project?.task_prefix === candidate)) {
33143
+ suffix += 1;
33144
+ candidate = `${prefix}${suffix}`;
33145
+ }
33146
+ return candidate;
33147
+ }
33148
+ }
33149
+
33150
+ class SqliteTodosProjectRegistrationBackend {
33151
+ db;
33152
+ kind = "sqlite";
33153
+ direct;
33154
+ constructor(db) {
33155
+ this.db = db;
33156
+ db.exec(sqliteTodosProjectRegistrationSchemaSql());
33157
+ this.direct = new SqliteTodosProjectRegistrationTransaction(db);
33158
+ }
33159
+ async transaction(fn) {
33160
+ const previous = sqliteTransactionTails2.get(this.db) ?? Promise.resolve();
33161
+ let release;
33162
+ const current = new Promise((resolve10) => {
33163
+ release = resolve10;
33164
+ });
33165
+ sqliteTransactionTails2.set(this.db, current);
33166
+ await previous;
33167
+ try {
33168
+ for (let attempt = 0;attempt < SQLITE_TRANSACTION_RETRY_LIMIT; attempt += 1) {
33169
+ const transaction = new StagedSqliteTodosProjectRegistrationTransaction(this.db);
33170
+ const result = await fn(transaction);
33171
+ try {
33172
+ transaction.commit();
33173
+ return result;
33174
+ } catch (error) {
33175
+ if (!(error instanceof SqliteRegistrationOptimisticConflict) || attempt === SQLITE_TRANSACTION_RETRY_LIMIT - 1) {
33176
+ throw error;
33177
+ }
33178
+ }
33179
+ }
33180
+ throw new SqliteRegistrationOptimisticConflict("Todos project registration exhausted SQLite optimistic retries");
33181
+ } finally {
33182
+ release();
33183
+ if (sqliteTransactionTails2.get(this.db) === current) {
33184
+ sqliteTransactionTails2.delete(this.db);
33185
+ }
33186
+ }
33187
+ }
33188
+ getReceiptForLookup(identity) {
33189
+ return this.direct.getReceiptForLookup(identity);
33190
+ }
33191
+ getReceiptById(receiptId) {
33192
+ return this.direct.getReceiptById(receiptId);
33193
+ }
33194
+ getBinding(scope, resourceKind, targetSelector) {
33195
+ return this.direct.getBinding(scope, resourceKind, targetSelector);
33196
+ }
33197
+ getProject(id) {
33198
+ return this.direct.getProject(id);
33199
+ }
33200
+ getTaskList(id) {
33201
+ return this.direct.getTaskList(id);
33202
+ }
33203
+ }
33204
+
33205
+ // src/project-registration/authority.ts
33206
+ var 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;
33207
+ var WORKSPACE_ID_PATTERN = /^wks_[A-Za-z0-9][A-Za-z0-9_-]{11,}$/;
33208
+ var OPERATION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
33209
+ var STEP_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$/;
33210
+ var SHA256_PATTERN = /^[0-9a-f]{64}$/;
33211
+ var IDEMPOTENCY_PATTERN = /^prk_[0-9a-f]{48}$/;
33212
+
33213
+ class WriteBoundaryError extends Error {
33214
+ point;
33215
+ cause;
33216
+ constructor(point, cause) {
33217
+ super(`Todos project registration failed at ${point}`);
33218
+ this.point = point;
33219
+ this.cause = cause;
33220
+ }
33221
+ }
33222
+ function canonicalProjectRegistrationJson(value) {
33223
+ return JSON.stringify(canonicalize3(value));
33224
+ }
33225
+ function canonicalize3(value) {
33226
+ if (Array.isArray(value))
33227
+ return value.map(canonicalize3);
33228
+ if (!value || typeof value !== "object")
33229
+ return value;
33230
+ const out = {};
33231
+ for (const key of Object.keys(value).sort()) {
33232
+ const entry2 = value[key];
33233
+ if (entry2 !== undefined)
33234
+ out[key] = canonicalize3(entry2);
33235
+ }
33236
+ return out;
33237
+ }
33238
+ function digestProjectRegistrationValue(value) {
33239
+ return createHash12("sha256").update(canonicalProjectRegistrationJson(value)).digest("hex");
33240
+ }
33241
+ function deriveTodosProjectRegistrationIdempotencyKey(input) {
33242
+ return `prk_${digestProjectRegistrationValue({
33243
+ route: TODOS_PROJECT_REGISTRATION_CALLER_ROUTE,
33244
+ ...input
33245
+ }).slice(0, 48)}`;
33246
+ }
33247
+ function responseBytes(value) {
33248
+ return Buffer.byteLength(JSON.stringify(value), "utf8");
33249
+ }
33250
+ function assertBounds(bounds) {
33251
+ if (!Number.isSafeInteger(bounds.response_byte_limit) || bounds.response_byte_limit <= 0) {
33252
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "response_byte_limit must be a positive integer");
33253
+ }
33254
+ if (!Number.isSafeInteger(bounds.time_budget_ms) || bounds.time_budget_ms <= 0) {
33255
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "time_budget_ms must be a positive integer");
33256
+ }
33257
+ }
33258
+ function assertResourceKind(value) {
33259
+ if (value !== "project" && value !== "task_list") {
33260
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "resource_kind must be project or task_list");
33261
+ }
33262
+ }
33263
+ function assertDirection(value) {
33264
+ if (value !== "forward" && value !== "inverse") {
33265
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "direction must be forward or inverse");
33266
+ }
33267
+ }
33268
+ function assertWithinBounds(value, bounds, startedAt) {
33269
+ const bytes = responseBytes(value);
33270
+ if (bytes > bounds.response_byte_limit) {
33271
+ 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 });
33272
+ }
33273
+ const elapsed = Date.now() - startedAt;
33274
+ if (elapsed > bounds.time_budget_ms) {
33275
+ 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 });
33276
+ }
33277
+ return { response_bytes: bytes, elapsed_ms: elapsed };
33278
+ }
33279
+ function withResponseControl(payload, bounds, startedAt) {
33280
+ const envelope = {
33281
+ ...payload,
33282
+ response_control: {
33283
+ response_byte_limit: bounds.response_byte_limit,
33284
+ time_budget_ms: bounds.time_budget_ms,
33285
+ response_bytes: 0,
33286
+ elapsed_ms: 0,
33287
+ complete: true,
33288
+ truncated: false
33289
+ }
33290
+ };
33291
+ for (let attempt = 0;attempt < 8; attempt += 1) {
33292
+ const measured = assertWithinBounds(envelope, bounds, startedAt);
33293
+ const stable2 = envelope.response_control.response_bytes === measured.response_bytes && envelope.response_control.elapsed_ms === measured.elapsed_ms;
33294
+ envelope.response_control = {
33295
+ response_byte_limit: bounds.response_byte_limit,
33296
+ time_budget_ms: bounds.time_budget_ms,
33297
+ response_bytes: measured.response_bytes,
33298
+ elapsed_ms: measured.elapsed_ms,
33299
+ complete: true,
33300
+ truncated: false
33301
+ };
33302
+ if (stable2)
33303
+ break;
33304
+ }
33305
+ const finalMeasurement = assertWithinBounds(envelope, bounds, startedAt);
33306
+ envelope.response_control.response_bytes = finalMeasurement.response_bytes;
33307
+ envelope.response_control.elapsed_ms = finalMeasurement.elapsed_ms;
33308
+ return envelope;
33309
+ }
33310
+ function requireString(value, field2, options = {}) {
33311
+ const min = options.min ?? 1;
33312
+ const max = options.max ?? 512;
33313
+ if (typeof value !== "string" || value.length < min || value.length > max || /[\u0000-\u001f]/.test(value) || options.pattern && !options.pattern.test(value)) {
33314
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field2} is not a valid bounded registration identifier`);
33315
+ }
33316
+ return value;
33317
+ }
33318
+ function exactKeys(value, expected, field2) {
33319
+ const actual = Object.keys(value).sort();
33320
+ const wanted = [...expected].sort();
33321
+ if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) {
33322
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field2} must contain exactly: ${wanted.join(", ")}`);
33323
+ }
33324
+ }
33325
+ function publicReceipt(row) {
33326
+ const {
33327
+ target_selector: _targetSelector,
33328
+ normalized_call_digest: _normalizedCallDigest,
33329
+ ...receipt
33330
+ } = row;
33331
+ return receipt;
33332
+ }
33333
+ function projectRegistrationPath(projectId) {
33334
+ return `hasna-project://${encodeURIComponent(projectId)}`;
33335
+ }
33336
+ function taskListSlug(projectSlug) {
33337
+ const slug = normalizeSlug(projectSlug);
33338
+ if (!slug || slug !== projectSlug) {
33339
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "project_slug must be canonical kebab-case");
33340
+ }
33341
+ return `todos-${slug}`;
33342
+ }
33343
+ function deterministicTaskPrefix(projectSlug) {
33344
+ const letters = projectSlug.replace(/[^a-z0-9]/gi, "").toUpperCase();
33345
+ return (letters.slice(0, 3) || "PRJ").padEnd(3, "X");
33346
+ }
33347
+ function projectRecord(project) {
33348
+ return {
33349
+ target_id: project.id,
33350
+ revision: project.updated_at,
33351
+ digest: digestProjectRegistrationValue({
33352
+ id: project.id,
33353
+ name: project.name,
33354
+ path: project.path,
33355
+ description: project.description,
33356
+ task_list_id: project.task_list_id,
33357
+ task_prefix: project.task_prefix,
33358
+ task_counter: project.task_counter,
33359
+ created_at: project.created_at,
33360
+ updated_at: project.updated_at
33361
+ })
33362
+ };
33363
+ }
33364
+ function taskListRecord(taskList) {
33365
+ return {
33366
+ target_id: taskList.id,
33367
+ revision: taskList.updated_at,
33368
+ digest: digestProjectRegistrationValue({
33369
+ id: taskList.id,
33370
+ project_id: taskList.project_id,
33371
+ slug: taskList.slug,
33372
+ name: taskList.name,
33373
+ description: taskList.description,
33374
+ metadata: taskList.metadata,
33375
+ created_at: taskList.created_at,
33376
+ updated_at: taskList.updated_at
33377
+ })
33378
+ };
33379
+ }
33380
+ function receiptId(input) {
33381
+ return `tpr_${digestProjectRegistrationValue(input).slice(0, 40)}`;
33382
+ }
33383
+ function capabilityMatches(request, capability2) {
33384
+ return request.authority_route === capability2.route && request.package_version === capability2.package_version && request.authority_id === capability2.authority_id && request.tenant_id === capability2.tenant_id && request.corpus_id === capability2.corpus_id;
33385
+ }
33386
+ function authorityScope(capability2) {
33387
+ return {
33388
+ authority_id: capability2.authority_id,
33389
+ tenant_id: capability2.tenant_id,
33390
+ corpus_id: capability2.corpus_id
33391
+ };
33392
+ }
33393
+ function assertCapabilityRequest(request, capability2) {
33394
+ if (!capabilityMatches(request, capability2)) {
33395
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH", "registration request does not match this authority capability identity");
33396
+ }
33397
+ }
33398
+ function normalizedCallDigest(request) {
33399
+ return digestProjectRegistrationValue({
33400
+ authority_route: request.authority_route,
33401
+ package_version: request.package_version,
33402
+ authority_id: request.authority_id,
33403
+ tenant_id: request.tenant_id,
33404
+ corpus_id: request.corpus_id,
33405
+ operation_id: request.operation_id,
33406
+ step_id: request.step_id,
33407
+ resource_kind: request.resource_kind,
33408
+ direction: request.direction,
33409
+ target_selector: request.target_selector,
33410
+ idempotency_key: request.idempotency_key,
33411
+ request_digest: request.request_digest,
33412
+ precondition_digest: request.precondition_digest,
33413
+ project_id: request.project_id,
33414
+ project_slug: request.project_slug,
33415
+ project_name: request.project_name,
33416
+ desired: request.desired,
33417
+ accepted_receipt_id: request.accepted_receipt?.receipt_id ?? null
33418
+ });
33419
+ }
33420
+ function assertCommonRequest(request, capability2) {
33421
+ assertBounds(request);
33422
+ assertResourceKind(request.resource_kind);
33423
+ assertDirection(request.direction);
33424
+ assertCapabilityRequest(request, capability2);
33425
+ requireString(request.operation_id, "operation_id", {
33426
+ min: 8,
33427
+ max: 128,
33428
+ pattern: OPERATION_PATTERN
33429
+ });
33430
+ requireString(request.step_id, "step_id", {
33431
+ min: 3,
33432
+ max: 128,
33433
+ pattern: STEP_PATTERN
33434
+ });
33435
+ requireString(request.target_selector, "target_selector", { max: 512 });
33436
+ requireString(request.project_id, "project_id", {
33437
+ min: 16,
33438
+ max: 128,
33439
+ pattern: WORKSPACE_ID_PATTERN
33440
+ });
33441
+ requireString(request.project_name, "project_name", { max: 256 });
33442
+ requireString(request.project_slug, "project_slug", { max: 128 });
33443
+ requireString(request.request_digest, "request_digest", {
33444
+ min: 64,
33445
+ max: 64,
33446
+ pattern: SHA256_PATTERN
33447
+ });
33448
+ requireString(request.precondition_digest, "precondition_digest", {
33449
+ min: 64,
33450
+ max: 64,
33451
+ pattern: SHA256_PATTERN
33452
+ });
33453
+ requireString(request.idempotency_key, "idempotency_key", {
33454
+ min: 52,
33455
+ max: 52,
33456
+ pattern: IDEMPOTENCY_PATTERN
33457
+ });
33458
+ if (!request.desired || typeof request.desired !== "object" || Array.isArray(request.desired)) {
33459
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "desired must be a JSON object");
33460
+ }
33461
+ const expectedKey = deriveTodosProjectRegistrationIdempotencyKey({
33462
+ operation_id: request.operation_id,
33463
+ step_id: request.step_id,
33464
+ direction: request.direction,
33465
+ target_selector: request.target_selector,
33466
+ request_digest: request.request_digest,
33467
+ precondition_digest: request.precondition_digest
33468
+ });
33469
+ if (request.idempotency_key !== expectedKey) {
33470
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_IDEMPOTENCY_MISMATCH", "idempotency_key does not match the deterministic operation/step/direction payload", { expected: expectedKey });
33471
+ }
33472
+ taskListSlug(request.project_slug);
33473
+ }
33474
+ function assertForwardRequest(request, capability2) {
33475
+ assertCommonRequest(request, capability2);
33476
+ if (request.direction !== "forward") {
33477
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "create requires direction=forward");
33478
+ }
33479
+ const expectedRequestDigest = digestProjectRegistrationValue(request.desired);
33480
+ const expectedPreconditionDigest = digestProjectRegistrationValue({
33481
+ target_selector: request.target_selector,
33482
+ expected: "absent"
33483
+ });
33484
+ if (request.request_digest !== expectedRequestDigest || request.precondition_digest !== expectedPreconditionDigest) {
33485
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "request_digest or precondition_digest does not match normalized forward semantics", {
33486
+ expected_request_digest: expectedRequestDigest,
33487
+ expected_precondition_digest: expectedPreconditionDigest
33488
+ });
33489
+ }
33490
+ if (request.resource_kind === "project") {
33491
+ exactKeys(request.desired, ["source_project_id", "source_project_slug", "name"], "project desired");
33492
+ 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) {
33493
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "project desired state and target selector must match the complete Projects identity");
33494
+ }
33495
+ return;
33496
+ }
33497
+ if (request.resource_kind === "task_list") {
33498
+ exactKeys(request.desired, ["todos_project_id", "source_project_id", "name"], "task-list desired");
33499
+ const todosProjectId = request.desired["todos_project_id"];
33500
+ if (typeof todosProjectId !== "string" || !UUID_PATTERN.test(todosProjectId)) {
33501
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED", "task-list create requires the exact full Todos project UUID");
33502
+ }
33503
+ if (request.target_selector !== `${todosProjectId}:default` || request.desired["source_project_id"] !== request.project_id || request.desired["name"] !== request.project_name) {
33504
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "task-list desired state must bind the exact Todos project id and Projects identity");
33505
+ }
33506
+ return;
33507
+ }
33508
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "unsupported registration resource kind");
33509
+ }
33510
+ function assertInverseRequest(request, capability2) {
33511
+ assertCommonRequest(request, capability2);
33512
+ if (request.direction !== "inverse") {
33513
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "compensate requires direction=inverse");
33514
+ }
33515
+ const accepted = request.accepted_receipt;
33516
+ if (!accepted || accepted.authority !== "todos" || accepted.route !== capability2.route || accepted.package_version !== capability2.package_version || accepted.authority_id !== capability2.authority_id || accepted.tenant_id !== capability2.tenant_id || accepted.corpus_id !== capability2.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) {
33517
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "inverse requires the complete accepted forward receipt created by this operation");
33518
+ }
33519
+ exactKeys(request.desired, ["accepted_receipt_id", "target_id"], "inverse desired");
33520
+ const expectedDesired = {
33521
+ accepted_receipt_id: accepted.receipt_id,
33522
+ target_id: accepted.target_id
33523
+ };
33524
+ const expectedPrecondition = {
33525
+ expected_revision: accepted.result_revision,
33526
+ expected_digest: accepted.result_digest
33527
+ };
33528
+ const expectedRequestDigest = digestProjectRegistrationValue(expectedDesired);
33529
+ const expectedPreconditionDigest = digestProjectRegistrationValue(expectedPrecondition);
33530
+ if (canonicalProjectRegistrationJson(request.desired) !== canonicalProjectRegistrationJson(expectedDesired) || request.request_digest !== expectedRequestDigest || request.precondition_digest !== expectedPreconditionDigest || request.target_selector !== accepted.target_id) {
33531
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "inverse request does not match the accepted receipt and exact readback precondition");
33532
+ }
33533
+ return accepted;
33534
+ }
33535
+ function makeReceipt(input, createdAt2) {
33536
+ return {
33537
+ ...input,
33538
+ receipt_id: receiptId(input),
33539
+ created_at: createdAt2
33540
+ };
33541
+ }
33542
+ function receiptBase(request, callDigest, capability2) {
33543
+ return {
33544
+ authority: "todos",
33545
+ route: capability2.route,
33546
+ package_version: capability2.package_version,
33547
+ authority_id: capability2.authority_id,
33548
+ tenant_id: capability2.tenant_id,
33549
+ corpus_id: capability2.corpus_id,
33550
+ operation_id: request.operation_id,
33551
+ step_id: request.step_id,
33552
+ resource_kind: request.resource_kind,
33553
+ direction: request.direction,
33554
+ target_selector: request.target_selector,
33555
+ idempotency_key: request.idempotency_key,
33556
+ request_digest: request.request_digest,
33557
+ precondition_digest: request.precondition_digest,
33558
+ normalized_call_digest: callDigest
33559
+ };
33560
+ }
33561
+ function makeAcceptedReceipt(request, callDigest, capability2, record, createdAt2) {
33562
+ return makeReceipt({
33563
+ ...receiptBase(request, callDigest, capability2),
33564
+ outcome: "accepted",
33565
+ reason: null,
33566
+ target_id: record.target_id,
33567
+ result_revision: record.revision,
33568
+ result_digest: record.digest,
33569
+ duplicate_of_receipt_id: null,
33570
+ accepted_receipt_id: request.direction === "inverse" ? request.accepted_receipt.receipt_id : null,
33571
+ created_by_operation: true
33572
+ }, createdAt2);
33573
+ }
33574
+ function makeDuplicateReceipt(request, callDigest, capability2, accepted, createdAt2) {
33575
+ return makeReceipt({
33576
+ ...receiptBase(request, callDigest, capability2),
33577
+ outcome: "duplicate_of_accepted",
33578
+ reason: null,
33579
+ target_id: accepted.target_id,
33580
+ result_revision: accepted.result_revision,
33581
+ result_digest: accepted.result_digest,
33582
+ duplicate_of_receipt_id: accepted.receipt_id,
33583
+ accepted_receipt_id: null,
33584
+ created_by_operation: false
33585
+ }, createdAt2);
33586
+ }
33587
+ function makeTerminalReceipt(request, callDigest, capability2, reason, createdAt2, options = {}) {
33588
+ return makeReceipt({
33589
+ ...receiptBase(request, callDigest, capability2),
33590
+ outcome: "terminal_nonacceptance",
33591
+ reason,
33592
+ target_id: options.targetId ?? null,
33593
+ result_revision: null,
33594
+ result_digest: null,
33595
+ duplicate_of_receipt_id: null,
33596
+ accepted_receipt_id: options.acceptedReceiptId ?? null,
33597
+ created_by_operation: false
33598
+ }, createdAt2);
33599
+ }
33600
+ async function insertDeterministicReceipt(transaction, receipt) {
33601
+ if (await transaction.insertReceipt(receipt))
33602
+ return receipt;
33603
+ const existing = await transaction.getReceiptById(receipt.receipt_id);
33604
+ const { created_at: _existingCreatedAt, ...existingContent } = existing ?? {};
33605
+ const { created_at: _receiptCreatedAt, ...receiptContent } = receipt;
33606
+ if (!existing || canonicalProjectRegistrationJson(existingContent) !== canonicalProjectRegistrationJson(receiptContent)) {
33607
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "deterministic receipt id is occupied by different immutable content", { receipt_id: receipt.receipt_id });
33608
+ }
33609
+ return existing;
33610
+ }
33611
+ function bindingFor(request, callDigest, timestamp3, capability2) {
33612
+ return {
33613
+ ...authorityScope(capability2),
33614
+ resource_kind: request.resource_kind,
33615
+ target_selector: request.target_selector,
33616
+ operation_id: request.operation_id,
33617
+ step_id: request.step_id,
33618
+ direction: "forward",
33619
+ idempotency_key: request.idempotency_key,
33620
+ request_digest: request.request_digest,
33621
+ precondition_digest: request.precondition_digest,
33622
+ normalized_call_digest: callDigest,
33623
+ state: "pending",
33624
+ target_id: null,
33625
+ accepted_receipt_id: null,
33626
+ result_revision: null,
33627
+ result_digest: null,
33628
+ removed_receipt_id: null,
33629
+ created_at: timestamp3,
33630
+ updated_at: timestamp3
33631
+ };
33632
+ }
33633
+
33634
+ class PackageOwnedTodosProjectRegistrationAuthority {
33635
+ backend;
33636
+ authority = "todos";
33637
+ capabilityValue;
33638
+ now;
33639
+ faultInjector;
33640
+ constructor(backend, options = {}) {
33641
+ this.backend = backend;
33642
+ this.capabilityValue = {
33643
+ authority: "todos",
33644
+ route: TODOS_PROJECT_REGISTRATION_ROUTE,
33645
+ package_version: options.packageVersion ?? getPackageVersion(import.meta.url),
33646
+ authority_id: options.authorityId ?? "todos",
33647
+ tenant_id: options.tenantId ?? backend.kind,
33648
+ corpus_id: options.corpusId ?? `todos:${backend.kind}`,
33649
+ supported_resources: ["project", "task_list"],
33650
+ conditional_create: true,
33651
+ immutable_receipts: true,
33652
+ exact_terminal_lookup: true,
33653
+ exact_readback: true,
33654
+ conditional_inverse: true,
33655
+ ambiguous_outcome_reconciliation: true
33656
+ };
33657
+ this.now = options.now ?? (() => new Date().toISOString());
33658
+ this.faultInjector = options.faultInjector;
33659
+ }
33660
+ async capability() {
33661
+ return {
33662
+ ...this.capabilityValue,
33663
+ supported_resources: [...this.capabilityValue.supported_resources]
33664
+ };
33665
+ }
33666
+ async fault(point, request) {
33667
+ if (!this.faultInjector)
33668
+ return;
33669
+ try {
33670
+ await this.faultInjector(point, {
33671
+ operation_id: request.operation_id,
33672
+ step_id: request.step_id,
33673
+ resource_kind: request.resource_kind,
33674
+ direction: request.direction
33675
+ });
33676
+ } catch (cause) {
33677
+ throw new WriteBoundaryError(point, cause);
33678
+ }
33679
+ }
33680
+ async afterCommit(request) {
33681
+ await this.faultInjector?.("after_commit", {
33682
+ operation_id: request.operation_id,
33683
+ step_id: request.step_id,
33684
+ resource_kind: request.resource_kind,
33685
+ direction: request.direction
33686
+ });
33687
+ }
33688
+ async duplicateFor(transaction, request, callDigest, accepted) {
33689
+ const duplicate = makeDuplicateReceipt(request, callDigest, this.capabilityValue, accepted, this.now());
33690
+ return insertDeterministicReceipt(transaction, duplicate);
33691
+ }
33692
+ async terminalFor(transaction, request, callDigest, reason, options = {}) {
33693
+ return insertDeterministicReceipt(transaction, makeTerminalReceipt(request, callDigest, this.capabilityValue, reason, this.now(), options));
33694
+ }
33695
+ async existingForwardResolution(transaction, request, callDigest) {
33696
+ const exact = await transaction.getReceiptForLookup({
33697
+ ...authorityScope(this.capabilityValue),
33698
+ operation_id: request.operation_id,
33699
+ step_id: request.step_id,
33700
+ resource_kind: request.resource_kind,
33701
+ direction: request.direction,
33702
+ idempotency_key: request.idempotency_key,
33703
+ target_selector: request.target_selector
33704
+ });
33705
+ if (exact) {
33706
+ if (exact.outcome === "terminal_nonacceptance")
33707
+ return exact;
33708
+ const accepted2 = exact.outcome === "accepted" ? exact : await transaction.getReceiptById(exact.duplicate_of_receipt_id);
33709
+ if (!accepted2) {
33710
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "duplicate receipt points to a missing accepted receipt");
33711
+ }
33712
+ if (accepted2.normalized_call_digest !== callDigest) {
33713
+ return this.terminalFor(transaction, request, callDigest, "operation_step_semantics_changed", { targetId: accepted2.target_id });
33714
+ }
33715
+ return this.duplicateFor(transaction, request, callDigest, accepted2);
33716
+ }
33717
+ const accepted = await transaction.getAcceptedReceiptForStep({
33718
+ ...authorityScope(this.capabilityValue),
33719
+ operation_id: request.operation_id,
33720
+ step_id: request.step_id,
33721
+ resource_kind: request.resource_kind,
33722
+ direction: "forward"
33723
+ });
33724
+ if (!accepted)
33725
+ return null;
33726
+ if (accepted.normalized_call_digest === callDigest) {
33727
+ return this.duplicateFor(transaction, request, callDigest, accepted);
33728
+ }
33729
+ return this.terminalFor(transaction, request, callDigest, "operation_step_semantics_changed", { targetId: accepted.target_id });
33730
+ }
33731
+ async createObject(transaction, request) {
33732
+ if (request.resource_kind === "project") {
33733
+ const path = projectRegistrationPath(request.project_id);
33734
+ const slug2 = taskListSlug(request.project_slug);
33735
+ const conflict2 = await transaction.findProjectConflict(path, slug2);
33736
+ if (conflict2) {
33737
+ return this.terminalFor(transaction, request, normalizedCallDigest(request), "target_already_exists", { targetId: conflict2.id });
33738
+ }
33739
+ await this.fault("before_object_write", request);
33740
+ const project = await transaction.createProject({
33741
+ name: request.project_name,
33742
+ path,
33743
+ description: `Registered from Projects workspace ${request.project_id}`,
33744
+ task_list_id: slug2,
33745
+ task_prefix: deterministicTaskPrefix(request.project_slug)
33746
+ });
33747
+ await this.fault("after_object_write", request);
33748
+ return projectRecord(project);
33749
+ }
33750
+ const todosProjectId = String(request.desired["todos_project_id"]);
33751
+ const sourceBinding = await transaction.getBinding(authorityScope(this.capabilityValue), "project", request.project_id);
33752
+ if (!sourceBinding || sourceBinding.state !== "accepted" || sourceBinding.target_id !== todosProjectId) {
33753
+ return this.terminalFor(transaction, request, normalizedCallDigest(request), "exact_parent_registration_missing", { targetId: todosProjectId });
33754
+ }
33755
+ const parent = await transaction.getProject(todosProjectId);
33756
+ if (!parent) {
33757
+ return this.terminalFor(transaction, request, normalizedCallDigest(request), "exact_parent_project_missing", { targetId: todosProjectId });
33758
+ }
33759
+ const slug = taskListSlug(request.project_slug);
33760
+ const conflict = await transaction.findTaskListConflict(todosProjectId, slug);
33761
+ if (conflict) {
33762
+ return this.terminalFor(transaction, request, normalizedCallDigest(request), "target_already_exists", { targetId: conflict.id });
33763
+ }
33764
+ await this.fault("before_object_write", request);
33765
+ const taskList = await transaction.createTaskList({
33766
+ name: request.project_name,
33767
+ slug,
33768
+ project_id: todosProjectId,
33769
+ metadata: {
33770
+ source_project_id: request.project_id,
33771
+ registration_authority: "todos"
33772
+ }
33773
+ });
33774
+ await this.fault("after_object_write", request);
33775
+ if (taskList.project_id !== todosProjectId) {
33776
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "task-list create did not preserve the exact full Todos project id");
33777
+ }
33778
+ return taskListRecord(taskList);
33779
+ }
33780
+ async create(request) {
33781
+ const startedAt = Date.now();
33782
+ assertForwardRequest(request, this.capabilityValue);
33783
+ const callDigest = normalizedCallDigest(request);
33784
+ try {
33785
+ const row = await this.backend.transaction(async (transaction) => {
33786
+ await transaction.lockStep({
33787
+ ...authorityScope(this.capabilityValue),
33788
+ operation_id: request.operation_id,
33789
+ step_id: request.step_id,
33790
+ resource_kind: request.resource_kind,
33791
+ direction: request.direction
33792
+ });
33793
+ const resolved = await this.existingForwardResolution(transaction, request, callDigest);
33794
+ if (resolved)
33795
+ return resolved;
33796
+ const timestamp3 = this.now();
33797
+ const claimed = await transaction.claimBinding(bindingFor(request, callDigest, timestamp3, this.capabilityValue));
33798
+ if (!claimed) {
33799
+ const binding = await transaction.getBinding(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector);
33800
+ if (binding?.state === "accepted" && binding.normalized_call_digest === callDigest && binding.accepted_receipt_id) {
33801
+ const accepted2 = await transaction.getReceiptById(binding.accepted_receipt_id);
33802
+ if (accepted2) {
33803
+ return this.duplicateFor(transaction, request, callDigest, accepted2);
33804
+ }
33805
+ }
33806
+ return this.terminalFor(transaction, request, callDigest, binding?.state === "removed" ? "target_registration_was_removed" : "target_already_registered", { targetId: binding?.target_id ?? null });
33807
+ }
33808
+ const recordOrTerminal = await this.createObject(transaction, request);
33809
+ if ("outcome" in recordOrTerminal) {
33810
+ await transaction.setBindingTerminal(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector, this.now());
33811
+ return recordOrTerminal;
33812
+ }
33813
+ const accepted = makeAcceptedReceipt(request, callDigest, this.capabilityValue, recordOrTerminal, this.now());
33814
+ await this.fault("before_receipt_write", request);
33815
+ const stored = await insertDeterministicReceipt(transaction, accepted);
33816
+ await this.fault("after_receipt_write", request);
33817
+ await transaction.setBindingAccepted(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector, {
33818
+ target_id: recordOrTerminal.target_id,
33819
+ accepted_receipt_id: stored.receipt_id,
33820
+ result_revision: recordOrTerminal.revision,
33821
+ result_digest: recordOrTerminal.digest,
33822
+ updated_at: this.now()
33823
+ });
33824
+ return stored;
33825
+ });
33826
+ await this.afterCommit(request);
33827
+ const receipt = publicReceipt(row);
33828
+ assertWithinBounds(receipt, request, startedAt);
33829
+ return receipt;
33830
+ } catch (error) {
33831
+ if (!(error instanceof WriteBoundaryError))
33832
+ throw error;
33833
+ const terminal = await this.recordWriteFailure(request, callDigest, error.point);
33834
+ const receipt = publicReceipt(terminal);
33835
+ assertWithinBounds(receipt, request, startedAt);
33836
+ return receipt;
33837
+ }
33838
+ }
33839
+ async recordWriteFailure(request, callDigest, point) {
33840
+ return this.backend.transaction(async (transaction) => {
33841
+ await transaction.lockStep({
33842
+ ...authorityScope(this.capabilityValue),
33843
+ operation_id: request.operation_id,
33844
+ step_id: request.step_id,
33845
+ resource_kind: request.resource_kind,
33846
+ direction: request.direction
33847
+ });
33848
+ const exact = await transaction.getReceiptForLookup({
33849
+ ...authorityScope(this.capabilityValue),
33850
+ operation_id: request.operation_id,
33851
+ step_id: request.step_id,
33852
+ resource_kind: request.resource_kind,
33853
+ direction: request.direction,
33854
+ idempotency_key: request.idempotency_key,
33855
+ target_selector: request.target_selector
33856
+ });
33857
+ if (exact)
33858
+ return exact;
33859
+ const accepted = await transaction.getAcceptedReceiptForStep({
33860
+ ...authorityScope(this.capabilityValue),
33861
+ operation_id: request.operation_id,
33862
+ step_id: request.step_id,
33863
+ resource_kind: request.resource_kind,
33864
+ direction: request.direction
33865
+ });
33866
+ if (accepted) {
33867
+ 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 });
33868
+ }
33869
+ const timestamp3 = this.now();
33870
+ const claimed = await transaction.claimBinding(bindingFor(request, callDigest, timestamp3, this.capabilityValue));
33871
+ const terminal = await this.terminalFor(transaction, request, callDigest, `write_failed:${point}`);
33872
+ if (claimed) {
33873
+ await transaction.setBindingTerminal(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector, this.now());
33874
+ }
33875
+ return terminal;
33876
+ });
33877
+ }
33878
+ async readExact(request) {
33879
+ const startedAt = Date.now();
33880
+ assertBounds(request);
33881
+ assertResourceKind(request.resource_kind);
33882
+ if (!UUID_PATTERN.test(request.target_id)) {
33883
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED", "exact readback requires a complete Todos object UUID");
33884
+ }
33885
+ 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);
33886
+ if (!record) {
33887
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_RECORD_NOT_FOUND", `registered ${request.resource_kind} was not found by exact id`, { target_id: request.target_id });
33888
+ }
33889
+ assertWithinBounds(record, request, startedAt);
33890
+ return record;
33891
+ }
33892
+ async lookupReceipt(request) {
33893
+ const startedAt = Date.now();
33894
+ assertBounds(request);
33895
+ assertResourceKind(request.resource_kind);
33896
+ assertDirection(request.direction);
33897
+ if (request.max_items !== 1) {
33898
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "max_items must be exactly 1 for terminal receipt lookup");
33899
+ }
33900
+ 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) {
33901
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH", "receipt lookup does not match this authority capability identity");
33902
+ }
33903
+ requireString(request.operation_id, "operation_id", {
33904
+ min: 8,
33905
+ max: 128,
33906
+ pattern: OPERATION_PATTERN
33907
+ });
33908
+ requireString(request.step_id, "step_id", {
33909
+ min: 3,
33910
+ max: 128,
33911
+ pattern: STEP_PATTERN
33912
+ });
33913
+ requireString(request.target_selector, "target_selector", { max: 512 });
33914
+ requireString(request.idempotency_key, "idempotency_key", {
33915
+ min: 52,
33916
+ max: 52,
33917
+ pattern: IDEMPOTENCY_PATTERN
33918
+ });
33919
+ if (request.target_id !== undefined && !UUID_PATTERN.test(request.target_id)) {
33920
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED", "receipt lookup target_id must be a complete Todos object UUID");
33921
+ }
33922
+ const receipt = await this.backend.getReceiptForLookup({
33923
+ ...authorityScope(this.capabilityValue),
33924
+ operation_id: request.operation_id,
33925
+ step_id: request.step_id,
33926
+ resource_kind: request.resource_kind,
33927
+ direction: request.direction,
33928
+ idempotency_key: request.idempotency_key,
33929
+ target_selector: request.target_selector
33930
+ });
33931
+ if (!receipt || request.target_id && receipt.target_id !== request.target_id) {
33932
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND", "no exact terminal receipt matched the bounded lookup");
33933
+ }
33934
+ return withResponseControl({ receipt: publicReceipt(receipt) }, request, startedAt);
33935
+ }
33936
+ async storedAcceptedReceipt(request, supplied) {
33937
+ const stored = await this.backend.getReceiptById(supplied.receipt_id);
33938
+ if (!stored || stored.outcome !== "accepted" || !stored.created_by_operation || canonicalProjectRegistrationJson(publicReceipt(stored)) !== canonicalProjectRegistrationJson(supplied)) {
33939
+ 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 });
33940
+ }
33941
+ 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) {
33942
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND", "accepted receipt does not own this exact operation step and target");
33943
+ }
33944
+ return stored;
33945
+ }
33946
+ async compensate(request) {
33947
+ const startedAt = Date.now();
33948
+ const suppliedAccepted = assertInverseRequest(request, this.capabilityValue);
33949
+ const accepted = await this.storedAcceptedReceipt(request, suppliedAccepted);
33950
+ const callDigest = normalizedCallDigest(request);
33951
+ try {
33952
+ const row = await this.backend.transaction(async (transaction) => {
33953
+ await transaction.lockStep({
33954
+ ...authorityScope(this.capabilityValue),
33955
+ operation_id: request.operation_id,
33956
+ step_id: request.step_id,
33957
+ resource_kind: request.resource_kind,
33958
+ direction: request.direction
33959
+ });
33960
+ const exact = await transaction.getReceiptForLookup({
33961
+ ...authorityScope(this.capabilityValue),
33962
+ operation_id: request.operation_id,
33963
+ step_id: request.step_id,
33964
+ resource_kind: request.resource_kind,
33965
+ direction: "inverse",
33966
+ idempotency_key: request.idempotency_key,
33967
+ target_selector: request.target_selector
33968
+ });
33969
+ if (exact)
33970
+ return exact;
33971
+ const storedAccepted = await transaction.getReceiptById(accepted.receipt_id);
33972
+ if (!storedAccepted || storedAccepted.outcome !== "accepted" || !storedAccepted.created_by_operation) {
33973
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND", "accepted receipt disappeared before conditional inverse");
33974
+ }
33975
+ const binding = await transaction.getBinding(authorityScope(this.capabilityValue), accepted.resource_kind, accepted.target_selector);
33976
+ if (!binding || binding.state !== "accepted" || binding.accepted_receipt_id !== accepted.receipt_id || binding.target_id !== accepted.target_id) {
33977
+ return this.terminalFor(transaction, request, callDigest, "target_not_owned_by_receipt", {
33978
+ targetId: accepted.target_id,
33979
+ acceptedReceiptId: accepted.receipt_id
33980
+ });
33981
+ }
33982
+ await transaction.lockCompensationWrites();
33983
+ const object = request.resource_kind === "project" ? await transaction.getProject(accepted.target_id) : await transaction.getTaskList(accepted.target_id);
33984
+ if (!object) {
33985
+ return this.terminalFor(transaction, request, callDigest, "target_missing_before_inverse", {
33986
+ targetId: accepted.target_id,
33987
+ acceptedReceiptId: accepted.receipt_id
33988
+ });
33989
+ }
33990
+ const current = request.resource_kind === "project" ? projectRecord(object) : taskListRecord(object);
33991
+ if (current.revision !== accepted.result_revision || current.digest !== accepted.result_digest) {
33992
+ return this.terminalFor(transaction, request, callDigest, "target_drifted", {
33993
+ targetId: accepted.target_id,
33994
+ acceptedReceiptId: accepted.receipt_id
33995
+ });
33996
+ }
33997
+ if (await transaction.hasDependents(request.resource_kind, accepted.target_id)) {
33998
+ return this.terminalFor(transaction, request, callDigest, "target_has_dependents", {
33999
+ targetId: accepted.target_id,
34000
+ acceptedReceiptId: accepted.receipt_id
34001
+ });
34002
+ }
34003
+ await this.fault("before_object_write", request);
34004
+ const deleted = request.resource_kind === "project" ? await transaction.deleteProject(accepted.target_id) : await transaction.deleteTaskList(accepted.target_id);
34005
+ if (!deleted) {
34006
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "conditional inverse could not delete the exact accepted target");
34007
+ }
34008
+ await this.fault("after_object_write", request);
34009
+ const inverseRecord = {
34010
+ target_id: accepted.target_id,
34011
+ revision: "absent",
34012
+ digest: digestProjectRegistrationValue({
34013
+ target_id: accepted.target_id,
34014
+ accepted_receipt_id: accepted.receipt_id,
34015
+ absent: true
34016
+ })
34017
+ };
34018
+ const inverse = makeAcceptedReceipt(request, callDigest, this.capabilityValue, inverseRecord, this.now());
34019
+ await this.fault("before_receipt_write", request);
34020
+ const stored = await insertDeterministicReceipt(transaction, inverse);
34021
+ await this.fault("after_receipt_write", request);
34022
+ await transaction.setBindingRemoved(authorityScope(this.capabilityValue), accepted.resource_kind, accepted.target_selector, stored.receipt_id, this.now());
34023
+ return stored;
34024
+ });
34025
+ await this.afterCommit(request);
34026
+ const receipt = publicReceipt(row);
34027
+ assertWithinBounds(receipt, request, startedAt);
34028
+ return receipt;
34029
+ } catch (error) {
34030
+ if (!(error instanceof WriteBoundaryError))
34031
+ throw error;
34032
+ const terminal = await this.backend.transaction(async (transaction) => {
34033
+ await transaction.lockStep({
34034
+ ...authorityScope(this.capabilityValue),
34035
+ operation_id: request.operation_id,
34036
+ step_id: request.step_id,
34037
+ resource_kind: request.resource_kind,
34038
+ direction: request.direction
34039
+ });
34040
+ return this.terminalFor(transaction, request, callDigest, `write_failed:${error.point}`, {
34041
+ targetId: accepted.target_id,
34042
+ acceptedReceiptId: accepted.receipt_id
34043
+ });
34044
+ });
34045
+ const receipt = publicReceipt(terminal);
34046
+ assertWithinBounds(receipt, request, startedAt);
34047
+ return receipt;
34048
+ }
34049
+ }
34050
+ async verifyInverse(request) {
34051
+ const startedAt = Date.now();
34052
+ const accepted = assertInverseRequest(request, this.capabilityValue);
34053
+ await this.storedAcceptedReceipt(request, accepted);
34054
+ const receipt = await this.backend.getReceiptForLookup({
34055
+ ...authorityScope(this.capabilityValue),
34056
+ operation_id: request.operation_id,
34057
+ step_id: request.step_id,
34058
+ resource_kind: request.resource_kind,
34059
+ direction: "inverse",
34060
+ idempotency_key: request.idempotency_key,
34061
+ target_selector: request.target_selector
34062
+ });
34063
+ if (!receipt || receipt.outcome !== "accepted" || receipt.accepted_receipt_id !== accepted.receipt_id || receipt.result_revision !== "absent" || !receipt.result_digest) {
34064
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND", "accepted conditional inverse receipt was not found");
34065
+ }
34066
+ const object = request.resource_kind === "project" ? await this.backend.getProject(accepted.target_id) : await this.backend.getTaskList(accepted.target_id);
34067
+ if (object) {
34068
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "inverse verification found the accepted target still present");
34069
+ }
34070
+ const verification = {
34071
+ target_id: accepted.target_id,
34072
+ accepted_receipt_id: accepted.receipt_id,
34073
+ absent: true,
34074
+ digest: digestProjectRegistrationValue({
34075
+ target_id: accepted.target_id,
34076
+ accepted_receipt_id: accepted.receipt_id,
34077
+ absent: true
34078
+ })
34079
+ };
34080
+ assertWithinBounds(verification, request, startedAt);
34081
+ if (verification.digest !== receipt.result_digest) {
34082
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "inverse verification digest does not match the immutable receipt");
34083
+ }
34084
+ return verification;
34085
+ }
34086
+ }
34087
+ function createLocalTodosProjectRegistrationAuthority(db, options = {}) {
34088
+ return new PackageOwnedTodosProjectRegistrationAuthority(new SqliteTodosProjectRegistrationBackend(db), options);
34089
+ }
34090
+ function createPostgresTodosProjectRegistrationAuthority(client, options = {}) {
34091
+ const {
34092
+ service,
34093
+ tableName,
34094
+ cursorTableName,
34095
+ ...authorityOptions
34096
+ } = options;
34097
+ return new PackageOwnedTodosProjectRegistrationAuthority(new PostgresTodosProjectRegistrationBackend(client, {
34098
+ service,
34099
+ tableName,
34100
+ cursorTableName
34101
+ }), authorityOptions);
34102
+ }
34103
+ // src/project-registration/http.ts
34104
+ var JSON_HEADERS = { "Content-Type": "application/json" };
34105
+ function json(body, status = 200) {
34106
+ return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS });
34107
+ }
34108
+ function errorStatus(error) {
34109
+ switch (error.code) {
34110
+ case "TODOS_PROJECT_REGISTRATION_INVALID_INPUT":
34111
+ case "TODOS_PROJECT_REGISTRATION_INVALID_BOUNDS":
34112
+ case "TODOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH":
34113
+ case "TODOS_PROJECT_REGISTRATION_DIGEST_MISMATCH":
34114
+ case "TODOS_PROJECT_REGISTRATION_IDEMPOTENCY_MISMATCH":
34115
+ case "TODOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED":
34116
+ return 400;
34117
+ case "TODOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND":
34118
+ case "TODOS_PROJECT_REGISTRATION_RECORD_NOT_FOUND":
34119
+ case "TODOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND":
34120
+ return 404;
34121
+ case "TODOS_PROJECT_REGISTRATION_RESPONSE_TOO_LARGE":
34122
+ return 413;
34123
+ case "TODOS_PROJECT_REGISTRATION_TIME_BUDGET_EXCEEDED":
34124
+ return 408;
34125
+ case "TODOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE":
34126
+ return 503;
34127
+ default:
34128
+ return 409;
34129
+ }
34130
+ }
34131
+ async function readJson(req) {
34132
+ try {
34133
+ const value = await req.json();
34134
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
34135
+ } catch {
34136
+ return null;
34137
+ }
34138
+ }
34139
+ async function handleTodosProjectRegistrationHttpRequest(req, url, authority, basePath = "/v1/project-registration") {
34140
+ const path = url.pathname;
34141
+ if (path !== basePath && !path.startsWith(`${basePath}/`))
34142
+ return null;
34143
+ const action = path.slice(basePath.length).split("/").filter(Boolean).join("/");
34144
+ const method = req.method.toUpperCase();
34145
+ try {
34146
+ if ((action === "" || action === "capability") && method === "GET") {
34147
+ return json({ capability: await authority.capability() });
34148
+ }
34149
+ if (method !== "POST")
34150
+ return json({ error: "method not allowed" }, 405);
34151
+ const body = await readJson(req);
34152
+ if (!body) {
34153
+ return json({
34154
+ error: "invalid JSON body",
34155
+ code: "TODOS_PROJECT_REGISTRATION_INVALID_INPUT"
34156
+ }, 400);
34157
+ }
34158
+ if (action === "create") {
34159
+ return json({
34160
+ receipt: await authority.create(body)
34161
+ }, 201);
34162
+ }
34163
+ if (action === "receipts/lookup") {
34164
+ return json(await authority.lookupReceipt(body));
34165
+ }
34166
+ if (action === "read-exact") {
34167
+ return json({
34168
+ record: await authority.readExact(body)
34169
+ });
34170
+ }
34171
+ if (action === "compensate") {
34172
+ return json({
34173
+ receipt: await authority.compensate(body)
34174
+ }, 201);
34175
+ }
34176
+ if (action === "verify-inverse") {
34177
+ return json({
34178
+ verification: await authority.verifyInverse(body)
34179
+ });
34180
+ }
34181
+ return json({
34182
+ error: "unknown Todos project-registration route",
34183
+ code: "TODOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND"
34184
+ }, 404);
34185
+ } catch (cause) {
34186
+ if (cause instanceof TodosProjectRegistrationError) {
34187
+ return json({
34188
+ error: cause.message,
34189
+ code: cause.code,
34190
+ details: cause.details,
34191
+ authoritative: true
34192
+ }, errorStatus(cause));
34193
+ }
34194
+ return json({
34195
+ error: cause instanceof Error ? cause.message : "internal registration error",
34196
+ code: "TODOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE"
34197
+ }, 500);
34198
+ }
34199
+ }
34200
+ function withoutTarget(value) {
34201
+ const { target: _target, ...serializable } = value;
34202
+ return serializable;
34203
+ }
34204
+
34205
+ class TodosProjectRegistrationHttpClient {
34206
+ authority = "todos";
34207
+ baseUrl;
34208
+ fetchImpl;
34209
+ headers;
34210
+ constructor(options) {
34211
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
34212
+ this.fetchImpl = options.fetch ?? globalThis.fetch;
34213
+ this.headers = {
34214
+ ...options.headers,
34215
+ ...options.apiKey ? { Authorization: `Bearer ${options.apiKey}` } : {},
34216
+ "Content-Type": "application/json"
34217
+ };
34218
+ }
34219
+ async request(action, init = {}) {
34220
+ const response = await this.fetchImpl(`${this.baseUrl}/v1/project-registration${action}`, {
34221
+ ...init,
34222
+ headers: { ...this.headers, ...init.headers ?? {} }
34223
+ });
34224
+ const body = await response.json();
34225
+ if (!response.ok) {
34226
+ throw new TodosProjectRegistrationError(typeof body["code"] === "string" ? body["code"] : "TODOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE", typeof body["error"] === "string" ? body["error"] : `Todos project registration HTTP ${response.status}`, body["details"] && typeof body["details"] === "object" ? body["details"] : {});
34227
+ }
34228
+ return body;
34229
+ }
34230
+ async capability() {
34231
+ const body = await this.request("/capability");
34232
+ return body.capability;
34233
+ }
34234
+ async create(request) {
34235
+ const body = await this.request("/create", { method: "POST", body: JSON.stringify(withoutTarget(request)) });
34236
+ return body.receipt;
34237
+ }
34238
+ async readExact(request) {
34239
+ const body = await this.request("/read-exact", { method: "POST", body: JSON.stringify(withoutTarget(request)) });
34240
+ return body.record;
34241
+ }
34242
+ async lookupReceipt(request) {
34243
+ return this.request("/receipts/lookup", { method: "POST", body: JSON.stringify(request) });
34244
+ }
34245
+ async compensate(request) {
34246
+ const body = await this.request("/compensate", { method: "POST", body: JSON.stringify(withoutTarget(request)) });
34247
+ return body.receipt;
34248
+ }
34249
+ async verifyInverse(request) {
34250
+ const body = await this.request("/verify-inverse", {
34251
+ method: "POST",
34252
+ body: JSON.stringify(withoutTarget(request))
34253
+ });
34254
+ return body.verification;
34255
+ }
34256
+ }
34257
+ function createTodosProjectRegistrationHttpClient(options) {
34258
+ return new TodosProjectRegistrationHttpClient(options);
34259
+ }
31899
34260
  // src/lib/native-storage-status.ts
31900
34261
  function getNativeStorageStatus(env = process.env) {
31901
34262
  const issues = [];
@@ -32031,7 +34392,7 @@ init_task_lifecycle();
32031
34392
  init_task_crud();
32032
34393
  init_redaction();
32033
34394
  import { Database as Database3 } from "bun:sqlite";
32034
- import { createHash as createHash12 } from "crypto";
34395
+ import { createHash as createHash13 } from "crypto";
32035
34396
  import { existsSync as existsSync9, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
32036
34397
  import { basename as basename2, dirname as dirname6, join as join9, resolve as resolve10 } from "path";
32037
34398
 
@@ -32314,7 +34675,7 @@ function normalizePath3(input) {
32314
34675
  return resolve10(input);
32315
34676
  }
32316
34677
  function sourceStoreId(sourceDbPath) {
32317
- const digest = createHash12("sha256").update(sourceDbPath).digest("hex").slice(0, 16);
34678
+ const digest = createHash13("sha256").update(sourceDbPath).digest("hex").slice(0, 16);
32318
34679
  return `sqlite:${digest}`;
32319
34680
  }
32320
34681
  function inferSourceRepoPath(sourceDbPath) {
@@ -34192,7 +36553,7 @@ function addSourceOnce(projectId, type, name, uri, metadata, db) {
34192
36553
  function bootstrapProject(options = {}, db) {
34193
36554
  const d = db || getDatabase();
34194
36555
  const discovery = discoverProjectWorkspace(options.path);
34195
- const taskListSlug = options.taskListSlug || `todos-${slugify(options.name || discovery.projectName)}`;
36556
+ const taskListSlug2 = options.taskListSlug || `todos-${slugify(options.name || discovery.projectName)}`;
34196
36557
  if (options.dryRun) {
34197
36558
  return {
34198
36559
  dryRun: true,
@@ -34206,15 +36567,15 @@ function bootstrapProject(options = {}, db) {
34206
36567
  const beforeProject = getProjectByCanonicalPath(discovery.projectPath, d);
34207
36568
  let project = ensureProject(options.name || discovery.projectName, discovery.projectPath, d);
34208
36569
  const createdProject = !beforeProject;
34209
- if (project.task_list_id !== taskListSlug || options.name && project.name !== options.name) {
36570
+ if (project.task_list_id !== taskListSlug2 || options.name && project.name !== options.name) {
34210
36571
  project = renameProject(project.id, {
34211
36572
  name: options.name ?? project.name,
34212
- new_slug: taskListSlug
36573
+ new_slug: taskListSlug2
34213
36574
  }, d).project;
34214
36575
  }
34215
36576
  setMachineLocalPath(project.id, discovery.projectPath, d);
34216
- const beforeTaskList = d.query("SELECT id FROM task_lists WHERE project_id = ? AND slug = ?").get(project.id, taskListSlug);
34217
- let taskList = ensureTaskList(`${project.name} Tasks`, taskListSlug, project.id, d);
36577
+ const beforeTaskList = d.query("SELECT id FROM task_lists WHERE project_id = ? AND slug = ?").get(project.id, taskListSlug2);
36578
+ let taskList = ensureTaskList(`${project.name} Tasks`, taskListSlug2, project.id, d);
34218
36579
  if (options.routeEnabled && taskList.metadata.route_enabled !== true) {
34219
36580
  taskList = updateTaskList(taskList.id, {
34220
36581
  metadata: {
@@ -34276,7 +36637,7 @@ init_comments();
34276
36637
 
34277
36638
  // src/db/api-keys.ts
34278
36639
  init_database();
34279
- import { createHash as createHash13, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual3 } from "crypto";
36640
+ import { createHash as createHash14, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual3 } from "crypto";
34280
36641
  function rowToRecord(row) {
34281
36642
  return {
34282
36643
  id: row.id,
@@ -34290,7 +36651,7 @@ function rowToRecord(row) {
34290
36651
  };
34291
36652
  }
34292
36653
  function hashApiKey(key) {
34293
- return createHash13("sha256").update(key).digest("hex");
36654
+ return createHash14("sha256").update(key).digest("hex");
34294
36655
  }
34295
36656
  function safeEqualHex(a, b) {
34296
36657
  if (a.length !== b.length)
@@ -37350,10 +39711,10 @@ function extractEmbeddedBridge(markdown) {
37350
39711
  const match = markdown.match(/<!--\s*hasna\.todos\.bridge\s*\n([\s\S]*?)\n\s*-->/);
37351
39712
  if (!match)
37352
39713
  return null;
37353
- const json = match[1].split(`
39714
+ const json2 = match[1].split(`
37354
39715
  `).map((line) => line.replace(/^ /, "")).join(`
37355
39716
  `);
37356
- return JSON.parse(json);
39717
+ return JSON.parse(json2);
37357
39718
  }
37358
39719
  function frontmatterValue(markdown, key) {
37359
39720
  const match = markdown.match(/^---\n([\s\S]*?)\n---/);
@@ -38737,7 +41098,7 @@ init_database();
38737
41098
  init_tasks();
38738
41099
  import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
38739
41100
  import { basename as basename5 } from "path";
38740
- import { createHash as createHash14 } from "crypto";
41101
+ import { createHash as createHash15 } from "crypto";
38741
41102
  init_secret_redaction();
38742
41103
  var INBOX_INTAKE_SCHEMA = "todos.inbox_intake.v1";
38743
41104
  var INTAKE_SOURCE_TYPES = [
@@ -38750,7 +41111,7 @@ var INTAKE_SOURCE_TYPES = [
38750
41111
  ];
38751
41112
  var INTAKE_TRIAGE_STATUSES = ["preview", "triaged", "duplicate", "created"];
38752
41113
  function fingerprint2(text) {
38753
- return createHash14("sha256").update(text).digest("hex").slice(0, 16);
41114
+ return createHash15("sha256").update(text).digest("hex").slice(0, 16);
38754
41115
  }
38755
41116
  function loadRawContent(input) {
38756
41117
  if (input.github_url) {
@@ -44210,7 +46571,7 @@ init_database();
44210
46571
  init_tasks();
44211
46572
  init_redaction();
44212
46573
  init_sync_utils();
44213
- import { createHash as createHash15 } from "crypto";
46574
+ import { createHash as createHash16 } from "crypto";
44214
46575
  import { existsSync as existsSync22, readFileSync as readFileSync19, statSync as statSync9 } from "fs";
44215
46576
  import { hostname as hostname3, platform, arch } from "os";
44216
46577
  import { dirname as dirname15, join as join18, resolve as resolve17 } from "path";
@@ -44232,7 +46593,7 @@ var CONFIG_FILES = [
44232
46593
  "dashboard/vite.config.ts"
44233
46594
  ];
44234
46595
  function sha2566(value) {
44235
- return createHash15("sha256").update(value).digest("hex");
46596
+ return createHash16("sha256").update(value).digest("hex");
44236
46597
  }
44237
46598
  function fileRecord(root, relativePath) {
44238
46599
  const path = join18(root, relativePath);
@@ -44484,7 +46845,7 @@ function compareEnvironmentSnapshotFiles(leftPath, rightPath) {
44484
46845
  init_database();
44485
46846
  init_projects();
44486
46847
  init_plans();
44487
- import { createHash as createHash16 } from "crypto";
46848
+ import { createHash as createHash17 } from "crypto";
44488
46849
  import { mkdirSync as mkdirSync18, writeFileSync as writeFileSync16 } from "fs";
44489
46850
  import { dirname as dirname16, join as join19 } from "path";
44490
46851
  var DECISION_RECORD_SCHEMA = "todos.decision_record.v1";
@@ -44540,7 +46901,7 @@ function rowToDecisionRecord(row) {
44540
46901
  }
44541
46902
  function stableSnapshotHash(payload) {
44542
46903
  const { captured_at: _capturedAt, ...rest } = payload;
44543
- return createHash16("sha256").update(JSON.stringify(rest)).digest("hex");
46904
+ return createHash17("sha256").update(JSON.stringify(rest)).digest("hex");
44544
46905
  }
44545
46906
  function createDecisionRecord(input, db) {
44546
46907
  const d = db || getDatabase();
@@ -49031,7 +51392,7 @@ function syncWithAgents(agents, taskListIdByAgent, projectId, direction = "both"
49031
51392
  init_tasks();
49032
51393
  init_task_files();
49033
51394
  import { existsSync as existsSync27, readFileSync as readFileSync23, statSync as statSync10 } from "fs";
49034
- import { createHash as createHash17 } from "crypto";
51395
+ import { createHash as createHash18 } from "crypto";
49035
51396
  import { relative as relative6, resolve as resolve18, join as join25 } from "path";
49036
51397
  var EXTRACT_TAGS = ["TODO", "FIXME", "HACK", "XXX", "BUG", "NOTE"];
49037
51398
  var DEFAULT_EXTENSIONS = new Set([
@@ -49096,7 +51457,7 @@ var SKIP_DIRS2 = new Set([
49096
51457
  ".parcel-cache"
49097
51458
  ]);
49098
51459
  function stableHash(value) {
49099
- return createHash17("sha256").update(value).digest("hex");
51460
+ return createHash18("sha256").update(value).digest("hex");
49100
51461
  }
49101
51462
  function normalizePathForMatch(value) {
49102
51463
  return value.replace(/\\/g, "/").replace(/^\.\//, "");
@@ -49994,7 +52355,7 @@ function renderWorkflowStatesMarkdown(states = listWorkflowStates()) {
49994
52355
  }
49995
52356
  // src/lib/agent-replay-simulator.ts
49996
52357
  init_redaction();
49997
- import { createHash as createHash18 } from "crypto";
52358
+ import { createHash as createHash19 } from "crypto";
49998
52359
  import { readFileSync as readFileSync24 } from "fs";
49999
52360
  function isObject(value) {
50000
52361
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
@@ -50016,7 +52377,7 @@ function stable2(value) {
50016
52377
  return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable2(value[key])]));
50017
52378
  }
50018
52379
  function fingerprint3(value) {
50019
- return createHash18("sha256").update(JSON.stringify(stable2(value))).digest("hex");
52380
+ return createHash19("sha256").update(JSON.stringify(stable2(value))).digest("hex");
50020
52381
  }
50021
52382
  function unpackFixture(input) {
50022
52383
  if (!isObject(input))
@@ -50255,7 +52616,7 @@ function renderAgentReplaySimulationMarkdown(simulation) {
50255
52616
  }
50256
52617
  // src/lib/local-extensions.ts
50257
52618
  init_config2();
50258
- import { createHash as createHash19, createVerify } from "crypto";
52619
+ import { createHash as createHash20, createVerify } from "crypto";
50259
52620
  import { existsSync as existsSync28, readdirSync as readdirSync6, readFileSync as readFileSync25, statSync as statSync11 } from "fs";
50260
52621
  import { basename as basename6, join as join26, resolve as resolve19 } from "path";
50261
52622
  init_redaction();
@@ -50343,7 +52704,7 @@ function parseJson2(path) {
50343
52704
  return JSON.parse(readFileSync25(path, "utf8"));
50344
52705
  }
50345
52706
  function sha2567(bytes) {
50346
- return `sha256:${createHash19("sha256").update(bytes).digest("hex")}`;
52707
+ return `sha256:${createHash20("sha256").update(bytes).digest("hex")}`;
50347
52708
  }
50348
52709
  function compareVersions(a, b) {
50349
52710
  const left = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -52976,6 +55337,7 @@ export {
52976
55337
  startTaskRun,
52977
55338
  startTask,
52978
55339
  startFocusSession,
55340
+ sqliteTodosProjectRegistrationSchemaSql,
52979
55341
  softDeleteArtifact,
52980
55342
  snoozeReminder,
52981
55343
  slugify,
@@ -53164,6 +55526,7 @@ export {
53164
55526
  previewInboxIntake,
53165
55527
  previewBuiltinTemplate,
53166
55528
  postgresTodosSyncSchemaSql,
55529
+ postgresTodosProjectRegistrationSchemaSql,
53167
55530
  pollLocalSnapshots,
53168
55531
  planRunArtifactsS3Sync,
53169
55532
  pauseFocusSession,
@@ -53354,6 +55717,7 @@ export {
53354
55717
  importActivityLog,
53355
55718
  hasSecretFindings,
53356
55719
  hasActiveApiKeys,
55720
+ handleTodosProjectRegistrationHttpRequest,
53357
55721
  getWorkspaceTrustStatus,
53358
55722
  getWorkflowPrompt,
53359
55723
  getWebhook,
@@ -53657,6 +56021,7 @@ export {
53657
56021
  discoverTaskRouteSources,
53658
56022
  discoverProjectWorkspace,
53659
56023
  discoverLocalExtensions,
56024
+ digestProjectRegistrationValue,
53660
56025
  deterministicPrGroupId,
53661
56026
  deterministicPrGroupAttemptId,
53662
56027
  detectSourceType,
@@ -53665,6 +56030,7 @@ export {
53665
56030
  detectInboxSourceType,
53666
56031
  detectCyclesFromEdges,
53667
56032
  describeTerminalNotificationRule,
56033
+ deriveTodosProjectRegistrationIdempotencyKey,
53668
56034
  deriveInboxTitle,
53669
56035
  deleteWebhook,
53670
56036
  deleteTemplate,
@@ -53699,6 +56065,7 @@ export {
53699
56065
  createTodosStorageAdapter,
53700
56066
  createTodosS3ArtifactStore,
53701
56067
  createTodosRegistry,
56068
+ createTodosProjectRegistrationHttpClient,
53702
56069
  createTemplate,
53703
56070
  createTaskList,
53704
56071
  createTaskBoard,
@@ -53719,6 +56086,7 @@ export {
53719
56086
  createProject,
53720
56087
  createPostgresTodosSyncStore,
53721
56088
  createPostgresTodosStorageAdapter,
56089
+ createPostgresTodosProjectRegistrationAuthority,
53722
56090
  createPlanWithSteps,
53723
56091
  createPlan,
53724
56092
  createOrg,
@@ -53726,6 +56094,7 @@ export {
53726
56094
  createMilestone,
53727
56095
  createMcpManifest,
53728
56096
  createLocalUsageLedger,
56097
+ createLocalTodosProjectRegistrationAuthority,
53729
56098
  createLocalSqliteTodosStorageAdapter,
53730
56099
  createLocalReport,
53731
56100
  createLocalPrGroupLedger,
@@ -53799,6 +56168,7 @@ export {
53799
56168
  categorizeMcpTool,
53800
56169
  captureKnowledgeSnapshot,
53801
56170
  captureEnvironmentSnapshot,
56171
+ canonicalProjectRegistrationJson,
53802
56172
  cancelDispatch,
53803
56173
  cancelAgentRunDispatch,
53804
56174
  cancelAgentRun,
@@ -53876,6 +56246,8 @@ export {
53876
56246
  VersionConflictError,
53877
56247
  VERIFICATION_EVIDENCE_SCHEMA,
53878
56248
  USER_SCAFFOLD_SCHEMA,
56249
+ TodosProjectRegistrationHttpClient,
56250
+ TodosProjectRegistrationError,
53879
56251
  TodosClient,
53880
56252
  TaskNotFoundError,
53881
56253
  TaskListNotFoundError,
@@ -53887,6 +56259,9 @@ export {
53887
56259
  TODOS_SDK_INTEGRATION_FIXTURE_SCHEMA_VERSION,
53888
56260
  TODOS_SDK_INTEGRATION_FIXTURE_GENERATED_AT,
53889
56261
  TODOS_REGISTRY,
56262
+ TODOS_PROJECT_REGISTRATION_SCHEMA_VERSION,
56263
+ TODOS_PROJECT_REGISTRATION_ROUTE,
56264
+ TODOS_PROJECT_REGISTRATION_CALLER_ROUTE,
53890
56265
  TODOS_PACKAGE_EXPORTS,
53891
56266
  TODOS_ONBOARDING_FIXTURE_SOURCE,
53892
56267
  TODOS_ONBOARDING_FIXTURE_LIBRARY_VERSION,
@@ -53925,6 +56300,7 @@ export {
53925
56300
  TASK_FINDING_SCHEMA_VERSION,
53926
56301
  TASK_FINDING_RESOLVE_MISSING_SCHEMA_VERSION,
53927
56302
  TASK_DEPENDENCY_EDGES_SCHEMA,
56303
+ SqliteTodosProjectRegistrationBackend,
53928
56304
  STORAGE_TABLES,
53929
56305
  SECRET_REDACTION_SCHEMA,
53930
56306
  SCHEMA_SEMVER,
@@ -53951,7 +56327,9 @@ export {
53951
56327
  PrGroupLedgerError,
53952
56328
  PrGroupLedger,
53953
56329
  PrGroupHttpClient,
56330
+ PostgresTodosProjectRegistrationBackend,
53954
56331
  PlanNotFoundError,
56332
+ PackageOwnedTodosProjectRegistrationAuthority,
53955
56333
  PROJECT_DEPENDENCY_GRAPH_SCHEMA,
53956
56334
  PREWRITE_SECRET_SCAN_SCHEMA,
53957
56335
  PLAN_STATUSES,