@hasna/todos 0.15.6 → 0.15.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/cloud-router.d.ts.map +1 -1
- package/dist/cli/index.js +3011 -1325
- package/dist/contracts.js +213 -3
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1950 -26
- package/dist/mcp/index.js +2073 -371
- package/dist/mcp.js +7 -3
- package/dist/project-registration/authority.d.ts +45 -0
- package/dist/project-registration/authority.d.ts.map +1 -0
- package/dist/project-registration/backend.d.ts +83 -0
- package/dist/project-registration/backend.d.ts.map +1 -0
- package/dist/project-registration/http.d.ts +24 -0
- package/dist/project-registration/http.d.ts.map +1 -0
- package/dist/project-registration/index.d.ts +8 -0
- package/dist/project-registration/index.d.ts.map +1 -0
- package/dist/project-registration/postgres.d.ts +30 -0
- package/dist/project-registration/postgres.d.ts.map +1 -0
- package/dist/project-registration/schema.d.ts +3 -0
- package/dist/project-registration/schema.d.ts.map +1 -0
- package/dist/project-registration/sqlite.d.ts +17 -0
- package/dist/project-registration/sqlite.d.ts.map +1 -0
- package/dist/project-registration/types.d.ts +155 -0
- package/dist/project-registration/types.d.ts.map +1 -0
- package/dist/project-registration.d.ts +2 -0
- package/dist/project-registration.d.ts.map +1 -0
- package/dist/project-registration.js +17689 -0
- package/dist/registry.d.ts +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +220 -3
- package/dist/release-provenance.json +5 -5
- package/dist/server/cloud.d.ts +3 -0
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +4374 -2672
- package/dist/server/v1.d.ts +2 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage.js +206 -0
- package/package.json +7 -3
package/dist/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.
|
|
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
|
|
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 =
|
|
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
|
|
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 !==
|
|
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:
|
|
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,
|
|
34217
|
-
let taskList = ensureTaskList(`${project.name} Tasks`,
|
|
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
|
|
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
|
|
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
|
|
39260
|
+
const json2 = match[1].split(`
|
|
37354
39261
|
`).map((line) => line.replace(/^ /, "")).join(`
|
|
37355
39262
|
`);
|
|
37356
|
-
return JSON.parse(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:${
|
|
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,
|