@hasna/todos 0.15.6 → 0.15.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/cli/cloud-router.d.ts.map +1 -1
  2. package/dist/cli/index.js +3011 -1325
  3. package/dist/contracts.js +213 -3
  4. package/dist/db/migrations.d.ts.map +1 -1
  5. package/dist/db/schema.d.ts.map +1 -1
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1950 -26
  9. package/dist/mcp/index.js +2073 -371
  10. package/dist/mcp.js +7 -3
  11. package/dist/project-registration/authority.d.ts +45 -0
  12. package/dist/project-registration/authority.d.ts.map +1 -0
  13. package/dist/project-registration/backend.d.ts +83 -0
  14. package/dist/project-registration/backend.d.ts.map +1 -0
  15. package/dist/project-registration/http.d.ts +24 -0
  16. package/dist/project-registration/http.d.ts.map +1 -0
  17. package/dist/project-registration/index.d.ts +8 -0
  18. package/dist/project-registration/index.d.ts.map +1 -0
  19. package/dist/project-registration/postgres.d.ts +30 -0
  20. package/dist/project-registration/postgres.d.ts.map +1 -0
  21. package/dist/project-registration/schema.d.ts +3 -0
  22. package/dist/project-registration/schema.d.ts.map +1 -0
  23. package/dist/project-registration/sqlite.d.ts +17 -0
  24. package/dist/project-registration/sqlite.d.ts.map +1 -0
  25. package/dist/project-registration/types.d.ts +155 -0
  26. package/dist/project-registration/types.d.ts.map +1 -0
  27. package/dist/project-registration.d.ts +2 -0
  28. package/dist/project-registration.d.ts.map +1 -0
  29. package/dist/project-registration.js +17689 -0
  30. package/dist/registry.d.ts +1 -1
  31. package/dist/registry.d.ts.map +1 -1
  32. package/dist/registry.js +220 -3
  33. package/dist/release-provenance.json +5 -5
  34. package/dist/server/cloud.d.ts +3 -0
  35. package/dist/server/cloud.d.ts.map +1 -1
  36. package/dist/server/index.js +4374 -2672
  37. package/dist/server/v1.d.ts +2 -1
  38. package/dist/server/v1.d.ts.map +1 -1
  39. package/dist/storage.js +206 -0
  40. 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,
@@ -12157,7 +12363,7 @@ var init_dispatches = __esm(() => {
12157
12363
  // package.json
12158
12364
  var package_default = {
12159
12365
  name: "@hasna/todos",
12160
- version: "0.15.6",
12366
+ version: "0.15.7",
12161
12367
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
12162
12368
  type: "module",
12163
12369
  main: "dist/index.js",
@@ -12195,6 +12401,10 @@ var package_default = {
12195
12401
  "./testing": {
12196
12402
  types: "./dist/testing.d.ts",
12197
12403
  import: "./dist/testing.js"
12404
+ },
12405
+ "./project-registration": {
12406
+ types: "./dist/project-registration.d.ts",
12407
+ import: "./dist/project-registration.js"
12198
12408
  }
12199
12409
  },
12200
12410
  workspaces: [
@@ -12207,8 +12417,8 @@ var package_default = {
12207
12417
  "README.md"
12208
12418
  ],
12209
12419
  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/*'",
12420
+ 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",
12421
+ "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
12422
  migrate: "bun run src/server/index.ts migrate",
12213
12423
  "backfill:comment-redaction": "bun run src/server/index.ts redact-comments",
12214
12424
  "generate:sdk": "bun run scripts/generate-sdk.ts",
@@ -23851,6 +24061,13 @@ var TODOS_PACKAGE_EXPORTS = [
23851
24061
  types: "./dist/testing.d.ts",
23852
24062
  description: "Test-store isolation helpers that keep a consumer's test suite off a shared todos store.",
23853
24063
  stability: "stable"
24064
+ },
24065
+ {
24066
+ subpath: "./project-registration",
24067
+ import: "./dist/project-registration.js",
24068
+ types: "./dist/project-registration.d.ts",
24069
+ description: "Conditional Projects-to-Todos project and task-list registration authority.",
24070
+ stability: "stable"
23854
24071
  }
23855
24072
  ];
23856
24073
  function source8(version) {
@@ -31896,6 +32113,1696 @@ class PrGroupHttpClient {
31896
32113
  function createLocalPrGroupLedger(db = getDatabase()) {
31897
32114
  return new PrGroupLedger(new SqlitePrGroupLedgerPersistence(db));
31898
32115
  }
32116
+ // src/project-registration/authority.ts
32117
+ import { createHash as createHash12 } from "crypto";
32118
+ // src/project-registration/types.ts
32119
+ var TODOS_PROJECT_REGISTRATION_ROUTE = "todos.project-registration.v1";
32120
+ var TODOS_PROJECT_REGISTRATION_CALLER_ROUTE = "projects.full-registration.v1";
32121
+ var TODOS_PROJECT_REGISTRATION_SCHEMA_VERSION = 1;
32122
+
32123
+ class TodosProjectRegistrationError extends Error {
32124
+ code;
32125
+ details;
32126
+ constructor(code, message, details = {}) {
32127
+ super(message);
32128
+ this.code = code;
32129
+ this.details = details;
32130
+ this.name = "TodosProjectRegistrationError";
32131
+ }
32132
+ }
32133
+
32134
+ // src/project-registration/postgres.ts
32135
+ function safeIdentifier(value, field2) {
32136
+ if (!/^[a-z_][a-z0-9_]*$/.test(value)) {
32137
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field2} must be a safe PostgreSQL identifier`);
32138
+ }
32139
+ return value;
32140
+ }
32141
+ function normalizeTimestamp(value) {
32142
+ return value instanceof Date ? value.toISOString() : new Date(String(value)).toISOString();
32143
+ }
32144
+ function parsePayload3(value) {
32145
+ if (typeof value === "string")
32146
+ return JSON.parse(value);
32147
+ return value;
32148
+ }
32149
+ function receiptFromRow(row) {
32150
+ return {
32151
+ ...row,
32152
+ authority: "todos",
32153
+ created_by_operation: Boolean(row["created_by_operation"]),
32154
+ created_at: normalizeTimestamp(row["created_at"])
32155
+ };
32156
+ }
32157
+ function bindingFromRow(row) {
32158
+ return {
32159
+ ...row,
32160
+ created_at: normalizeTimestamp(row["created_at"]),
32161
+ updated_at: normalizeTimestamp(row["updated_at"])
32162
+ };
32163
+ }
32164
+
32165
+ class PostgresTodosProjectRegistrationTransaction {
32166
+ client;
32167
+ service;
32168
+ tableName;
32169
+ storage;
32170
+ constructor(client, service, tableName, cursorTableName) {
32171
+ this.client = client;
32172
+ this.service = service;
32173
+ this.tableName = tableName;
32174
+ this.storage = createPostgresTodosStorageAdapter({
32175
+ client,
32176
+ service,
32177
+ tableName,
32178
+ cursorTableName
32179
+ });
32180
+ }
32181
+ async lockStep(identity) {
32182
+ const key = [
32183
+ identity.authority_id,
32184
+ identity.tenant_id,
32185
+ identity.corpus_id,
32186
+ identity.operation_id,
32187
+ identity.step_id,
32188
+ identity.resource_kind,
32189
+ identity.direction
32190
+ ].join("\x1F");
32191
+ await this.client.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [key]);
32192
+ }
32193
+ async getReceiptForLookup(identity) {
32194
+ const result = await this.client.query(`
32195
+ SELECT * FROM todos_project_registration_receipts
32196
+ WHERE authority_id = $1 AND tenant_id = $2 AND corpus_id = $3
32197
+ AND operation_id = $4 AND step_id = $5 AND resource_kind = $6
32198
+ AND direction = $7 AND idempotency_key = $8 AND target_selector = $9
32199
+ ORDER BY CASE outcome
32200
+ WHEN 'terminal_nonacceptance' THEN 0
32201
+ WHEN 'duplicate_of_accepted' THEN 1
32202
+ ELSE 2
32203
+ END, created_at DESC, receipt_id DESC
32204
+ LIMIT 1
32205
+ `, [
32206
+ identity.authority_id,
32207
+ identity.tenant_id,
32208
+ identity.corpus_id,
32209
+ identity.operation_id,
32210
+ identity.step_id,
32211
+ identity.resource_kind,
32212
+ identity.direction,
32213
+ identity.idempotency_key,
32214
+ identity.target_selector
32215
+ ]);
32216
+ return result.rows[0] ? receiptFromRow(result.rows[0]) : null;
32217
+ }
32218
+ async getReceiptById(receiptId) {
32219
+ const result = await this.client.query("SELECT * FROM todos_project_registration_receipts WHERE receipt_id = $1 LIMIT 1", [receiptId]);
32220
+ return result.rows[0] ? receiptFromRow(result.rows[0]) : null;
32221
+ }
32222
+ async getAcceptedReceiptForStep(identity) {
32223
+ const result = await this.client.query(`
32224
+ SELECT * FROM todos_project_registration_receipts
32225
+ WHERE authority_id = $1 AND tenant_id = $2 AND corpus_id = $3
32226
+ AND operation_id = $4 AND step_id = $5 AND resource_kind = $6
32227
+ AND direction = $7 AND outcome = 'accepted'
32228
+ ORDER BY created_at ASC, receipt_id ASC
32229
+ LIMIT 1
32230
+ FOR UPDATE
32231
+ `, [
32232
+ identity.authority_id,
32233
+ identity.tenant_id,
32234
+ identity.corpus_id,
32235
+ identity.operation_id,
32236
+ identity.step_id,
32237
+ identity.resource_kind,
32238
+ identity.direction
32239
+ ]);
32240
+ return result.rows[0] ? receiptFromRow(result.rows[0]) : null;
32241
+ }
32242
+ async insertReceipt(receipt) {
32243
+ const result = await this.client.query(`
32244
+ INSERT INTO todos_project_registration_receipts (
32245
+ receipt_id, authority, route, package_version, authority_id, tenant_id,
32246
+ corpus_id, operation_id, step_id, resource_kind, direction,
32247
+ target_selector, idempotency_key, request_digest, precondition_digest,
32248
+ normalized_call_digest, outcome, reason, target_id, result_revision,
32249
+ result_digest, duplicate_of_receipt_id, accepted_receipt_id,
32250
+ created_by_operation, created_at
32251
+ ) VALUES (
32252
+ $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,
32253
+ $19,$20,$21,$22,$23,$24,$25
32254
+ )
32255
+ ON CONFLICT (receipt_id) DO NOTHING
32256
+ RETURNING receipt_id
32257
+ `, [
32258
+ receipt.receipt_id,
32259
+ receipt.authority,
32260
+ receipt.route,
32261
+ receipt.package_version,
32262
+ receipt.authority_id,
32263
+ receipt.tenant_id,
32264
+ receipt.corpus_id,
32265
+ receipt.operation_id,
32266
+ receipt.step_id,
32267
+ receipt.resource_kind,
32268
+ receipt.direction,
32269
+ receipt.target_selector,
32270
+ receipt.idempotency_key,
32271
+ receipt.request_digest,
32272
+ receipt.precondition_digest,
32273
+ receipt.normalized_call_digest,
32274
+ receipt.outcome,
32275
+ receipt.reason,
32276
+ receipt.target_id,
32277
+ receipt.result_revision,
32278
+ receipt.result_digest,
32279
+ receipt.duplicate_of_receipt_id,
32280
+ receipt.accepted_receipt_id,
32281
+ receipt.created_by_operation,
32282
+ receipt.created_at
32283
+ ]);
32284
+ return result.rows.length === 1;
32285
+ }
32286
+ async getBinding(scope, resourceKind, targetSelector) {
32287
+ const result = await this.client.query(`
32288
+ SELECT * FROM todos_project_registration_bindings
32289
+ WHERE authority_id = $1 AND tenant_id = $2 AND corpus_id = $3
32290
+ AND resource_kind = $4 AND target_selector = $5
32291
+ LIMIT 1
32292
+ FOR UPDATE
32293
+ `, [
32294
+ scope.authority_id,
32295
+ scope.tenant_id,
32296
+ scope.corpus_id,
32297
+ resourceKind,
32298
+ targetSelector
32299
+ ]);
32300
+ return result.rows[0] ? bindingFromRow(result.rows[0]) : null;
32301
+ }
32302
+ async claimBinding(binding) {
32303
+ const result = await this.client.query(`
32304
+ INSERT INTO todos_project_registration_bindings (
32305
+ authority_id, tenant_id, corpus_id, resource_kind, target_selector,
32306
+ operation_id, step_id, direction, idempotency_key, request_digest,
32307
+ precondition_digest, normalized_call_digest, state, target_id,
32308
+ accepted_receipt_id, result_revision, result_digest, removed_receipt_id,
32309
+ created_at, updated_at
32310
+ ) VALUES (
32311
+ $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,
32312
+ $18,$19,$20
32313
+ )
32314
+ ON CONFLICT (
32315
+ authority_id, tenant_id, corpus_id, resource_kind, target_selector
32316
+ ) DO NOTHING
32317
+ RETURNING target_selector
32318
+ `, [
32319
+ binding.authority_id,
32320
+ binding.tenant_id,
32321
+ binding.corpus_id,
32322
+ binding.resource_kind,
32323
+ binding.target_selector,
32324
+ binding.operation_id,
32325
+ binding.step_id,
32326
+ binding.direction,
32327
+ binding.idempotency_key,
32328
+ binding.request_digest,
32329
+ binding.precondition_digest,
32330
+ binding.normalized_call_digest,
32331
+ binding.state,
32332
+ binding.target_id,
32333
+ binding.accepted_receipt_id,
32334
+ binding.result_revision,
32335
+ binding.result_digest,
32336
+ binding.removed_receipt_id,
32337
+ binding.created_at,
32338
+ binding.updated_at
32339
+ ]);
32340
+ return result.rows.length === 1;
32341
+ }
32342
+ async setBindingAccepted(scope, resourceKind, targetSelector, update) {
32343
+ const result = await this.client.query(`
32344
+ UPDATE todos_project_registration_bindings
32345
+ SET state = 'accepted', target_id = $1, accepted_receipt_id = $2,
32346
+ result_revision = $3, result_digest = $4, updated_at = $5
32347
+ WHERE authority_id = $6 AND tenant_id = $7 AND corpus_id = $8
32348
+ AND resource_kind = $9 AND target_selector = $10 AND state = 'pending'
32349
+ RETURNING target_selector
32350
+ `, [
32351
+ update.target_id,
32352
+ update.accepted_receipt_id,
32353
+ update.result_revision,
32354
+ update.result_digest,
32355
+ update.updated_at,
32356
+ scope.authority_id,
32357
+ scope.tenant_id,
32358
+ scope.corpus_id,
32359
+ resourceKind,
32360
+ targetSelector
32361
+ ]);
32362
+ if (result.rows.length !== 1) {
32363
+ throw new Error("Todos project registration binding was not pending at acceptance");
32364
+ }
32365
+ }
32366
+ async setBindingTerminal(scope, resourceKind, targetSelector, updatedAt) {
32367
+ await this.client.query(`
32368
+ UPDATE todos_project_registration_bindings
32369
+ SET state = 'terminal_nonacceptance', updated_at = $1
32370
+ WHERE authority_id = $2 AND tenant_id = $3 AND corpus_id = $4
32371
+ AND resource_kind = $5 AND target_selector = $6 AND state = 'pending'
32372
+ `, [
32373
+ updatedAt,
32374
+ scope.authority_id,
32375
+ scope.tenant_id,
32376
+ scope.corpus_id,
32377
+ resourceKind,
32378
+ targetSelector
32379
+ ]);
32380
+ }
32381
+ async setBindingRemoved(scope, resourceKind, targetSelector, removedReceiptId, updatedAt) {
32382
+ const result = await this.client.query(`
32383
+ UPDATE todos_project_registration_bindings
32384
+ SET state = 'removed', removed_receipt_id = $1, updated_at = $2
32385
+ WHERE authority_id = $3 AND tenant_id = $4 AND corpus_id = $5
32386
+ AND resource_kind = $6 AND target_selector = $7 AND state = 'accepted'
32387
+ RETURNING target_selector
32388
+ `, [
32389
+ removedReceiptId,
32390
+ updatedAt,
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 accepted at removal");
32399
+ }
32400
+ }
32401
+ async findProjectConflict(path, taskListSlug) {
32402
+ const result = await this.client.query(`
32403
+ SELECT payload FROM ${this.tableName}
32404
+ WHERE service = $1 AND object_type = 'projects' AND deleted_at IS NULL
32405
+ AND (payload->>'path' = $2 OR payload->>'task_list_id' = $3)
32406
+ ORDER BY payload->>'created_at' ASC, object_id ASC
32407
+ LIMIT 1
32408
+ `, [this.service, path, taskListSlug]);
32409
+ return result.rows[0] ? parsePayload3(result.rows[0].payload) : null;
32410
+ }
32411
+ async findTaskListConflict(projectId, slug) {
32412
+ const result = await this.client.query(`
32413
+ SELECT payload FROM ${this.tableName}
32414
+ WHERE service = $1 AND object_type = 'task_lists' AND deleted_at IS NULL
32415
+ AND payload->>'project_id' = $2 AND payload->>'slug' = $3
32416
+ ORDER BY payload->>'created_at' ASC, object_id ASC
32417
+ LIMIT 1
32418
+ `, [this.service, projectId, slug]);
32419
+ return result.rows[0] ? parsePayload3(result.rows[0].payload) : null;
32420
+ }
32421
+ async createProject(input) {
32422
+ return await this.storage.projects.create(input);
32423
+ }
32424
+ async createTaskList(input) {
32425
+ return await this.storage.taskLists.create(input);
32426
+ }
32427
+ async getProject(id) {
32428
+ return await this.storage.projects.get(id);
32429
+ }
32430
+ async getTaskList(id) {
32431
+ return await this.storage.taskLists.get(id);
32432
+ }
32433
+ async lockCompensationWrites() {
32434
+ await this.client.query(`LOCK TABLE ${this.tableName} IN SHARE ROW EXCLUSIVE MODE`);
32435
+ }
32436
+ async hasDependents(resourceKind, targetId) {
32437
+ const referencePredicate = resourceKind === "project" ? `(
32438
+ payload->>'project_id' = $2
32439
+ OR payload->>'active_project_id' = $2
32440
+ OR payload->>'assigned_from_project' = $2
32441
+ OR payload->>'external_project_id' = $2
32442
+ )` : "payload->>'task_list_id' = $2";
32443
+ const result = await this.client.query(`
32444
+ SELECT EXISTS (
32445
+ SELECT 1 FROM ${this.tableName}
32446
+ WHERE service = $1 AND deleted_at IS NULL
32447
+ AND ${referencePredicate}
32448
+ LIMIT 1
32449
+ ) AS exists
32450
+ `, [this.service, targetId]);
32451
+ return result.rows[0]?.exists === true;
32452
+ }
32453
+ async deleteProject(id) {
32454
+ return await this.storage.projects.delete(id);
32455
+ }
32456
+ async deleteTaskList(id) {
32457
+ return await this.storage.taskLists.delete(id);
32458
+ }
32459
+ }
32460
+
32461
+ class PostgresTodosProjectRegistrationBackend {
32462
+ client;
32463
+ kind = "postgresql";
32464
+ service;
32465
+ tableName;
32466
+ cursorTableName;
32467
+ schemaReady = null;
32468
+ constructor(client, options = {}) {
32469
+ this.client = client;
32470
+ this.service = options.service ?? "todos";
32471
+ this.tableName = safeIdentifier(options.tableName ?? "todos_sync_records", "tableName");
32472
+ this.cursorTableName = safeIdentifier(options.cursorTableName ?? "todos_sync_cursors", "cursorTableName");
32473
+ }
32474
+ async ensureSchema() {
32475
+ this.schemaReady ??= (async () => {
32476
+ for (const statement of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
32477
+ await this.client.query(statement);
32478
+ }
32479
+ for (const statement of postgresTodosProjectRegistrationSchemaSql()) {
32480
+ await this.client.query(statement);
32481
+ }
32482
+ })();
32483
+ await this.schemaReady;
32484
+ }
32485
+ async transaction(fn) {
32486
+ await this.ensureSchema();
32487
+ if (typeof this.client.transaction !== "function") {
32488
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE", "PostgreSQL project registration requires an authoritative transaction");
32489
+ }
32490
+ return this.client.transaction((transaction) => fn(new PostgresTodosProjectRegistrationTransaction(transaction, this.service, this.tableName, this.cursorTableName)));
32491
+ }
32492
+ async direct() {
32493
+ await this.ensureSchema();
32494
+ return new PostgresTodosProjectRegistrationTransaction(this.client, this.service, this.tableName, this.cursorTableName);
32495
+ }
32496
+ async getReceiptForLookup(identity) {
32497
+ return (await this.direct()).getReceiptForLookup(identity);
32498
+ }
32499
+ async getReceiptById(receiptId) {
32500
+ return (await this.direct()).getReceiptById(receiptId);
32501
+ }
32502
+ async getBinding(scope, resourceKind, targetSelector) {
32503
+ return (await this.direct()).getBinding(scope, resourceKind, targetSelector);
32504
+ }
32505
+ async getProject(id) {
32506
+ return (await this.direct()).getProject(id);
32507
+ }
32508
+ async getTaskList(id) {
32509
+ return (await this.direct()).getTaskList(id);
32510
+ }
32511
+ }
32512
+
32513
+ // src/project-registration/sqlite.ts
32514
+ var sqliteTransactionTails2 = new WeakMap;
32515
+ var PROJECT_REFERENCE_COLUMNS = new Set([
32516
+ "project_id",
32517
+ "active_project_id",
32518
+ "assigned_from_project",
32519
+ "external_project_id"
32520
+ ]);
32521
+ var TASK_LIST_REFERENCE_COLUMNS = new Set(["task_list_id"]);
32522
+ function quoteSqliteIdentifier(value) {
32523
+ return `"${value.replaceAll('"', '""')}"`;
32524
+ }
32525
+ function receiptFromRow2(row) {
32526
+ return {
32527
+ ...row,
32528
+ authority: "todos",
32529
+ created_by_operation: Number(row["created_by_operation"]) === 1
32530
+ };
32531
+ }
32532
+ function bindingFromRow2(row) {
32533
+ return row;
32534
+ }
32535
+
32536
+ class SqliteTodosProjectRegistrationTransaction {
32537
+ db;
32538
+ storage;
32539
+ constructor(db) {
32540
+ this.db = db;
32541
+ this.storage = createLocalSqliteTodosStorageAdapter({ db });
32542
+ }
32543
+ async lockStep(_identity) {}
32544
+ async getReceiptForLookup(identity) {
32545
+ const row = this.db.query(`
32546
+ SELECT * FROM todos_project_registration_receipts
32547
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
32548
+ AND operation_id = ? AND step_id = ? AND resource_kind = ?
32549
+ AND direction = ? AND idempotency_key = ? AND target_selector = ?
32550
+ ORDER BY CASE outcome
32551
+ WHEN 'terminal_nonacceptance' THEN 0
32552
+ WHEN 'duplicate_of_accepted' THEN 1
32553
+ ELSE 2
32554
+ END, created_at DESC, receipt_id DESC
32555
+ LIMIT 1
32556
+ `).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);
32557
+ return row ? receiptFromRow2(row) : null;
32558
+ }
32559
+ async getReceiptById(receiptId) {
32560
+ const row = this.db.query("SELECT * FROM todos_project_registration_receipts WHERE receipt_id = ? LIMIT 1").get(receiptId);
32561
+ return row ? receiptFromRow2(row) : null;
32562
+ }
32563
+ async getAcceptedReceiptForStep(identity) {
32564
+ const row = this.db.query(`
32565
+ SELECT * FROM todos_project_registration_receipts
32566
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
32567
+ AND operation_id = ? AND step_id = ? AND resource_kind = ?
32568
+ AND direction = ? AND outcome = 'accepted'
32569
+ ORDER BY created_at ASC, receipt_id ASC
32570
+ LIMIT 1
32571
+ `).get(identity.authority_id, identity.tenant_id, identity.corpus_id, identity.operation_id, identity.step_id, identity.resource_kind, identity.direction);
32572
+ return row ? receiptFromRow2(row) : null;
32573
+ }
32574
+ async insertReceipt(receipt) {
32575
+ const result = this.db.query(`
32576
+ INSERT OR IGNORE INTO todos_project_registration_receipts (
32577
+ receipt_id, authority, route, package_version, authority_id, tenant_id,
32578
+ corpus_id, operation_id, step_id, resource_kind, direction,
32579
+ target_selector, idempotency_key, request_digest, precondition_digest,
32580
+ normalized_call_digest, outcome, reason, target_id, result_revision,
32581
+ result_digest, duplicate_of_receipt_id, accepted_receipt_id,
32582
+ created_by_operation, created_at
32583
+ ) VALUES (
32584
+ ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?
32585
+ )
32586
+ `).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);
32587
+ return result.changes === 1;
32588
+ }
32589
+ async getBinding(scope, resourceKind, targetSelector) {
32590
+ const row = this.db.query(`
32591
+ SELECT * FROM todos_project_registration_bindings
32592
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
32593
+ AND resource_kind = ? AND target_selector = ?
32594
+ LIMIT 1
32595
+ `).get(scope.authority_id, scope.tenant_id, scope.corpus_id, resourceKind, targetSelector);
32596
+ return row ? bindingFromRow2(row) : null;
32597
+ }
32598
+ async claimBinding(binding) {
32599
+ const result = this.db.query(`
32600
+ INSERT OR IGNORE INTO todos_project_registration_bindings (
32601
+ authority_id, tenant_id, corpus_id, resource_kind, target_selector,
32602
+ operation_id, step_id, direction, idempotency_key, request_digest,
32603
+ precondition_digest, normalized_call_digest, state, target_id,
32604
+ accepted_receipt_id, result_revision, result_digest, removed_receipt_id,
32605
+ created_at, updated_at
32606
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
32607
+ `).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);
32608
+ return result.changes === 1;
32609
+ }
32610
+ async setBindingAccepted(scope, resourceKind, targetSelector, update) {
32611
+ const result = this.db.query(`
32612
+ UPDATE todos_project_registration_bindings
32613
+ SET state = 'accepted', target_id = ?, accepted_receipt_id = ?,
32614
+ result_revision = ?, result_digest = ?, updated_at = ?
32615
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
32616
+ AND resource_kind = ? AND target_selector = ? AND state = 'pending'
32617
+ `).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);
32618
+ if (result.changes !== 1) {
32619
+ throw new Error("Todos project registration binding was not pending at acceptance");
32620
+ }
32621
+ }
32622
+ async setBindingTerminal(scope, resourceKind, targetSelector, updatedAt) {
32623
+ this.db.query(`
32624
+ UPDATE todos_project_registration_bindings
32625
+ SET state = 'terminal_nonacceptance', updated_at = ?
32626
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
32627
+ AND resource_kind = ? AND target_selector = ? AND state = 'pending'
32628
+ `).run(updatedAt, scope.authority_id, scope.tenant_id, scope.corpus_id, resourceKind, targetSelector);
32629
+ }
32630
+ async setBindingRemoved(scope, resourceKind, targetSelector, removedReceiptId, updatedAt) {
32631
+ const result = this.db.query(`
32632
+ UPDATE todos_project_registration_bindings
32633
+ SET state = 'removed', removed_receipt_id = ?, updated_at = ?
32634
+ WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
32635
+ AND resource_kind = ? AND target_selector = ? AND state = 'accepted'
32636
+ `).run(removedReceiptId, updatedAt, scope.authority_id, scope.tenant_id, scope.corpus_id, resourceKind, targetSelector);
32637
+ if (result.changes !== 1) {
32638
+ throw new Error("Todos project registration binding was not accepted at removal");
32639
+ }
32640
+ }
32641
+ async findProjectConflict(path, taskListSlug) {
32642
+ const row = this.db.query(`
32643
+ SELECT * FROM projects
32644
+ WHERE path = ? OR task_list_id = ?
32645
+ ORDER BY created_at ASC, id ASC
32646
+ LIMIT 1
32647
+ `).get(path, taskListSlug);
32648
+ return row ?? null;
32649
+ }
32650
+ async findTaskListConflict(projectId, slug) {
32651
+ return await this.storage.taskLists.getBySlug(slug, projectId);
32652
+ }
32653
+ async createProject(input) {
32654
+ return await this.storage.projects.create(input);
32655
+ }
32656
+ async createTaskList(input) {
32657
+ return await this.storage.taskLists.create(input);
32658
+ }
32659
+ async getProject(id) {
32660
+ return await this.storage.projects.get(id);
32661
+ }
32662
+ async getTaskList(id) {
32663
+ return await this.storage.taskLists.get(id);
32664
+ }
32665
+ async lockCompensationWrites() {}
32666
+ async hasDependents(resourceKind, targetId) {
32667
+ const targetTable = resourceKind === "project" ? "projects" : "task_lists";
32668
+ const semanticColumns = resourceKind === "project" ? PROJECT_REFERENCE_COLUMNS : TASK_LIST_REFERENCE_COLUMNS;
32669
+ const tables = this.db.query(`
32670
+ SELECT name FROM sqlite_schema
32671
+ WHERE type = 'table' AND name NOT LIKE 'sqlite_%'
32672
+ ORDER BY name
32673
+ `).all();
32674
+ for (const { name: tableName } of tables) {
32675
+ const quotedTable = quoteSqliteIdentifier(tableName);
32676
+ const columns = this.db.query(`PRAGMA table_info(${quotedTable})`).all();
32677
+ const foreignKeys = this.db.query(`PRAGMA foreign_key_list(${quotedTable})`).all();
32678
+ const referenceColumns = columns.map((column) => column.name).filter((columnName) => semanticColumns.has(columnName) || foreignKeys.some((foreignKey) => foreignKey.from === columnName && foreignKey.table === targetTable));
32679
+ for (const columnName of referenceColumns) {
32680
+ const row = this.db.query(`
32681
+ SELECT 1 AS found
32682
+ FROM ${quotedTable}
32683
+ WHERE ${quoteSqliteIdentifier(columnName)} = ?
32684
+ LIMIT 1
32685
+ `).get(targetId);
32686
+ if (row)
32687
+ return true;
32688
+ }
32689
+ }
32690
+ return false;
32691
+ }
32692
+ async deleteProject(id) {
32693
+ return await this.storage.projects.delete(id);
32694
+ }
32695
+ async deleteTaskList(id) {
32696
+ return await this.storage.taskLists.delete(id);
32697
+ }
32698
+ }
32699
+
32700
+ class SqliteTodosProjectRegistrationBackend {
32701
+ db;
32702
+ kind = "sqlite";
32703
+ direct;
32704
+ constructor(db) {
32705
+ this.db = db;
32706
+ db.exec(sqliteTodosProjectRegistrationSchemaSql());
32707
+ this.direct = new SqliteTodosProjectRegistrationTransaction(db);
32708
+ }
32709
+ async transaction(fn) {
32710
+ const previous = sqliteTransactionTails2.get(this.db) ?? Promise.resolve();
32711
+ let release;
32712
+ const current = new Promise((resolve10) => {
32713
+ release = resolve10;
32714
+ });
32715
+ sqliteTransactionTails2.set(this.db, current);
32716
+ await previous;
32717
+ try {
32718
+ this.db.exec("BEGIN IMMEDIATE");
32719
+ const result = await fn(new SqliteTodosProjectRegistrationTransaction(this.db));
32720
+ this.db.exec("COMMIT");
32721
+ return result;
32722
+ } catch (error) {
32723
+ try {
32724
+ this.db.exec("ROLLBACK");
32725
+ } catch {}
32726
+ throw error;
32727
+ } finally {
32728
+ release();
32729
+ if (sqliteTransactionTails2.get(this.db) === current) {
32730
+ sqliteTransactionTails2.delete(this.db);
32731
+ }
32732
+ }
32733
+ }
32734
+ getReceiptForLookup(identity) {
32735
+ return this.direct.getReceiptForLookup(identity);
32736
+ }
32737
+ getReceiptById(receiptId) {
32738
+ return this.direct.getReceiptById(receiptId);
32739
+ }
32740
+ getBinding(scope, resourceKind, targetSelector) {
32741
+ return this.direct.getBinding(scope, resourceKind, targetSelector);
32742
+ }
32743
+ getProject(id) {
32744
+ return this.direct.getProject(id);
32745
+ }
32746
+ getTaskList(id) {
32747
+ return this.direct.getTaskList(id);
32748
+ }
32749
+ }
32750
+
32751
+ // src/project-registration/authority.ts
32752
+ 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;
32753
+ var WORKSPACE_ID_PATTERN = /^wks_[A-Za-z0-9][A-Za-z0-9_-]{11,}$/;
32754
+ var OPERATION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
32755
+ var STEP_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$/;
32756
+ var SHA256_PATTERN = /^[0-9a-f]{64}$/;
32757
+ var IDEMPOTENCY_PATTERN = /^prk_[0-9a-f]{48}$/;
32758
+
32759
+ class WriteBoundaryError extends Error {
32760
+ point;
32761
+ cause;
32762
+ constructor(point, cause) {
32763
+ super(`Todos project registration failed at ${point}`);
32764
+ this.point = point;
32765
+ this.cause = cause;
32766
+ }
32767
+ }
32768
+ function canonicalProjectRegistrationJson(value) {
32769
+ return JSON.stringify(canonicalize3(value));
32770
+ }
32771
+ function canonicalize3(value) {
32772
+ if (Array.isArray(value))
32773
+ return value.map(canonicalize3);
32774
+ if (!value || typeof value !== "object")
32775
+ return value;
32776
+ const out = {};
32777
+ for (const key of Object.keys(value).sort()) {
32778
+ const entry2 = value[key];
32779
+ if (entry2 !== undefined)
32780
+ out[key] = canonicalize3(entry2);
32781
+ }
32782
+ return out;
32783
+ }
32784
+ function digestProjectRegistrationValue(value) {
32785
+ return createHash12("sha256").update(canonicalProjectRegistrationJson(value)).digest("hex");
32786
+ }
32787
+ function deriveTodosProjectRegistrationIdempotencyKey(input) {
32788
+ return `prk_${digestProjectRegistrationValue({
32789
+ route: TODOS_PROJECT_REGISTRATION_CALLER_ROUTE,
32790
+ ...input
32791
+ }).slice(0, 48)}`;
32792
+ }
32793
+ function responseBytes(value) {
32794
+ return Buffer.byteLength(JSON.stringify(value), "utf8");
32795
+ }
32796
+ function assertBounds(bounds) {
32797
+ if (!Number.isSafeInteger(bounds.response_byte_limit) || bounds.response_byte_limit <= 0) {
32798
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "response_byte_limit must be a positive integer");
32799
+ }
32800
+ if (!Number.isSafeInteger(bounds.time_budget_ms) || bounds.time_budget_ms <= 0) {
32801
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "time_budget_ms must be a positive integer");
32802
+ }
32803
+ }
32804
+ function assertResourceKind(value) {
32805
+ if (value !== "project" && value !== "task_list") {
32806
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "resource_kind must be project or task_list");
32807
+ }
32808
+ }
32809
+ function assertDirection(value) {
32810
+ if (value !== "forward" && value !== "inverse") {
32811
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "direction must be forward or inverse");
32812
+ }
32813
+ }
32814
+ function assertWithinBounds(value, bounds, startedAt) {
32815
+ const bytes = responseBytes(value);
32816
+ if (bytes > bounds.response_byte_limit) {
32817
+ 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 });
32818
+ }
32819
+ const elapsed = Date.now() - startedAt;
32820
+ if (elapsed > bounds.time_budget_ms) {
32821
+ 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 });
32822
+ }
32823
+ return { response_bytes: bytes, elapsed_ms: elapsed };
32824
+ }
32825
+ function withResponseControl(payload, bounds, startedAt) {
32826
+ const envelope = {
32827
+ ...payload,
32828
+ response_control: {
32829
+ response_byte_limit: bounds.response_byte_limit,
32830
+ time_budget_ms: bounds.time_budget_ms,
32831
+ response_bytes: 0,
32832
+ elapsed_ms: 0,
32833
+ complete: true,
32834
+ truncated: false
32835
+ }
32836
+ };
32837
+ for (let attempt = 0;attempt < 8; attempt += 1) {
32838
+ const measured = assertWithinBounds(envelope, bounds, startedAt);
32839
+ const stable2 = envelope.response_control.response_bytes === measured.response_bytes && envelope.response_control.elapsed_ms === measured.elapsed_ms;
32840
+ envelope.response_control = {
32841
+ response_byte_limit: bounds.response_byte_limit,
32842
+ time_budget_ms: bounds.time_budget_ms,
32843
+ response_bytes: measured.response_bytes,
32844
+ elapsed_ms: measured.elapsed_ms,
32845
+ complete: true,
32846
+ truncated: false
32847
+ };
32848
+ if (stable2)
32849
+ break;
32850
+ }
32851
+ const finalMeasurement = assertWithinBounds(envelope, bounds, startedAt);
32852
+ envelope.response_control.response_bytes = finalMeasurement.response_bytes;
32853
+ envelope.response_control.elapsed_ms = finalMeasurement.elapsed_ms;
32854
+ return envelope;
32855
+ }
32856
+ function requireString(value, field2, options = {}) {
32857
+ const min = options.min ?? 1;
32858
+ const max = options.max ?? 512;
32859
+ if (typeof value !== "string" || value.length < min || value.length > max || /[\u0000-\u001f]/.test(value) || options.pattern && !options.pattern.test(value)) {
32860
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field2} is not a valid bounded registration identifier`);
32861
+ }
32862
+ return value;
32863
+ }
32864
+ function exactKeys(value, expected, field2) {
32865
+ const actual = Object.keys(value).sort();
32866
+ const wanted = [...expected].sort();
32867
+ if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) {
32868
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field2} must contain exactly: ${wanted.join(", ")}`);
32869
+ }
32870
+ }
32871
+ function publicReceipt(row) {
32872
+ const {
32873
+ target_selector: _targetSelector,
32874
+ normalized_call_digest: _normalizedCallDigest,
32875
+ ...receipt
32876
+ } = row;
32877
+ return receipt;
32878
+ }
32879
+ function projectRegistrationPath(projectId) {
32880
+ return `hasna-project://${encodeURIComponent(projectId)}`;
32881
+ }
32882
+ function taskListSlug(projectSlug) {
32883
+ const slug = normalizeSlug(projectSlug);
32884
+ if (!slug || slug !== projectSlug) {
32885
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "project_slug must be canonical kebab-case");
32886
+ }
32887
+ return `todos-${slug}`;
32888
+ }
32889
+ function deterministicTaskPrefix(projectSlug) {
32890
+ const letters = projectSlug.replace(/[^a-z0-9]/gi, "").toUpperCase();
32891
+ return (letters.slice(0, 3) || "PRJ").padEnd(3, "X");
32892
+ }
32893
+ function projectRecord(project) {
32894
+ return {
32895
+ target_id: project.id,
32896
+ revision: project.updated_at,
32897
+ digest: digestProjectRegistrationValue({
32898
+ id: project.id,
32899
+ name: project.name,
32900
+ path: project.path,
32901
+ description: project.description,
32902
+ task_list_id: project.task_list_id,
32903
+ task_prefix: project.task_prefix,
32904
+ task_counter: project.task_counter,
32905
+ created_at: project.created_at,
32906
+ updated_at: project.updated_at
32907
+ })
32908
+ };
32909
+ }
32910
+ function taskListRecord(taskList) {
32911
+ return {
32912
+ target_id: taskList.id,
32913
+ revision: taskList.updated_at,
32914
+ digest: digestProjectRegistrationValue({
32915
+ id: taskList.id,
32916
+ project_id: taskList.project_id,
32917
+ slug: taskList.slug,
32918
+ name: taskList.name,
32919
+ description: taskList.description,
32920
+ metadata: taskList.metadata,
32921
+ created_at: taskList.created_at,
32922
+ updated_at: taskList.updated_at
32923
+ })
32924
+ };
32925
+ }
32926
+ function receiptId(input) {
32927
+ return `tpr_${digestProjectRegistrationValue(input).slice(0, 40)}`;
32928
+ }
32929
+ function capabilityMatches(request, capability2) {
32930
+ 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;
32931
+ }
32932
+ function authorityScope(capability2) {
32933
+ return {
32934
+ authority_id: capability2.authority_id,
32935
+ tenant_id: capability2.tenant_id,
32936
+ corpus_id: capability2.corpus_id
32937
+ };
32938
+ }
32939
+ function assertCapabilityRequest(request, capability2) {
32940
+ if (!capabilityMatches(request, capability2)) {
32941
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH", "registration request does not match this authority capability identity");
32942
+ }
32943
+ }
32944
+ function normalizedCallDigest(request) {
32945
+ return digestProjectRegistrationValue({
32946
+ authority_route: request.authority_route,
32947
+ package_version: request.package_version,
32948
+ authority_id: request.authority_id,
32949
+ tenant_id: request.tenant_id,
32950
+ corpus_id: request.corpus_id,
32951
+ operation_id: request.operation_id,
32952
+ step_id: request.step_id,
32953
+ resource_kind: request.resource_kind,
32954
+ direction: request.direction,
32955
+ target_selector: request.target_selector,
32956
+ idempotency_key: request.idempotency_key,
32957
+ request_digest: request.request_digest,
32958
+ precondition_digest: request.precondition_digest,
32959
+ project_id: request.project_id,
32960
+ project_slug: request.project_slug,
32961
+ project_name: request.project_name,
32962
+ desired: request.desired,
32963
+ accepted_receipt_id: request.accepted_receipt?.receipt_id ?? null
32964
+ });
32965
+ }
32966
+ function assertCommonRequest(request, capability2) {
32967
+ assertBounds(request);
32968
+ assertResourceKind(request.resource_kind);
32969
+ assertDirection(request.direction);
32970
+ assertCapabilityRequest(request, capability2);
32971
+ requireString(request.operation_id, "operation_id", {
32972
+ min: 8,
32973
+ max: 128,
32974
+ pattern: OPERATION_PATTERN
32975
+ });
32976
+ requireString(request.step_id, "step_id", {
32977
+ min: 3,
32978
+ max: 128,
32979
+ pattern: STEP_PATTERN
32980
+ });
32981
+ requireString(request.target_selector, "target_selector", { max: 512 });
32982
+ requireString(request.project_id, "project_id", {
32983
+ min: 16,
32984
+ max: 128,
32985
+ pattern: WORKSPACE_ID_PATTERN
32986
+ });
32987
+ requireString(request.project_name, "project_name", { max: 256 });
32988
+ requireString(request.project_slug, "project_slug", { max: 128 });
32989
+ requireString(request.request_digest, "request_digest", {
32990
+ min: 64,
32991
+ max: 64,
32992
+ pattern: SHA256_PATTERN
32993
+ });
32994
+ requireString(request.precondition_digest, "precondition_digest", {
32995
+ min: 64,
32996
+ max: 64,
32997
+ pattern: SHA256_PATTERN
32998
+ });
32999
+ requireString(request.idempotency_key, "idempotency_key", {
33000
+ min: 52,
33001
+ max: 52,
33002
+ pattern: IDEMPOTENCY_PATTERN
33003
+ });
33004
+ if (!request.desired || typeof request.desired !== "object" || Array.isArray(request.desired)) {
33005
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "desired must be a JSON object");
33006
+ }
33007
+ const expectedKey = deriveTodosProjectRegistrationIdempotencyKey({
33008
+ operation_id: request.operation_id,
33009
+ step_id: request.step_id,
33010
+ direction: request.direction,
33011
+ target_selector: request.target_selector,
33012
+ request_digest: request.request_digest,
33013
+ precondition_digest: request.precondition_digest
33014
+ });
33015
+ if (request.idempotency_key !== expectedKey) {
33016
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_IDEMPOTENCY_MISMATCH", "idempotency_key does not match the deterministic operation/step/direction payload", { expected: expectedKey });
33017
+ }
33018
+ taskListSlug(request.project_slug);
33019
+ }
33020
+ function assertForwardRequest(request, capability2) {
33021
+ assertCommonRequest(request, capability2);
33022
+ if (request.direction !== "forward") {
33023
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "create requires direction=forward");
33024
+ }
33025
+ const expectedRequestDigest = digestProjectRegistrationValue(request.desired);
33026
+ const expectedPreconditionDigest = digestProjectRegistrationValue({
33027
+ target_selector: request.target_selector,
33028
+ expected: "absent"
33029
+ });
33030
+ if (request.request_digest !== expectedRequestDigest || request.precondition_digest !== expectedPreconditionDigest) {
33031
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "request_digest or precondition_digest does not match normalized forward semantics", {
33032
+ expected_request_digest: expectedRequestDigest,
33033
+ expected_precondition_digest: expectedPreconditionDigest
33034
+ });
33035
+ }
33036
+ if (request.resource_kind === "project") {
33037
+ exactKeys(request.desired, ["source_project_id", "source_project_slug", "name"], "project desired");
33038
+ 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) {
33039
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "project desired state and target selector must match the complete Projects identity");
33040
+ }
33041
+ return;
33042
+ }
33043
+ if (request.resource_kind === "task_list") {
33044
+ exactKeys(request.desired, ["todos_project_id", "source_project_id", "name"], "task-list desired");
33045
+ const todosProjectId = request.desired["todos_project_id"];
33046
+ if (typeof todosProjectId !== "string" || !UUID_PATTERN.test(todosProjectId)) {
33047
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED", "task-list create requires the exact full Todos project UUID");
33048
+ }
33049
+ if (request.target_selector !== `${todosProjectId}:default` || request.desired["source_project_id"] !== request.project_id || request.desired["name"] !== request.project_name) {
33050
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "task-list desired state must bind the exact Todos project id and Projects identity");
33051
+ }
33052
+ return;
33053
+ }
33054
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "unsupported registration resource kind");
33055
+ }
33056
+ function assertInverseRequest(request, capability2) {
33057
+ assertCommonRequest(request, capability2);
33058
+ if (request.direction !== "inverse") {
33059
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "compensate requires direction=inverse");
33060
+ }
33061
+ const accepted = request.accepted_receipt;
33062
+ 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) {
33063
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "inverse requires the complete accepted forward receipt created by this operation");
33064
+ }
33065
+ exactKeys(request.desired, ["accepted_receipt_id", "target_id"], "inverse desired");
33066
+ const expectedDesired = {
33067
+ accepted_receipt_id: accepted.receipt_id,
33068
+ target_id: accepted.target_id
33069
+ };
33070
+ const expectedPrecondition = {
33071
+ expected_revision: accepted.result_revision,
33072
+ expected_digest: accepted.result_digest
33073
+ };
33074
+ const expectedRequestDigest = digestProjectRegistrationValue(expectedDesired);
33075
+ const expectedPreconditionDigest = digestProjectRegistrationValue(expectedPrecondition);
33076
+ if (canonicalProjectRegistrationJson(request.desired) !== canonicalProjectRegistrationJson(expectedDesired) || request.request_digest !== expectedRequestDigest || request.precondition_digest !== expectedPreconditionDigest || request.target_selector !== accepted.target_id) {
33077
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "inverse request does not match the accepted receipt and exact readback precondition");
33078
+ }
33079
+ return accepted;
33080
+ }
33081
+ function makeReceipt(input, createdAt2) {
33082
+ return {
33083
+ ...input,
33084
+ receipt_id: receiptId(input),
33085
+ created_at: createdAt2
33086
+ };
33087
+ }
33088
+ function receiptBase(request, callDigest, capability2) {
33089
+ return {
33090
+ authority: "todos",
33091
+ route: capability2.route,
33092
+ package_version: capability2.package_version,
33093
+ authority_id: capability2.authority_id,
33094
+ tenant_id: capability2.tenant_id,
33095
+ corpus_id: capability2.corpus_id,
33096
+ operation_id: request.operation_id,
33097
+ step_id: request.step_id,
33098
+ resource_kind: request.resource_kind,
33099
+ direction: request.direction,
33100
+ target_selector: request.target_selector,
33101
+ idempotency_key: request.idempotency_key,
33102
+ request_digest: request.request_digest,
33103
+ precondition_digest: request.precondition_digest,
33104
+ normalized_call_digest: callDigest
33105
+ };
33106
+ }
33107
+ function makeAcceptedReceipt(request, callDigest, capability2, record, createdAt2) {
33108
+ return makeReceipt({
33109
+ ...receiptBase(request, callDigest, capability2),
33110
+ outcome: "accepted",
33111
+ reason: null,
33112
+ target_id: record.target_id,
33113
+ result_revision: record.revision,
33114
+ result_digest: record.digest,
33115
+ duplicate_of_receipt_id: null,
33116
+ accepted_receipt_id: request.direction === "inverse" ? request.accepted_receipt.receipt_id : null,
33117
+ created_by_operation: true
33118
+ }, createdAt2);
33119
+ }
33120
+ function makeDuplicateReceipt(request, callDigest, capability2, accepted, createdAt2) {
33121
+ return makeReceipt({
33122
+ ...receiptBase(request, callDigest, capability2),
33123
+ outcome: "duplicate_of_accepted",
33124
+ reason: null,
33125
+ target_id: accepted.target_id,
33126
+ result_revision: accepted.result_revision,
33127
+ result_digest: accepted.result_digest,
33128
+ duplicate_of_receipt_id: accepted.receipt_id,
33129
+ accepted_receipt_id: null,
33130
+ created_by_operation: false
33131
+ }, createdAt2);
33132
+ }
33133
+ function makeTerminalReceipt(request, callDigest, capability2, reason, createdAt2, options = {}) {
33134
+ return makeReceipt({
33135
+ ...receiptBase(request, callDigest, capability2),
33136
+ outcome: "terminal_nonacceptance",
33137
+ reason,
33138
+ target_id: options.targetId ?? null,
33139
+ result_revision: null,
33140
+ result_digest: null,
33141
+ duplicate_of_receipt_id: null,
33142
+ accepted_receipt_id: options.acceptedReceiptId ?? null,
33143
+ created_by_operation: false
33144
+ }, createdAt2);
33145
+ }
33146
+ async function insertDeterministicReceipt(transaction, receipt) {
33147
+ if (await transaction.insertReceipt(receipt))
33148
+ return receipt;
33149
+ const existing = await transaction.getReceiptById(receipt.receipt_id);
33150
+ const { created_at: _existingCreatedAt, ...existingContent } = existing ?? {};
33151
+ const { created_at: _receiptCreatedAt, ...receiptContent } = receipt;
33152
+ if (!existing || canonicalProjectRegistrationJson(existingContent) !== canonicalProjectRegistrationJson(receiptContent)) {
33153
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "deterministic receipt id is occupied by different immutable content", { receipt_id: receipt.receipt_id });
33154
+ }
33155
+ return existing;
33156
+ }
33157
+ function bindingFor(request, callDigest, timestamp3, capability2) {
33158
+ return {
33159
+ ...authorityScope(capability2),
33160
+ resource_kind: request.resource_kind,
33161
+ target_selector: request.target_selector,
33162
+ operation_id: request.operation_id,
33163
+ step_id: request.step_id,
33164
+ direction: "forward",
33165
+ idempotency_key: request.idempotency_key,
33166
+ request_digest: request.request_digest,
33167
+ precondition_digest: request.precondition_digest,
33168
+ normalized_call_digest: callDigest,
33169
+ state: "pending",
33170
+ target_id: null,
33171
+ accepted_receipt_id: null,
33172
+ result_revision: null,
33173
+ result_digest: null,
33174
+ removed_receipt_id: null,
33175
+ created_at: timestamp3,
33176
+ updated_at: timestamp3
33177
+ };
33178
+ }
33179
+
33180
+ class PackageOwnedTodosProjectRegistrationAuthority {
33181
+ backend;
33182
+ authority = "todos";
33183
+ capabilityValue;
33184
+ now;
33185
+ faultInjector;
33186
+ constructor(backend, options = {}) {
33187
+ this.backend = backend;
33188
+ this.capabilityValue = {
33189
+ authority: "todos",
33190
+ route: TODOS_PROJECT_REGISTRATION_ROUTE,
33191
+ package_version: options.packageVersion ?? getPackageVersion(import.meta.url),
33192
+ authority_id: options.authorityId ?? "todos",
33193
+ tenant_id: options.tenantId ?? backend.kind,
33194
+ corpus_id: options.corpusId ?? `todos:${backend.kind}`,
33195
+ supported_resources: ["project", "task_list"],
33196
+ conditional_create: true,
33197
+ immutable_receipts: true,
33198
+ exact_terminal_lookup: true,
33199
+ exact_readback: true,
33200
+ conditional_inverse: true,
33201
+ ambiguous_outcome_reconciliation: true
33202
+ };
33203
+ this.now = options.now ?? (() => new Date().toISOString());
33204
+ this.faultInjector = options.faultInjector;
33205
+ }
33206
+ async capability() {
33207
+ return {
33208
+ ...this.capabilityValue,
33209
+ supported_resources: [...this.capabilityValue.supported_resources]
33210
+ };
33211
+ }
33212
+ async fault(point, request) {
33213
+ if (!this.faultInjector)
33214
+ return;
33215
+ try {
33216
+ await this.faultInjector(point, {
33217
+ operation_id: request.operation_id,
33218
+ step_id: request.step_id,
33219
+ resource_kind: request.resource_kind,
33220
+ direction: request.direction
33221
+ });
33222
+ } catch (cause) {
33223
+ throw new WriteBoundaryError(point, cause);
33224
+ }
33225
+ }
33226
+ async afterCommit(request) {
33227
+ await this.faultInjector?.("after_commit", {
33228
+ operation_id: request.operation_id,
33229
+ step_id: request.step_id,
33230
+ resource_kind: request.resource_kind,
33231
+ direction: request.direction
33232
+ });
33233
+ }
33234
+ async duplicateFor(transaction, request, callDigest, accepted) {
33235
+ const duplicate = makeDuplicateReceipt(request, callDigest, this.capabilityValue, accepted, this.now());
33236
+ return insertDeterministicReceipt(transaction, duplicate);
33237
+ }
33238
+ async terminalFor(transaction, request, callDigest, reason, options = {}) {
33239
+ return insertDeterministicReceipt(transaction, makeTerminalReceipt(request, callDigest, this.capabilityValue, reason, this.now(), options));
33240
+ }
33241
+ async existingForwardResolution(transaction, request, callDigest) {
33242
+ const exact = await transaction.getReceiptForLookup({
33243
+ ...authorityScope(this.capabilityValue),
33244
+ operation_id: request.operation_id,
33245
+ step_id: request.step_id,
33246
+ resource_kind: request.resource_kind,
33247
+ direction: request.direction,
33248
+ idempotency_key: request.idempotency_key,
33249
+ target_selector: request.target_selector
33250
+ });
33251
+ if (exact) {
33252
+ if (exact.outcome === "terminal_nonacceptance")
33253
+ return exact;
33254
+ const accepted2 = exact.outcome === "accepted" ? exact : await transaction.getReceiptById(exact.duplicate_of_receipt_id);
33255
+ if (!accepted2) {
33256
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "duplicate receipt points to a missing accepted receipt");
33257
+ }
33258
+ if (accepted2.normalized_call_digest !== callDigest) {
33259
+ return this.terminalFor(transaction, request, callDigest, "operation_step_semantics_changed", { targetId: accepted2.target_id });
33260
+ }
33261
+ return this.duplicateFor(transaction, request, callDigest, accepted2);
33262
+ }
33263
+ const accepted = await transaction.getAcceptedReceiptForStep({
33264
+ ...authorityScope(this.capabilityValue),
33265
+ operation_id: request.operation_id,
33266
+ step_id: request.step_id,
33267
+ resource_kind: request.resource_kind,
33268
+ direction: "forward"
33269
+ });
33270
+ if (!accepted)
33271
+ return null;
33272
+ if (accepted.normalized_call_digest === callDigest) {
33273
+ return this.duplicateFor(transaction, request, callDigest, accepted);
33274
+ }
33275
+ return this.terminalFor(transaction, request, callDigest, "operation_step_semantics_changed", { targetId: accepted.target_id });
33276
+ }
33277
+ async createObject(transaction, request) {
33278
+ if (request.resource_kind === "project") {
33279
+ const path = projectRegistrationPath(request.project_id);
33280
+ const slug2 = taskListSlug(request.project_slug);
33281
+ const conflict2 = await transaction.findProjectConflict(path, slug2);
33282
+ if (conflict2) {
33283
+ return this.terminalFor(transaction, request, normalizedCallDigest(request), "target_already_exists", { targetId: conflict2.id });
33284
+ }
33285
+ await this.fault("before_object_write", request);
33286
+ const project = await transaction.createProject({
33287
+ name: request.project_name,
33288
+ path,
33289
+ description: `Registered from Projects workspace ${request.project_id}`,
33290
+ task_list_id: slug2,
33291
+ task_prefix: deterministicTaskPrefix(request.project_slug)
33292
+ });
33293
+ await this.fault("after_object_write", request);
33294
+ return projectRecord(project);
33295
+ }
33296
+ const todosProjectId = String(request.desired["todos_project_id"]);
33297
+ const sourceBinding = await transaction.getBinding(authorityScope(this.capabilityValue), "project", request.project_id);
33298
+ if (!sourceBinding || sourceBinding.state !== "accepted" || sourceBinding.target_id !== todosProjectId) {
33299
+ return this.terminalFor(transaction, request, normalizedCallDigest(request), "exact_parent_registration_missing", { targetId: todosProjectId });
33300
+ }
33301
+ const parent = await transaction.getProject(todosProjectId);
33302
+ if (!parent) {
33303
+ return this.terminalFor(transaction, request, normalizedCallDigest(request), "exact_parent_project_missing", { targetId: todosProjectId });
33304
+ }
33305
+ const slug = taskListSlug(request.project_slug);
33306
+ const conflict = await transaction.findTaskListConflict(todosProjectId, slug);
33307
+ if (conflict) {
33308
+ return this.terminalFor(transaction, request, normalizedCallDigest(request), "target_already_exists", { targetId: conflict.id });
33309
+ }
33310
+ await this.fault("before_object_write", request);
33311
+ const taskList = await transaction.createTaskList({
33312
+ name: request.project_name,
33313
+ slug,
33314
+ project_id: todosProjectId,
33315
+ metadata: {
33316
+ source_project_id: request.project_id,
33317
+ registration_authority: "todos"
33318
+ }
33319
+ });
33320
+ await this.fault("after_object_write", request);
33321
+ if (taskList.project_id !== todosProjectId) {
33322
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "task-list create did not preserve the exact full Todos project id");
33323
+ }
33324
+ return taskListRecord(taskList);
33325
+ }
33326
+ async create(request) {
33327
+ const startedAt = Date.now();
33328
+ assertForwardRequest(request, this.capabilityValue);
33329
+ const callDigest = normalizedCallDigest(request);
33330
+ try {
33331
+ const row = await this.backend.transaction(async (transaction) => {
33332
+ await transaction.lockStep({
33333
+ ...authorityScope(this.capabilityValue),
33334
+ operation_id: request.operation_id,
33335
+ step_id: request.step_id,
33336
+ resource_kind: request.resource_kind,
33337
+ direction: request.direction
33338
+ });
33339
+ const resolved = await this.existingForwardResolution(transaction, request, callDigest);
33340
+ if (resolved)
33341
+ return resolved;
33342
+ const timestamp3 = this.now();
33343
+ const claimed = await transaction.claimBinding(bindingFor(request, callDigest, timestamp3, this.capabilityValue));
33344
+ if (!claimed) {
33345
+ const binding = await transaction.getBinding(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector);
33346
+ if (binding?.state === "accepted" && binding.normalized_call_digest === callDigest && binding.accepted_receipt_id) {
33347
+ const accepted2 = await transaction.getReceiptById(binding.accepted_receipt_id);
33348
+ if (accepted2) {
33349
+ return this.duplicateFor(transaction, request, callDigest, accepted2);
33350
+ }
33351
+ }
33352
+ return this.terminalFor(transaction, request, callDigest, binding?.state === "removed" ? "target_registration_was_removed" : "target_already_registered", { targetId: binding?.target_id ?? null });
33353
+ }
33354
+ const recordOrTerminal = await this.createObject(transaction, request);
33355
+ if ("outcome" in recordOrTerminal) {
33356
+ await transaction.setBindingTerminal(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector, this.now());
33357
+ return recordOrTerminal;
33358
+ }
33359
+ const accepted = makeAcceptedReceipt(request, callDigest, this.capabilityValue, recordOrTerminal, this.now());
33360
+ await this.fault("before_receipt_write", request);
33361
+ const stored = await insertDeterministicReceipt(transaction, accepted);
33362
+ await this.fault("after_receipt_write", request);
33363
+ await transaction.setBindingAccepted(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector, {
33364
+ target_id: recordOrTerminal.target_id,
33365
+ accepted_receipt_id: stored.receipt_id,
33366
+ result_revision: recordOrTerminal.revision,
33367
+ result_digest: recordOrTerminal.digest,
33368
+ updated_at: this.now()
33369
+ });
33370
+ return stored;
33371
+ });
33372
+ await this.afterCommit(request);
33373
+ const receipt = publicReceipt(row);
33374
+ assertWithinBounds(receipt, request, startedAt);
33375
+ return receipt;
33376
+ } catch (error) {
33377
+ if (!(error instanceof WriteBoundaryError))
33378
+ throw error;
33379
+ const terminal = await this.recordWriteFailure(request, callDigest, error.point);
33380
+ const receipt = publicReceipt(terminal);
33381
+ assertWithinBounds(receipt, request, startedAt);
33382
+ return receipt;
33383
+ }
33384
+ }
33385
+ async recordWriteFailure(request, callDigest, point) {
33386
+ return this.backend.transaction(async (transaction) => {
33387
+ await transaction.lockStep({
33388
+ ...authorityScope(this.capabilityValue),
33389
+ operation_id: request.operation_id,
33390
+ step_id: request.step_id,
33391
+ resource_kind: request.resource_kind,
33392
+ direction: request.direction
33393
+ });
33394
+ const exact = await transaction.getReceiptForLookup({
33395
+ ...authorityScope(this.capabilityValue),
33396
+ operation_id: request.operation_id,
33397
+ step_id: request.step_id,
33398
+ resource_kind: request.resource_kind,
33399
+ direction: request.direction,
33400
+ idempotency_key: request.idempotency_key,
33401
+ target_selector: request.target_selector
33402
+ });
33403
+ if (exact)
33404
+ return exact;
33405
+ const accepted = await transaction.getAcceptedReceiptForStep({
33406
+ ...authorityScope(this.capabilityValue),
33407
+ operation_id: request.operation_id,
33408
+ step_id: request.step_id,
33409
+ resource_kind: request.resource_kind,
33410
+ direction: request.direction
33411
+ });
33412
+ if (accepted) {
33413
+ 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 });
33414
+ }
33415
+ const timestamp3 = this.now();
33416
+ const claimed = await transaction.claimBinding(bindingFor(request, callDigest, timestamp3, this.capabilityValue));
33417
+ const terminal = await this.terminalFor(transaction, request, callDigest, `write_failed:${point}`);
33418
+ if (claimed) {
33419
+ await transaction.setBindingTerminal(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector, this.now());
33420
+ }
33421
+ return terminal;
33422
+ });
33423
+ }
33424
+ async readExact(request) {
33425
+ const startedAt = Date.now();
33426
+ assertBounds(request);
33427
+ assertResourceKind(request.resource_kind);
33428
+ if (!UUID_PATTERN.test(request.target_id)) {
33429
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED", "exact readback requires a complete Todos object UUID");
33430
+ }
33431
+ 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);
33432
+ if (!record) {
33433
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_RECORD_NOT_FOUND", `registered ${request.resource_kind} was not found by exact id`, { target_id: request.target_id });
33434
+ }
33435
+ assertWithinBounds(record, request, startedAt);
33436
+ return record;
33437
+ }
33438
+ async lookupReceipt(request) {
33439
+ const startedAt = Date.now();
33440
+ assertBounds(request);
33441
+ assertResourceKind(request.resource_kind);
33442
+ assertDirection(request.direction);
33443
+ if (request.max_items !== 1) {
33444
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "max_items must be exactly 1 for terminal receipt lookup");
33445
+ }
33446
+ 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) {
33447
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH", "receipt lookup does not match this authority capability identity");
33448
+ }
33449
+ requireString(request.operation_id, "operation_id", {
33450
+ min: 8,
33451
+ max: 128,
33452
+ pattern: OPERATION_PATTERN
33453
+ });
33454
+ requireString(request.step_id, "step_id", {
33455
+ min: 3,
33456
+ max: 128,
33457
+ pattern: STEP_PATTERN
33458
+ });
33459
+ requireString(request.target_selector, "target_selector", { max: 512 });
33460
+ requireString(request.idempotency_key, "idempotency_key", {
33461
+ min: 52,
33462
+ max: 52,
33463
+ pattern: IDEMPOTENCY_PATTERN
33464
+ });
33465
+ if (request.target_id !== undefined && !UUID_PATTERN.test(request.target_id)) {
33466
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED", "receipt lookup target_id must be a complete Todos object UUID");
33467
+ }
33468
+ const receipt = await this.backend.getReceiptForLookup({
33469
+ ...authorityScope(this.capabilityValue),
33470
+ operation_id: request.operation_id,
33471
+ step_id: request.step_id,
33472
+ resource_kind: request.resource_kind,
33473
+ direction: request.direction,
33474
+ idempotency_key: request.idempotency_key,
33475
+ target_selector: request.target_selector
33476
+ });
33477
+ if (!receipt || request.target_id && receipt.target_id !== request.target_id) {
33478
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND", "no exact terminal receipt matched the bounded lookup");
33479
+ }
33480
+ return withResponseControl({ receipt: publicReceipt(receipt) }, request, startedAt);
33481
+ }
33482
+ async storedAcceptedReceipt(request, supplied) {
33483
+ const stored = await this.backend.getReceiptById(supplied.receipt_id);
33484
+ if (!stored || stored.outcome !== "accepted" || !stored.created_by_operation || canonicalProjectRegistrationJson(publicReceipt(stored)) !== canonicalProjectRegistrationJson(supplied)) {
33485
+ 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 });
33486
+ }
33487
+ 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) {
33488
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND", "accepted receipt does not own this exact operation step and target");
33489
+ }
33490
+ return stored;
33491
+ }
33492
+ async compensate(request) {
33493
+ const startedAt = Date.now();
33494
+ const suppliedAccepted = assertInverseRequest(request, this.capabilityValue);
33495
+ const accepted = await this.storedAcceptedReceipt(request, suppliedAccepted);
33496
+ const callDigest = normalizedCallDigest(request);
33497
+ try {
33498
+ const row = await this.backend.transaction(async (transaction) => {
33499
+ await transaction.lockStep({
33500
+ ...authorityScope(this.capabilityValue),
33501
+ operation_id: request.operation_id,
33502
+ step_id: request.step_id,
33503
+ resource_kind: request.resource_kind,
33504
+ direction: request.direction
33505
+ });
33506
+ const exact = await transaction.getReceiptForLookup({
33507
+ ...authorityScope(this.capabilityValue),
33508
+ operation_id: request.operation_id,
33509
+ step_id: request.step_id,
33510
+ resource_kind: request.resource_kind,
33511
+ direction: "inverse",
33512
+ idempotency_key: request.idempotency_key,
33513
+ target_selector: request.target_selector
33514
+ });
33515
+ if (exact)
33516
+ return exact;
33517
+ const storedAccepted = await transaction.getReceiptById(accepted.receipt_id);
33518
+ if (!storedAccepted || storedAccepted.outcome !== "accepted" || !storedAccepted.created_by_operation) {
33519
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND", "accepted receipt disappeared before conditional inverse");
33520
+ }
33521
+ const binding = await transaction.getBinding(authorityScope(this.capabilityValue), accepted.resource_kind, accepted.target_selector);
33522
+ if (!binding || binding.state !== "accepted" || binding.accepted_receipt_id !== accepted.receipt_id || binding.target_id !== accepted.target_id) {
33523
+ return this.terminalFor(transaction, request, callDigest, "target_not_owned_by_receipt", {
33524
+ targetId: accepted.target_id,
33525
+ acceptedReceiptId: accepted.receipt_id
33526
+ });
33527
+ }
33528
+ await transaction.lockCompensationWrites();
33529
+ const object = request.resource_kind === "project" ? await transaction.getProject(accepted.target_id) : await transaction.getTaskList(accepted.target_id);
33530
+ if (!object) {
33531
+ return this.terminalFor(transaction, request, callDigest, "target_missing_before_inverse", {
33532
+ targetId: accepted.target_id,
33533
+ acceptedReceiptId: accepted.receipt_id
33534
+ });
33535
+ }
33536
+ const current = request.resource_kind === "project" ? projectRecord(object) : taskListRecord(object);
33537
+ if (current.revision !== accepted.result_revision || current.digest !== accepted.result_digest) {
33538
+ return this.terminalFor(transaction, request, callDigest, "target_drifted", {
33539
+ targetId: accepted.target_id,
33540
+ acceptedReceiptId: accepted.receipt_id
33541
+ });
33542
+ }
33543
+ if (await transaction.hasDependents(request.resource_kind, accepted.target_id)) {
33544
+ return this.terminalFor(transaction, request, callDigest, "target_has_dependents", {
33545
+ targetId: accepted.target_id,
33546
+ acceptedReceiptId: accepted.receipt_id
33547
+ });
33548
+ }
33549
+ await this.fault("before_object_write", request);
33550
+ const deleted = request.resource_kind === "project" ? await transaction.deleteProject(accepted.target_id) : await transaction.deleteTaskList(accepted.target_id);
33551
+ if (!deleted) {
33552
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "conditional inverse could not delete the exact accepted target");
33553
+ }
33554
+ await this.fault("after_object_write", request);
33555
+ const inverseRecord = {
33556
+ target_id: accepted.target_id,
33557
+ revision: "absent",
33558
+ digest: digestProjectRegistrationValue({
33559
+ target_id: accepted.target_id,
33560
+ accepted_receipt_id: accepted.receipt_id,
33561
+ absent: true
33562
+ })
33563
+ };
33564
+ const inverse = makeAcceptedReceipt(request, callDigest, this.capabilityValue, inverseRecord, this.now());
33565
+ await this.fault("before_receipt_write", request);
33566
+ const stored = await insertDeterministicReceipt(transaction, inverse);
33567
+ await this.fault("after_receipt_write", request);
33568
+ await transaction.setBindingRemoved(authorityScope(this.capabilityValue), accepted.resource_kind, accepted.target_selector, stored.receipt_id, this.now());
33569
+ return stored;
33570
+ });
33571
+ await this.afterCommit(request);
33572
+ const receipt = publicReceipt(row);
33573
+ assertWithinBounds(receipt, request, startedAt);
33574
+ return receipt;
33575
+ } catch (error) {
33576
+ if (!(error instanceof WriteBoundaryError))
33577
+ throw error;
33578
+ const terminal = await this.backend.transaction(async (transaction) => {
33579
+ await transaction.lockStep({
33580
+ ...authorityScope(this.capabilityValue),
33581
+ operation_id: request.operation_id,
33582
+ step_id: request.step_id,
33583
+ resource_kind: request.resource_kind,
33584
+ direction: request.direction
33585
+ });
33586
+ return this.terminalFor(transaction, request, callDigest, `write_failed:${error.point}`, {
33587
+ targetId: accepted.target_id,
33588
+ acceptedReceiptId: accepted.receipt_id
33589
+ });
33590
+ });
33591
+ const receipt = publicReceipt(terminal);
33592
+ assertWithinBounds(receipt, request, startedAt);
33593
+ return receipt;
33594
+ }
33595
+ }
33596
+ async verifyInverse(request) {
33597
+ const startedAt = Date.now();
33598
+ const accepted = assertInverseRequest(request, this.capabilityValue);
33599
+ await this.storedAcceptedReceipt(request, accepted);
33600
+ const receipt = await this.backend.getReceiptForLookup({
33601
+ ...authorityScope(this.capabilityValue),
33602
+ operation_id: request.operation_id,
33603
+ step_id: request.step_id,
33604
+ resource_kind: request.resource_kind,
33605
+ direction: "inverse",
33606
+ idempotency_key: request.idempotency_key,
33607
+ target_selector: request.target_selector
33608
+ });
33609
+ if (!receipt || receipt.outcome !== "accepted" || receipt.accepted_receipt_id !== accepted.receipt_id || receipt.result_revision !== "absent" || !receipt.result_digest) {
33610
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND", "accepted conditional inverse receipt was not found");
33611
+ }
33612
+ const object = request.resource_kind === "project" ? await this.backend.getProject(accepted.target_id) : await this.backend.getTaskList(accepted.target_id);
33613
+ if (object) {
33614
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "inverse verification found the accepted target still present");
33615
+ }
33616
+ const verification = {
33617
+ target_id: accepted.target_id,
33618
+ accepted_receipt_id: accepted.receipt_id,
33619
+ absent: true,
33620
+ digest: digestProjectRegistrationValue({
33621
+ target_id: accepted.target_id,
33622
+ accepted_receipt_id: accepted.receipt_id,
33623
+ absent: true
33624
+ })
33625
+ };
33626
+ assertWithinBounds(verification, request, startedAt);
33627
+ if (verification.digest !== receipt.result_digest) {
33628
+ throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "inverse verification digest does not match the immutable receipt");
33629
+ }
33630
+ return verification;
33631
+ }
33632
+ }
33633
+ function createLocalTodosProjectRegistrationAuthority(db, options = {}) {
33634
+ return new PackageOwnedTodosProjectRegistrationAuthority(new SqliteTodosProjectRegistrationBackend(db), options);
33635
+ }
33636
+ function createPostgresTodosProjectRegistrationAuthority(client, options = {}) {
33637
+ const {
33638
+ service,
33639
+ tableName,
33640
+ cursorTableName,
33641
+ ...authorityOptions
33642
+ } = options;
33643
+ return new PackageOwnedTodosProjectRegistrationAuthority(new PostgresTodosProjectRegistrationBackend(client, {
33644
+ service,
33645
+ tableName,
33646
+ cursorTableName
33647
+ }), authorityOptions);
33648
+ }
33649
+ // src/project-registration/http.ts
33650
+ var JSON_HEADERS = { "Content-Type": "application/json" };
33651
+ function json(body, status = 200) {
33652
+ return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS });
33653
+ }
33654
+ function errorStatus(error) {
33655
+ switch (error.code) {
33656
+ case "TODOS_PROJECT_REGISTRATION_INVALID_INPUT":
33657
+ case "TODOS_PROJECT_REGISTRATION_INVALID_BOUNDS":
33658
+ case "TODOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH":
33659
+ case "TODOS_PROJECT_REGISTRATION_DIGEST_MISMATCH":
33660
+ case "TODOS_PROJECT_REGISTRATION_IDEMPOTENCY_MISMATCH":
33661
+ case "TODOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED":
33662
+ return 400;
33663
+ case "TODOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND":
33664
+ case "TODOS_PROJECT_REGISTRATION_RECORD_NOT_FOUND":
33665
+ case "TODOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND":
33666
+ return 404;
33667
+ case "TODOS_PROJECT_REGISTRATION_RESPONSE_TOO_LARGE":
33668
+ return 413;
33669
+ case "TODOS_PROJECT_REGISTRATION_TIME_BUDGET_EXCEEDED":
33670
+ return 408;
33671
+ case "TODOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE":
33672
+ return 503;
33673
+ default:
33674
+ return 409;
33675
+ }
33676
+ }
33677
+ async function readJson(req) {
33678
+ try {
33679
+ const value = await req.json();
33680
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
33681
+ } catch {
33682
+ return null;
33683
+ }
33684
+ }
33685
+ async function handleTodosProjectRegistrationHttpRequest(req, url, authority, basePath = "/v1/project-registration") {
33686
+ const path = url.pathname;
33687
+ if (path !== basePath && !path.startsWith(`${basePath}/`))
33688
+ return null;
33689
+ const action = path.slice(basePath.length).split("/").filter(Boolean).join("/");
33690
+ const method = req.method.toUpperCase();
33691
+ try {
33692
+ if ((action === "" || action === "capability") && method === "GET") {
33693
+ return json({ capability: await authority.capability() });
33694
+ }
33695
+ if (method !== "POST")
33696
+ return json({ error: "method not allowed" }, 405);
33697
+ const body = await readJson(req);
33698
+ if (!body) {
33699
+ return json({
33700
+ error: "invalid JSON body",
33701
+ code: "TODOS_PROJECT_REGISTRATION_INVALID_INPUT"
33702
+ }, 400);
33703
+ }
33704
+ if (action === "create") {
33705
+ return json({
33706
+ receipt: await authority.create(body)
33707
+ }, 201);
33708
+ }
33709
+ if (action === "receipts/lookup") {
33710
+ return json(await authority.lookupReceipt(body));
33711
+ }
33712
+ if (action === "read-exact") {
33713
+ return json({
33714
+ record: await authority.readExact(body)
33715
+ });
33716
+ }
33717
+ if (action === "compensate") {
33718
+ return json({
33719
+ receipt: await authority.compensate(body)
33720
+ }, 201);
33721
+ }
33722
+ if (action === "verify-inverse") {
33723
+ return json({
33724
+ verification: await authority.verifyInverse(body)
33725
+ });
33726
+ }
33727
+ return json({
33728
+ error: "unknown Todos project-registration route",
33729
+ code: "TODOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND"
33730
+ }, 404);
33731
+ } catch (cause) {
33732
+ if (cause instanceof TodosProjectRegistrationError) {
33733
+ return json({
33734
+ error: cause.message,
33735
+ code: cause.code,
33736
+ details: cause.details,
33737
+ authoritative: true
33738
+ }, errorStatus(cause));
33739
+ }
33740
+ return json({
33741
+ error: cause instanceof Error ? cause.message : "internal registration error",
33742
+ code: "TODOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE"
33743
+ }, 500);
33744
+ }
33745
+ }
33746
+ function withoutTarget(value) {
33747
+ const { target: _target, ...serializable } = value;
33748
+ return serializable;
33749
+ }
33750
+
33751
+ class TodosProjectRegistrationHttpClient {
33752
+ authority = "todos";
33753
+ baseUrl;
33754
+ fetchImpl;
33755
+ headers;
33756
+ constructor(options) {
33757
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
33758
+ this.fetchImpl = options.fetch ?? globalThis.fetch;
33759
+ this.headers = {
33760
+ ...options.headers,
33761
+ ...options.apiKey ? { Authorization: `Bearer ${options.apiKey}` } : {},
33762
+ "Content-Type": "application/json"
33763
+ };
33764
+ }
33765
+ async request(action, init = {}) {
33766
+ const response = await this.fetchImpl(`${this.baseUrl}/v1/project-registration${action}`, {
33767
+ ...init,
33768
+ headers: { ...this.headers, ...init.headers ?? {} }
33769
+ });
33770
+ const body = await response.json();
33771
+ if (!response.ok) {
33772
+ 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"] : {});
33773
+ }
33774
+ return body;
33775
+ }
33776
+ async capability() {
33777
+ const body = await this.request("/capability");
33778
+ return body.capability;
33779
+ }
33780
+ async create(request) {
33781
+ const body = await this.request("/create", { method: "POST", body: JSON.stringify(withoutTarget(request)) });
33782
+ return body.receipt;
33783
+ }
33784
+ async readExact(request) {
33785
+ const body = await this.request("/read-exact", { method: "POST", body: JSON.stringify(withoutTarget(request)) });
33786
+ return body.record;
33787
+ }
33788
+ async lookupReceipt(request) {
33789
+ return this.request("/receipts/lookup", { method: "POST", body: JSON.stringify(request) });
33790
+ }
33791
+ async compensate(request) {
33792
+ const body = await this.request("/compensate", { method: "POST", body: JSON.stringify(withoutTarget(request)) });
33793
+ return body.receipt;
33794
+ }
33795
+ async verifyInverse(request) {
33796
+ const body = await this.request("/verify-inverse", {
33797
+ method: "POST",
33798
+ body: JSON.stringify(withoutTarget(request))
33799
+ });
33800
+ return body.verification;
33801
+ }
33802
+ }
33803
+ function createTodosProjectRegistrationHttpClient(options) {
33804
+ return new TodosProjectRegistrationHttpClient(options);
33805
+ }
31899
33806
  // src/lib/native-storage-status.ts
31900
33807
  function getNativeStorageStatus(env = process.env) {
31901
33808
  const issues = [];
@@ -32031,7 +33938,7 @@ init_task_lifecycle();
32031
33938
  init_task_crud();
32032
33939
  init_redaction();
32033
33940
  import { Database as Database3 } from "bun:sqlite";
32034
- import { createHash as createHash12 } from "crypto";
33941
+ import { createHash as createHash13 } from "crypto";
32035
33942
  import { existsSync as existsSync9, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
32036
33943
  import { basename as basename2, dirname as dirname6, join as join9, resolve as resolve10 } from "path";
32037
33944
 
@@ -32314,7 +34221,7 @@ function normalizePath3(input) {
32314
34221
  return resolve10(input);
32315
34222
  }
32316
34223
  function sourceStoreId(sourceDbPath) {
32317
- const digest = createHash12("sha256").update(sourceDbPath).digest("hex").slice(0, 16);
34224
+ const digest = createHash13("sha256").update(sourceDbPath).digest("hex").slice(0, 16);
32318
34225
  return `sqlite:${digest}`;
32319
34226
  }
32320
34227
  function inferSourceRepoPath(sourceDbPath) {
@@ -34192,7 +36099,7 @@ function addSourceOnce(projectId, type, name, uri, metadata, db) {
34192
36099
  function bootstrapProject(options = {}, db) {
34193
36100
  const d = db || getDatabase();
34194
36101
  const discovery = discoverProjectWorkspace(options.path);
34195
- const taskListSlug = options.taskListSlug || `todos-${slugify(options.name || discovery.projectName)}`;
36102
+ const taskListSlug2 = options.taskListSlug || `todos-${slugify(options.name || discovery.projectName)}`;
34196
36103
  if (options.dryRun) {
34197
36104
  return {
34198
36105
  dryRun: true,
@@ -34206,15 +36113,15 @@ function bootstrapProject(options = {}, db) {
34206
36113
  const beforeProject = getProjectByCanonicalPath(discovery.projectPath, d);
34207
36114
  let project = ensureProject(options.name || discovery.projectName, discovery.projectPath, d);
34208
36115
  const createdProject = !beforeProject;
34209
- if (project.task_list_id !== taskListSlug || options.name && project.name !== options.name) {
36116
+ if (project.task_list_id !== taskListSlug2 || options.name && project.name !== options.name) {
34210
36117
  project = renameProject(project.id, {
34211
36118
  name: options.name ?? project.name,
34212
- new_slug: taskListSlug
36119
+ new_slug: taskListSlug2
34213
36120
  }, d).project;
34214
36121
  }
34215
36122
  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);
36123
+ const beforeTaskList = d.query("SELECT id FROM task_lists WHERE project_id = ? AND slug = ?").get(project.id, taskListSlug2);
36124
+ let taskList = ensureTaskList(`${project.name} Tasks`, taskListSlug2, project.id, d);
34218
36125
  if (options.routeEnabled && taskList.metadata.route_enabled !== true) {
34219
36126
  taskList = updateTaskList(taskList.id, {
34220
36127
  metadata: {
@@ -34276,7 +36183,7 @@ init_comments();
34276
36183
 
34277
36184
  // src/db/api-keys.ts
34278
36185
  init_database();
34279
- import { createHash as createHash13, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual3 } from "crypto";
36186
+ import { createHash as createHash14, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual3 } from "crypto";
34280
36187
  function rowToRecord(row) {
34281
36188
  return {
34282
36189
  id: row.id,
@@ -34290,7 +36197,7 @@ function rowToRecord(row) {
34290
36197
  };
34291
36198
  }
34292
36199
  function hashApiKey(key) {
34293
- return createHash13("sha256").update(key).digest("hex");
36200
+ return createHash14("sha256").update(key).digest("hex");
34294
36201
  }
34295
36202
  function safeEqualHex(a, b) {
34296
36203
  if (a.length !== b.length)
@@ -37350,10 +39257,10 @@ function extractEmbeddedBridge(markdown) {
37350
39257
  const match = markdown.match(/<!--\s*hasna\.todos\.bridge\s*\n([\s\S]*?)\n\s*-->/);
37351
39258
  if (!match)
37352
39259
  return null;
37353
- const json = match[1].split(`
39260
+ const json2 = match[1].split(`
37354
39261
  `).map((line) => line.replace(/^ /, "")).join(`
37355
39262
  `);
37356
- return JSON.parse(json);
39263
+ return JSON.parse(json2);
37357
39264
  }
37358
39265
  function frontmatterValue(markdown, key) {
37359
39266
  const match = markdown.match(/^---\n([\s\S]*?)\n---/);
@@ -38737,7 +40644,7 @@ init_database();
38737
40644
  init_tasks();
38738
40645
  import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
38739
40646
  import { basename as basename5 } from "path";
38740
- import { createHash as createHash14 } from "crypto";
40647
+ import { createHash as createHash15 } from "crypto";
38741
40648
  init_secret_redaction();
38742
40649
  var INBOX_INTAKE_SCHEMA = "todos.inbox_intake.v1";
38743
40650
  var INTAKE_SOURCE_TYPES = [
@@ -38750,7 +40657,7 @@ var INTAKE_SOURCE_TYPES = [
38750
40657
  ];
38751
40658
  var INTAKE_TRIAGE_STATUSES = ["preview", "triaged", "duplicate", "created"];
38752
40659
  function fingerprint2(text) {
38753
- return createHash14("sha256").update(text).digest("hex").slice(0, 16);
40660
+ return createHash15("sha256").update(text).digest("hex").slice(0, 16);
38754
40661
  }
38755
40662
  function loadRawContent(input) {
38756
40663
  if (input.github_url) {
@@ -44210,7 +46117,7 @@ init_database();
44210
46117
  init_tasks();
44211
46118
  init_redaction();
44212
46119
  init_sync_utils();
44213
- import { createHash as createHash15 } from "crypto";
46120
+ import { createHash as createHash16 } from "crypto";
44214
46121
  import { existsSync as existsSync22, readFileSync as readFileSync19, statSync as statSync9 } from "fs";
44215
46122
  import { hostname as hostname3, platform, arch } from "os";
44216
46123
  import { dirname as dirname15, join as join18, resolve as resolve17 } from "path";
@@ -44232,7 +46139,7 @@ var CONFIG_FILES = [
44232
46139
  "dashboard/vite.config.ts"
44233
46140
  ];
44234
46141
  function sha2566(value) {
44235
- return createHash15("sha256").update(value).digest("hex");
46142
+ return createHash16("sha256").update(value).digest("hex");
44236
46143
  }
44237
46144
  function fileRecord(root, relativePath) {
44238
46145
  const path = join18(root, relativePath);
@@ -44484,7 +46391,7 @@ function compareEnvironmentSnapshotFiles(leftPath, rightPath) {
44484
46391
  init_database();
44485
46392
  init_projects();
44486
46393
  init_plans();
44487
- import { createHash as createHash16 } from "crypto";
46394
+ import { createHash as createHash17 } from "crypto";
44488
46395
  import { mkdirSync as mkdirSync18, writeFileSync as writeFileSync16 } from "fs";
44489
46396
  import { dirname as dirname16, join as join19 } from "path";
44490
46397
  var DECISION_RECORD_SCHEMA = "todos.decision_record.v1";
@@ -44540,7 +46447,7 @@ function rowToDecisionRecord(row) {
44540
46447
  }
44541
46448
  function stableSnapshotHash(payload) {
44542
46449
  const { captured_at: _capturedAt, ...rest } = payload;
44543
- return createHash16("sha256").update(JSON.stringify(rest)).digest("hex");
46450
+ return createHash17("sha256").update(JSON.stringify(rest)).digest("hex");
44544
46451
  }
44545
46452
  function createDecisionRecord(input, db) {
44546
46453
  const d = db || getDatabase();
@@ -49031,7 +50938,7 @@ function syncWithAgents(agents, taskListIdByAgent, projectId, direction = "both"
49031
50938
  init_tasks();
49032
50939
  init_task_files();
49033
50940
  import { existsSync as existsSync27, readFileSync as readFileSync23, statSync as statSync10 } from "fs";
49034
- import { createHash as createHash17 } from "crypto";
50941
+ import { createHash as createHash18 } from "crypto";
49035
50942
  import { relative as relative6, resolve as resolve18, join as join25 } from "path";
49036
50943
  var EXTRACT_TAGS = ["TODO", "FIXME", "HACK", "XXX", "BUG", "NOTE"];
49037
50944
  var DEFAULT_EXTENSIONS = new Set([
@@ -49096,7 +51003,7 @@ var SKIP_DIRS2 = new Set([
49096
51003
  ".parcel-cache"
49097
51004
  ]);
49098
51005
  function stableHash(value) {
49099
- return createHash17("sha256").update(value).digest("hex");
51006
+ return createHash18("sha256").update(value).digest("hex");
49100
51007
  }
49101
51008
  function normalizePathForMatch(value) {
49102
51009
  return value.replace(/\\/g, "/").replace(/^\.\//, "");
@@ -49994,7 +51901,7 @@ function renderWorkflowStatesMarkdown(states = listWorkflowStates()) {
49994
51901
  }
49995
51902
  // src/lib/agent-replay-simulator.ts
49996
51903
  init_redaction();
49997
- import { createHash as createHash18 } from "crypto";
51904
+ import { createHash as createHash19 } from "crypto";
49998
51905
  import { readFileSync as readFileSync24 } from "fs";
49999
51906
  function isObject(value) {
50000
51907
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
@@ -50016,7 +51923,7 @@ function stable2(value) {
50016
51923
  return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable2(value[key])]));
50017
51924
  }
50018
51925
  function fingerprint3(value) {
50019
- return createHash18("sha256").update(JSON.stringify(stable2(value))).digest("hex");
51926
+ return createHash19("sha256").update(JSON.stringify(stable2(value))).digest("hex");
50020
51927
  }
50021
51928
  function unpackFixture(input) {
50022
51929
  if (!isObject(input))
@@ -50255,7 +52162,7 @@ function renderAgentReplaySimulationMarkdown(simulation) {
50255
52162
  }
50256
52163
  // src/lib/local-extensions.ts
50257
52164
  init_config2();
50258
- import { createHash as createHash19, createVerify } from "crypto";
52165
+ import { createHash as createHash20, createVerify } from "crypto";
50259
52166
  import { existsSync as existsSync28, readdirSync as readdirSync6, readFileSync as readFileSync25, statSync as statSync11 } from "fs";
50260
52167
  import { basename as basename6, join as join26, resolve as resolve19 } from "path";
50261
52168
  init_redaction();
@@ -50343,7 +52250,7 @@ function parseJson2(path) {
50343
52250
  return JSON.parse(readFileSync25(path, "utf8"));
50344
52251
  }
50345
52252
  function sha2567(bytes) {
50346
- return `sha256:${createHash19("sha256").update(bytes).digest("hex")}`;
52253
+ return `sha256:${createHash20("sha256").update(bytes).digest("hex")}`;
50347
52254
  }
50348
52255
  function compareVersions(a, b) {
50349
52256
  const left = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -52976,6 +54883,7 @@ export {
52976
54883
  startTaskRun,
52977
54884
  startTask,
52978
54885
  startFocusSession,
54886
+ sqliteTodosProjectRegistrationSchemaSql,
52979
54887
  softDeleteArtifact,
52980
54888
  snoozeReminder,
52981
54889
  slugify,
@@ -53164,6 +55072,7 @@ export {
53164
55072
  previewInboxIntake,
53165
55073
  previewBuiltinTemplate,
53166
55074
  postgresTodosSyncSchemaSql,
55075
+ postgresTodosProjectRegistrationSchemaSql,
53167
55076
  pollLocalSnapshots,
53168
55077
  planRunArtifactsS3Sync,
53169
55078
  pauseFocusSession,
@@ -53354,6 +55263,7 @@ export {
53354
55263
  importActivityLog,
53355
55264
  hasSecretFindings,
53356
55265
  hasActiveApiKeys,
55266
+ handleTodosProjectRegistrationHttpRequest,
53357
55267
  getWorkspaceTrustStatus,
53358
55268
  getWorkflowPrompt,
53359
55269
  getWebhook,
@@ -53657,6 +55567,7 @@ export {
53657
55567
  discoverTaskRouteSources,
53658
55568
  discoverProjectWorkspace,
53659
55569
  discoverLocalExtensions,
55570
+ digestProjectRegistrationValue,
53660
55571
  deterministicPrGroupId,
53661
55572
  deterministicPrGroupAttemptId,
53662
55573
  detectSourceType,
@@ -53665,6 +55576,7 @@ export {
53665
55576
  detectInboxSourceType,
53666
55577
  detectCyclesFromEdges,
53667
55578
  describeTerminalNotificationRule,
55579
+ deriveTodosProjectRegistrationIdempotencyKey,
53668
55580
  deriveInboxTitle,
53669
55581
  deleteWebhook,
53670
55582
  deleteTemplate,
@@ -53699,6 +55611,7 @@ export {
53699
55611
  createTodosStorageAdapter,
53700
55612
  createTodosS3ArtifactStore,
53701
55613
  createTodosRegistry,
55614
+ createTodosProjectRegistrationHttpClient,
53702
55615
  createTemplate,
53703
55616
  createTaskList,
53704
55617
  createTaskBoard,
@@ -53719,6 +55632,7 @@ export {
53719
55632
  createProject,
53720
55633
  createPostgresTodosSyncStore,
53721
55634
  createPostgresTodosStorageAdapter,
55635
+ createPostgresTodosProjectRegistrationAuthority,
53722
55636
  createPlanWithSteps,
53723
55637
  createPlan,
53724
55638
  createOrg,
@@ -53726,6 +55640,7 @@ export {
53726
55640
  createMilestone,
53727
55641
  createMcpManifest,
53728
55642
  createLocalUsageLedger,
55643
+ createLocalTodosProjectRegistrationAuthority,
53729
55644
  createLocalSqliteTodosStorageAdapter,
53730
55645
  createLocalReport,
53731
55646
  createLocalPrGroupLedger,
@@ -53799,6 +55714,7 @@ export {
53799
55714
  categorizeMcpTool,
53800
55715
  captureKnowledgeSnapshot,
53801
55716
  captureEnvironmentSnapshot,
55717
+ canonicalProjectRegistrationJson,
53802
55718
  cancelDispatch,
53803
55719
  cancelAgentRunDispatch,
53804
55720
  cancelAgentRun,
@@ -53876,6 +55792,8 @@ export {
53876
55792
  VersionConflictError,
53877
55793
  VERIFICATION_EVIDENCE_SCHEMA,
53878
55794
  USER_SCAFFOLD_SCHEMA,
55795
+ TodosProjectRegistrationHttpClient,
55796
+ TodosProjectRegistrationError,
53879
55797
  TodosClient,
53880
55798
  TaskNotFoundError,
53881
55799
  TaskListNotFoundError,
@@ -53887,6 +55805,9 @@ export {
53887
55805
  TODOS_SDK_INTEGRATION_FIXTURE_SCHEMA_VERSION,
53888
55806
  TODOS_SDK_INTEGRATION_FIXTURE_GENERATED_AT,
53889
55807
  TODOS_REGISTRY,
55808
+ TODOS_PROJECT_REGISTRATION_SCHEMA_VERSION,
55809
+ TODOS_PROJECT_REGISTRATION_ROUTE,
55810
+ TODOS_PROJECT_REGISTRATION_CALLER_ROUTE,
53890
55811
  TODOS_PACKAGE_EXPORTS,
53891
55812
  TODOS_ONBOARDING_FIXTURE_SOURCE,
53892
55813
  TODOS_ONBOARDING_FIXTURE_LIBRARY_VERSION,
@@ -53925,6 +55846,7 @@ export {
53925
55846
  TASK_FINDING_SCHEMA_VERSION,
53926
55847
  TASK_FINDING_RESOLVE_MISSING_SCHEMA_VERSION,
53927
55848
  TASK_DEPENDENCY_EDGES_SCHEMA,
55849
+ SqliteTodosProjectRegistrationBackend,
53928
55850
  STORAGE_TABLES,
53929
55851
  SECRET_REDACTION_SCHEMA,
53930
55852
  SCHEMA_SEMVER,
@@ -53951,7 +55873,9 @@ export {
53951
55873
  PrGroupLedgerError,
53952
55874
  PrGroupLedger,
53953
55875
  PrGroupHttpClient,
55876
+ PostgresTodosProjectRegistrationBackend,
53954
55877
  PlanNotFoundError,
55878
+ PackageOwnedTodosProjectRegistrationAuthority,
53955
55879
  PROJECT_DEPENDENCY_GRAPH_SCHEMA,
53956
55880
  PREWRITE_SECRET_SCAN_SCHEMA,
53957
55881
  PLAN_STATUSES,