@hasna/mementos 0.14.74 → 0.14.78
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/bun.lock +93 -0
- package/dist/cli/commands/agent.d.ts.map +1 -1
- package/dist/cli/commands/info-context.d.ts.map +1 -1
- package/dist/cli/commands/info-stale.d.ts.map +1 -1
- package/dist/cli/commands/io-export.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-crud.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-list.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-recall.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-search.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-tail.d.ts.map +1 -1
- package/dist/cli/commands/project.d.ts.map +1 -1
- package/dist/cli/commands/system-watch.d.ts.map +1 -1
- package/dist/cli/helpers.d.ts +38 -0
- package/dist/cli/helpers.d.ts.map +1 -1
- package/dist/cli/index.js +2077 -299
- package/dist/db/__fixtures__/fail-closed-stub-server.d.ts.map +1 -1
- package/dist/db/agents.d.ts +6 -0
- package/dist/db/agents.d.ts.map +1 -1
- package/dist/db/audit.d.ts +14 -0
- package/dist/db/audit.d.ts.map +1 -1
- package/dist/db/memory-project-link.d.ts +13 -0
- package/dist/db/memory-project-link.d.ts.map +1 -0
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts +0 -6
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/projects.d.ts +22 -1
- package/dist/db/projects.d.ts.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2909 -173
- package/dist/lib/package-version.d.ts +3 -0
- package/dist/lib/package-version.d.ts.map +1 -0
- package/dist/mcp/index.js +524 -10
- package/dist/memory-project-link/index.d.ts +2 -0
- package/dist/memory-project-link/index.d.ts.map +1 -0
- package/dist/memory-project-link/schema.d.ts +5 -0
- package/dist/memory-project-link/schema.d.ts.map +1 -0
- package/dist/project-registration/authority.d.ts +39 -0
- package/dist/project-registration/authority.d.ts.map +1 -0
- package/dist/project-registration/http.d.ts +29 -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/project-references.d.ts +71 -0
- package/dist/project-registration/project-references.d.ts.map +1 -0
- package/dist/project-registration/schema.d.ts +5 -0
- package/dist/project-registration/schema.d.ts.map +1 -0
- package/dist/project-registration/types.d.ts +168 -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 +1496 -0
- package/dist/sdk/index.d.ts +127 -0
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +74 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2667 -149
- package/dist/server/routes/memories.d.ts +1 -0
- package/dist/server/routes/memories.d.ts.map +1 -1
- package/dist/server/routes/memory-project-link.d.ts +2 -0
- package/dist/server/routes/memory-project-link.d.ts.map +1 -0
- package/dist/server/routes/project-registration.d.ts +2 -0
- package/dist/server/routes/project-registration.d.ts.map +1 -0
- package/dist/types/index.d.ts +119 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +9 -3
- package/dashboard/bun.lock +0 -519
|
@@ -0,0 +1,1496 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
12
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
20
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
21
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
22
|
+
for (let key of __getOwnPropNames(mod))
|
|
23
|
+
if (!__hasOwnProp.call(to, key))
|
|
24
|
+
__defProp(to, key, {
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
26
|
+
enumerable: true
|
|
27
|
+
});
|
|
28
|
+
if (canCache)
|
|
29
|
+
cache.set(mod, to);
|
|
30
|
+
return to;
|
|
31
|
+
};
|
|
32
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
33
|
+
var __returnValue = (v) => v;
|
|
34
|
+
function __exportSetter(name, newValue) {
|
|
35
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
36
|
+
}
|
|
37
|
+
var __export = (target, all) => {
|
|
38
|
+
for (var name in all)
|
|
39
|
+
__defProp(target, name, {
|
|
40
|
+
get: all[name],
|
|
41
|
+
enumerable: true,
|
|
42
|
+
configurable: true,
|
|
43
|
+
set: __exportSetter.bind(all, name)
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
47
|
+
var __require = import.meta.require;
|
|
48
|
+
|
|
49
|
+
// src/project-registration/schema.ts
|
|
50
|
+
function sqliteMementosProjectRegistrationSchemaSql() {
|
|
51
|
+
return `
|
|
52
|
+
CREATE TABLE IF NOT EXISTS mementos_project_registration_receipts (
|
|
53
|
+
receipt_id TEXT PRIMARY KEY,
|
|
54
|
+
authority TEXT NOT NULL CHECK(authority = 'mementos'),
|
|
55
|
+
route TEXT NOT NULL,
|
|
56
|
+
package_version TEXT NOT NULL,
|
|
57
|
+
authority_id TEXT NOT NULL,
|
|
58
|
+
tenant_id TEXT NOT NULL,
|
|
59
|
+
corpus_id TEXT NOT NULL,
|
|
60
|
+
operation_id TEXT NOT NULL,
|
|
61
|
+
step_id TEXT NOT NULL,
|
|
62
|
+
resource_kind TEXT NOT NULL CHECK(resource_kind = 'project'),
|
|
63
|
+
direction TEXT NOT NULL CHECK(direction IN ('forward', 'inverse')),
|
|
64
|
+
target_selector TEXT NOT NULL,
|
|
65
|
+
idempotency_key TEXT NOT NULL,
|
|
66
|
+
request_digest TEXT NOT NULL,
|
|
67
|
+
precondition_digest TEXT NOT NULL,
|
|
68
|
+
normalized_call_digest TEXT NOT NULL,
|
|
69
|
+
outcome TEXT NOT NULL CHECK(outcome IN (
|
|
70
|
+
'accepted', 'duplicate_of_accepted', 'terminal_nonacceptance'
|
|
71
|
+
)),
|
|
72
|
+
reason TEXT,
|
|
73
|
+
target_id TEXT,
|
|
74
|
+
result_revision TEXT,
|
|
75
|
+
result_digest TEXT,
|
|
76
|
+
duplicate_of_receipt_id TEXT,
|
|
77
|
+
accepted_receipt_id TEXT,
|
|
78
|
+
created_by_operation INTEGER NOT NULL CHECK(created_by_operation IN (0, 1)),
|
|
79
|
+
created_at TEXT NOT NULL
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
CREATE INDEX IF NOT EXISTS idx_mementos_project_registration_receipts_lookup
|
|
83
|
+
ON mementos_project_registration_receipts (
|
|
84
|
+
authority_id, tenant_id, corpus_id, operation_id, step_id,
|
|
85
|
+
resource_kind, direction, idempotency_key, target_selector
|
|
86
|
+
);
|
|
87
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_mementos_project_registration_receipts_accepted_step
|
|
88
|
+
ON mementos_project_registration_receipts (
|
|
89
|
+
authority_id, tenant_id, corpus_id, operation_id, step_id,
|
|
90
|
+
resource_kind, direction
|
|
91
|
+
)
|
|
92
|
+
WHERE outcome = 'accepted';
|
|
93
|
+
|
|
94
|
+
CREATE TABLE IF NOT EXISTS mementos_project_registration_bindings (
|
|
95
|
+
authority_id TEXT NOT NULL,
|
|
96
|
+
tenant_id TEXT NOT NULL,
|
|
97
|
+
corpus_id TEXT NOT NULL,
|
|
98
|
+
resource_kind TEXT NOT NULL CHECK(resource_kind = 'project'),
|
|
99
|
+
target_selector TEXT NOT NULL,
|
|
100
|
+
operation_id TEXT NOT NULL,
|
|
101
|
+
step_id TEXT NOT NULL,
|
|
102
|
+
direction TEXT NOT NULL CHECK(direction = 'forward'),
|
|
103
|
+
idempotency_key TEXT NOT NULL,
|
|
104
|
+
request_digest TEXT NOT NULL,
|
|
105
|
+
precondition_digest TEXT NOT NULL,
|
|
106
|
+
normalized_call_digest TEXT NOT NULL,
|
|
107
|
+
state TEXT NOT NULL CHECK(state IN (
|
|
108
|
+
'pending', 'accepted', 'terminal_nonacceptance', 'removed'
|
|
109
|
+
)),
|
|
110
|
+
target_id TEXT,
|
|
111
|
+
accepted_receipt_id TEXT,
|
|
112
|
+
result_revision TEXT,
|
|
113
|
+
result_digest TEXT,
|
|
114
|
+
removed_receipt_id TEXT,
|
|
115
|
+
created_at TEXT NOT NULL,
|
|
116
|
+
updated_at TEXT NOT NULL,
|
|
117
|
+
PRIMARY KEY(authority_id, tenant_id, corpus_id, resource_kind, target_selector),
|
|
118
|
+
UNIQUE(accepted_receipt_id)
|
|
119
|
+
);
|
|
120
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_mementos_project_registration_binding_target
|
|
121
|
+
ON mementos_project_registration_bindings(
|
|
122
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_id
|
|
123
|
+
)
|
|
124
|
+
WHERE target_id IS NOT NULL;
|
|
125
|
+
|
|
126
|
+
CREATE TRIGGER IF NOT EXISTS mementos_project_registration_receipts_immutable_update
|
|
127
|
+
BEFORE UPDATE ON mementos_project_registration_receipts
|
|
128
|
+
BEGIN
|
|
129
|
+
SELECT RAISE(ABORT, 'mementos project registration receipts are immutable');
|
|
130
|
+
END;
|
|
131
|
+
|
|
132
|
+
CREATE TRIGGER IF NOT EXISTS mementos_project_registration_receipts_immutable_delete
|
|
133
|
+
BEFORE DELETE ON mementos_project_registration_receipts
|
|
134
|
+
BEGIN
|
|
135
|
+
SELECT RAISE(ABORT, 'mementos project registration receipts are immutable');
|
|
136
|
+
END;
|
|
137
|
+
`;
|
|
138
|
+
}
|
|
139
|
+
function postgresMementosProjectRegistrationSchemaSql() {
|
|
140
|
+
return `
|
|
141
|
+
CREATE TABLE IF NOT EXISTS mementos_project_registration_receipts (
|
|
142
|
+
receipt_id TEXT PRIMARY KEY,
|
|
143
|
+
authority TEXT NOT NULL CHECK(authority = 'mementos'),
|
|
144
|
+
route TEXT NOT NULL,
|
|
145
|
+
package_version TEXT NOT NULL,
|
|
146
|
+
authority_id TEXT NOT NULL,
|
|
147
|
+
tenant_id TEXT NOT NULL,
|
|
148
|
+
corpus_id TEXT NOT NULL,
|
|
149
|
+
operation_id TEXT NOT NULL,
|
|
150
|
+
step_id TEXT NOT NULL,
|
|
151
|
+
resource_kind TEXT NOT NULL CHECK(resource_kind = 'project'),
|
|
152
|
+
direction TEXT NOT NULL CHECK(direction IN ('forward', 'inverse')),
|
|
153
|
+
target_selector TEXT NOT NULL,
|
|
154
|
+
idempotency_key TEXT NOT NULL,
|
|
155
|
+
request_digest TEXT NOT NULL,
|
|
156
|
+
precondition_digest TEXT NOT NULL,
|
|
157
|
+
normalized_call_digest TEXT NOT NULL,
|
|
158
|
+
outcome TEXT NOT NULL CHECK(outcome IN (
|
|
159
|
+
'accepted', 'duplicate_of_accepted', 'terminal_nonacceptance'
|
|
160
|
+
)),
|
|
161
|
+
reason TEXT,
|
|
162
|
+
target_id TEXT,
|
|
163
|
+
result_revision TEXT,
|
|
164
|
+
result_digest TEXT,
|
|
165
|
+
duplicate_of_receipt_id TEXT,
|
|
166
|
+
accepted_receipt_id TEXT,
|
|
167
|
+
created_by_operation BOOLEAN NOT NULL,
|
|
168
|
+
created_at TIMESTAMPTZ NOT NULL
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
CREATE INDEX IF NOT EXISTS idx_mementos_project_registration_receipts_lookup
|
|
172
|
+
ON mementos_project_registration_receipts (
|
|
173
|
+
authority_id, tenant_id, corpus_id, operation_id, step_id,
|
|
174
|
+
resource_kind, direction, idempotency_key, target_selector
|
|
175
|
+
);
|
|
176
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_mementos_project_registration_receipts_accepted_step
|
|
177
|
+
ON mementos_project_registration_receipts (
|
|
178
|
+
authority_id, tenant_id, corpus_id, operation_id, step_id,
|
|
179
|
+
resource_kind, direction
|
|
180
|
+
)
|
|
181
|
+
WHERE outcome = 'accepted';
|
|
182
|
+
|
|
183
|
+
CREATE TABLE IF NOT EXISTS mementos_project_registration_bindings (
|
|
184
|
+
authority_id TEXT NOT NULL,
|
|
185
|
+
tenant_id TEXT NOT NULL,
|
|
186
|
+
corpus_id TEXT NOT NULL,
|
|
187
|
+
resource_kind TEXT NOT NULL CHECK(resource_kind = 'project'),
|
|
188
|
+
target_selector TEXT NOT NULL,
|
|
189
|
+
operation_id TEXT NOT NULL,
|
|
190
|
+
step_id TEXT NOT NULL,
|
|
191
|
+
direction TEXT NOT NULL CHECK(direction = 'forward'),
|
|
192
|
+
idempotency_key TEXT NOT NULL,
|
|
193
|
+
request_digest TEXT NOT NULL,
|
|
194
|
+
precondition_digest TEXT NOT NULL,
|
|
195
|
+
normalized_call_digest TEXT NOT NULL,
|
|
196
|
+
state TEXT NOT NULL CHECK(state IN (
|
|
197
|
+
'pending', 'accepted', 'terminal_nonacceptance', 'removed'
|
|
198
|
+
)),
|
|
199
|
+
target_id TEXT,
|
|
200
|
+
accepted_receipt_id TEXT UNIQUE,
|
|
201
|
+
result_revision TEXT,
|
|
202
|
+
result_digest TEXT,
|
|
203
|
+
removed_receipt_id TEXT,
|
|
204
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
205
|
+
updated_at TIMESTAMPTZ NOT NULL,
|
|
206
|
+
PRIMARY KEY(authority_id, tenant_id, corpus_id, resource_kind, target_selector)
|
|
207
|
+
);
|
|
208
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_mementos_project_registration_binding_target
|
|
209
|
+
ON mementos_project_registration_bindings(
|
|
210
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_id
|
|
211
|
+
)
|
|
212
|
+
WHERE target_id IS NOT NULL;
|
|
213
|
+
|
|
214
|
+
CREATE OR REPLACE FUNCTION mementos_project_registration_receipts_immutable()
|
|
215
|
+
RETURNS trigger
|
|
216
|
+
LANGUAGE plpgsql
|
|
217
|
+
AS $$
|
|
218
|
+
BEGIN
|
|
219
|
+
RAISE EXCEPTION 'mementos project registration receipts are immutable';
|
|
220
|
+
END;
|
|
221
|
+
$$;
|
|
222
|
+
DROP TRIGGER IF EXISTS mementos_project_registration_receipts_immutable
|
|
223
|
+
ON mementos_project_registration_receipts;
|
|
224
|
+
CREATE TRIGGER mementos_project_registration_receipts_immutable
|
|
225
|
+
BEFORE UPDATE OR DELETE ON mementos_project_registration_receipts
|
|
226
|
+
FOR EACH ROW EXECUTE FUNCTION mementos_project_registration_receipts_immutable();
|
|
227
|
+
`;
|
|
228
|
+
}
|
|
229
|
+
function sqliteMementosProjectGuardedUpdateSchemaSql() {
|
|
230
|
+
return `
|
|
231
|
+
CREATE TABLE IF NOT EXISTS mementos_project_update_receipts (
|
|
232
|
+
receipt_id TEXT PRIMARY KEY,
|
|
233
|
+
authority TEXT NOT NULL CHECK(authority = 'mementos'),
|
|
234
|
+
route TEXT NOT NULL CHECK(route = 'mementos.project-guarded-update.v1'),
|
|
235
|
+
package_version TEXT NOT NULL,
|
|
236
|
+
authority_id TEXT NOT NULL,
|
|
237
|
+
tenant_id TEXT NOT NULL,
|
|
238
|
+
corpus_id TEXT NOT NULL,
|
|
239
|
+
operation_id TEXT NOT NULL,
|
|
240
|
+
step_id TEXT NOT NULL,
|
|
241
|
+
direction TEXT NOT NULL CHECK(direction IN ('forward', 'rollback')),
|
|
242
|
+
idempotency_key TEXT NOT NULL,
|
|
243
|
+
request_digest TEXT NOT NULL,
|
|
244
|
+
outcome TEXT NOT NULL CHECK(outcome = 'accepted'),
|
|
245
|
+
target_id TEXT NOT NULL,
|
|
246
|
+
expected_revision TEXT NOT NULL,
|
|
247
|
+
result_revision TEXT NOT NULL,
|
|
248
|
+
result_digest TEXT NOT NULL,
|
|
249
|
+
accepted_receipt_id TEXT,
|
|
250
|
+
before_project_json TEXT NOT NULL,
|
|
251
|
+
after_project_json TEXT NOT NULL,
|
|
252
|
+
created_at TEXT NOT NULL,
|
|
253
|
+
UNIQUE(authority_id, tenant_id, corpus_id, direction, idempotency_key)
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
CREATE INDEX IF NOT EXISTS idx_mementos_project_update_receipts_target
|
|
257
|
+
ON mementos_project_update_receipts(
|
|
258
|
+
authority_id, tenant_id, corpus_id, target_id, created_at
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
CREATE TRIGGER IF NOT EXISTS mementos_project_update_receipts_immutable_update
|
|
262
|
+
BEFORE UPDATE ON mementos_project_update_receipts
|
|
263
|
+
BEGIN
|
|
264
|
+
SELECT RAISE(ABORT, 'mementos project update receipts are immutable');
|
|
265
|
+
END;
|
|
266
|
+
|
|
267
|
+
CREATE TRIGGER IF NOT EXISTS mementos_project_update_receipts_immutable_delete
|
|
268
|
+
BEFORE DELETE ON mementos_project_update_receipts
|
|
269
|
+
BEGIN
|
|
270
|
+
SELECT RAISE(ABORT, 'mementos project update receipts are immutable');
|
|
271
|
+
END;
|
|
272
|
+
`;
|
|
273
|
+
}
|
|
274
|
+
function postgresMementosProjectGuardedUpdateSchemaSql() {
|
|
275
|
+
return `
|
|
276
|
+
CREATE TABLE IF NOT EXISTS mementos_project_update_receipts (
|
|
277
|
+
receipt_id TEXT PRIMARY KEY,
|
|
278
|
+
authority TEXT NOT NULL CHECK(authority = 'mementos'),
|
|
279
|
+
route TEXT NOT NULL CHECK(route = 'mementos.project-guarded-update.v1'),
|
|
280
|
+
package_version TEXT NOT NULL,
|
|
281
|
+
authority_id TEXT NOT NULL,
|
|
282
|
+
tenant_id TEXT NOT NULL,
|
|
283
|
+
corpus_id TEXT NOT NULL,
|
|
284
|
+
operation_id TEXT NOT NULL,
|
|
285
|
+
step_id TEXT NOT NULL,
|
|
286
|
+
direction TEXT NOT NULL CHECK(direction IN ('forward', 'rollback')),
|
|
287
|
+
idempotency_key TEXT NOT NULL,
|
|
288
|
+
request_digest TEXT NOT NULL,
|
|
289
|
+
outcome TEXT NOT NULL CHECK(outcome = 'accepted'),
|
|
290
|
+
target_id TEXT NOT NULL,
|
|
291
|
+
expected_revision TEXT NOT NULL,
|
|
292
|
+
result_revision TEXT NOT NULL,
|
|
293
|
+
result_digest TEXT NOT NULL,
|
|
294
|
+
accepted_receipt_id TEXT,
|
|
295
|
+
before_project_json JSONB NOT NULL,
|
|
296
|
+
after_project_json JSONB NOT NULL,
|
|
297
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
298
|
+
UNIQUE(authority_id, tenant_id, corpus_id, direction, idempotency_key)
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
CREATE INDEX IF NOT EXISTS idx_mementos_project_update_receipts_target
|
|
302
|
+
ON mementos_project_update_receipts(
|
|
303
|
+
authority_id, tenant_id, corpus_id, target_id, created_at
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
CREATE OR REPLACE FUNCTION mementos_project_update_receipts_immutable()
|
|
307
|
+
RETURNS trigger
|
|
308
|
+
LANGUAGE plpgsql
|
|
309
|
+
AS $$
|
|
310
|
+
BEGIN
|
|
311
|
+
RAISE EXCEPTION 'mementos project update receipts are immutable';
|
|
312
|
+
END;
|
|
313
|
+
$$;
|
|
314
|
+
DROP TRIGGER IF EXISTS mementos_project_update_receipts_immutable
|
|
315
|
+
ON mementos_project_update_receipts;
|
|
316
|
+
CREATE TRIGGER mementos_project_update_receipts_immutable
|
|
317
|
+
BEFORE UPDATE OR DELETE ON mementos_project_update_receipts
|
|
318
|
+
FOR EACH ROW EXECUTE FUNCTION mementos_project_update_receipts_immutable();
|
|
319
|
+
`;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/project-registration/authority.ts
|
|
323
|
+
import { createHash } from "crypto";
|
|
324
|
+
import { resolve } from "path";
|
|
325
|
+
|
|
326
|
+
// src/lib/package-version.ts
|
|
327
|
+
import { readFileSync } from "fs";
|
|
328
|
+
import { dirname, join } from "path";
|
|
329
|
+
import { fileURLToPath } from "url";
|
|
330
|
+
function getMementosPackageVersion() {
|
|
331
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
332
|
+
for (const candidate of [
|
|
333
|
+
join(here, "..", "..", "package.json"),
|
|
334
|
+
join(here, "..", "package.json")
|
|
335
|
+
]) {
|
|
336
|
+
try {
|
|
337
|
+
const parsed = JSON.parse(readFileSync(candidate, "utf8"));
|
|
338
|
+
if (typeof parsed.version === "string" && parsed.version.trim())
|
|
339
|
+
return parsed.version;
|
|
340
|
+
} catch {}
|
|
341
|
+
}
|
|
342
|
+
return "0.0.0";
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// src/project-registration/project-references.ts
|
|
346
|
+
var MEMENTOS_PROJECT_REFERENCE_SURFACES = [
|
|
347
|
+
{ key: "memories", table: "memories", column: "project_id" },
|
|
348
|
+
{ key: "sessions", table: "sessions", column: "project_id" },
|
|
349
|
+
{ key: "entities", table: "entities", column: "project_id" },
|
|
350
|
+
{ key: "agents", table: "agents", column: "active_project_id" },
|
|
351
|
+
{ key: "tool_events", table: "tool_events", column: "project_id" },
|
|
352
|
+
{ key: "tasks", table: "tasks", column: "project_id" },
|
|
353
|
+
{
|
|
354
|
+
key: "memory_consolidation_runs",
|
|
355
|
+
table: "memory_consolidation_runs",
|
|
356
|
+
column: "project_id"
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
key: "memory_reflection_runs",
|
|
360
|
+
table: "memory_reflection_runs",
|
|
361
|
+
column: "project_id"
|
|
362
|
+
},
|
|
363
|
+
{ key: "memory_acl", table: "memory_acl", column: "project_id" },
|
|
364
|
+
{ key: "search_history", table: "search_history", column: "project_id" },
|
|
365
|
+
{
|
|
366
|
+
key: "session_memory_jobs",
|
|
367
|
+
table: "session_memory_jobs",
|
|
368
|
+
column: "project_id"
|
|
369
|
+
},
|
|
370
|
+
{ key: "synthesis_events", table: "synthesis_events", column: "project_id" },
|
|
371
|
+
{ key: "synthesis_runs", table: "synthesis_runs", column: "project_id" },
|
|
372
|
+
{ key: "webhook_hooks", table: "webhook_hooks", column: "project_id" }
|
|
373
|
+
];
|
|
374
|
+
function quotedIdentifier(value) {
|
|
375
|
+
if (!/^[a-z_][a-z0-9_]*$/.test(value)) {
|
|
376
|
+
throw new Error(`unsafe project-reference identifier: ${value}`);
|
|
377
|
+
}
|
|
378
|
+
return `"${value}"`;
|
|
379
|
+
}
|
|
380
|
+
function mementosProjectReferenceCounts(db, projectId) {
|
|
381
|
+
const projections = MEMENTOS_PROJECT_REFERENCE_SURFACES.map(({ key, table, column }) => `(SELECT COUNT(*) FROM ${quotedIdentifier(table)} ` + `WHERE ${quotedIdentifier(column)} = ?) AS ${quotedIdentifier(key)}`).join(`,
|
|
382
|
+
`);
|
|
383
|
+
const row = db.get(`SELECT
|
|
384
|
+
${projections}`, ...MEMENTOS_PROJECT_REFERENCE_SURFACES.map(() => projectId));
|
|
385
|
+
return Object.fromEntries(MEMENTOS_PROJECT_REFERENCE_SURFACES.map(({ key }) => [key, Number(row[key] ?? 0)]));
|
|
386
|
+
}
|
|
387
|
+
function hasMementosProjectReferences(counts) {
|
|
388
|
+
return MEMENTOS_PROJECT_REFERENCE_SURFACES.some(({ key }) => counts[key] > 0);
|
|
389
|
+
}
|
|
390
|
+
function deleteMementosProjectIfUnreferenced(db, projectId) {
|
|
391
|
+
const guards = MEMENTOS_PROJECT_REFERENCE_SURFACES.map(({ table, column }) => `NOT EXISTS (SELECT 1 FROM ${quotedIdentifier(table)} ` + `WHERE ${quotedIdentifier(column)} = ?)`).join(`
|
|
392
|
+
AND `);
|
|
393
|
+
const result = db.run(`DELETE FROM projects
|
|
394
|
+
WHERE id = ?
|
|
395
|
+
AND ${guards}`, projectId, ...MEMENTOS_PROJECT_REFERENCE_SURFACES.map(() => projectId));
|
|
396
|
+
return result.changes;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// src/project-registration/types.ts
|
|
400
|
+
var MEMENTOS_PROJECT_REGISTRATION_ROUTE = "mementos.project-registration.v1";
|
|
401
|
+
var MEMENTOS_PROJECT_REGISTRATION_CALLER_ROUTE = "projects.full-registration.v1";
|
|
402
|
+
var MEMENTOS_PROJECT_REGISTRATION_SCHEMA_VERSION = 1;
|
|
403
|
+
var MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE = "mementos.project-guarded-update.v1";
|
|
404
|
+
|
|
405
|
+
class MementosProjectRegistrationError extends Error {
|
|
406
|
+
code;
|
|
407
|
+
details;
|
|
408
|
+
constructor(code, message, details = {}) {
|
|
409
|
+
super(message);
|
|
410
|
+
this.code = code;
|
|
411
|
+
this.details = details;
|
|
412
|
+
this.name = "MementosProjectRegistrationError";
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// src/project-registration/authority.ts
|
|
417
|
+
var WORKSPACE_ID_PATTERN = /^wks_[A-Za-z0-9][A-Za-z0-9_-]{11,}$/;
|
|
418
|
+
var OPERATION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
|
|
419
|
+
var STEP_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$/;
|
|
420
|
+
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
421
|
+
var IDEMPOTENCY_PATTERN = /^prk_[0-9a-f]{48}$/;
|
|
422
|
+
var PROJECT_ID_PATTERN = /^mm_project_[0-9a-f]{40}$/;
|
|
423
|
+
|
|
424
|
+
class WriteBoundaryError extends Error {
|
|
425
|
+
point;
|
|
426
|
+
cause;
|
|
427
|
+
constructor(point, cause) {
|
|
428
|
+
super(`Mementos project registration failed at ${point}`);
|
|
429
|
+
this.point = point;
|
|
430
|
+
this.cause = cause;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function canonicalMementosProjectRegistrationJson(value) {
|
|
434
|
+
return JSON.stringify(canonicalize(value));
|
|
435
|
+
}
|
|
436
|
+
function canonicalize(value) {
|
|
437
|
+
if (Array.isArray(value))
|
|
438
|
+
return value.map(canonicalize);
|
|
439
|
+
if (!value || typeof value !== "object")
|
|
440
|
+
return value;
|
|
441
|
+
const out = {};
|
|
442
|
+
for (const key of Object.keys(value).sort()) {
|
|
443
|
+
const item = value[key];
|
|
444
|
+
if (item !== undefined)
|
|
445
|
+
out[key] = canonicalize(item);
|
|
446
|
+
}
|
|
447
|
+
return out;
|
|
448
|
+
}
|
|
449
|
+
function digestMementosProjectRegistrationValue(value) {
|
|
450
|
+
return createHash("sha256").update(canonicalMementosProjectRegistrationJson(value)).digest("hex");
|
|
451
|
+
}
|
|
452
|
+
function digestOwnedPath(path) {
|
|
453
|
+
return createHash("sha256").update(path).digest("hex");
|
|
454
|
+
}
|
|
455
|
+
function deriveMementosProjectRegistrationIdempotencyKey(input) {
|
|
456
|
+
return `prk_${digestMementosProjectRegistrationValue({
|
|
457
|
+
route: MEMENTOS_PROJECT_REGISTRATION_CALLER_ROUTE,
|
|
458
|
+
operation_id: input.operation_id,
|
|
459
|
+
step_id: input.step_id,
|
|
460
|
+
direction: input.direction,
|
|
461
|
+
target_selector: input.target_selector,
|
|
462
|
+
request_digest: input.request_digest,
|
|
463
|
+
precondition_digest: input.precondition_digest
|
|
464
|
+
}).slice(0, 48)}`;
|
|
465
|
+
}
|
|
466
|
+
function responseBytes(value) {
|
|
467
|
+
return Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
468
|
+
}
|
|
469
|
+
function assertBounds(bounds) {
|
|
470
|
+
if (!Number.isSafeInteger(bounds.response_byte_limit) || bounds.response_byte_limit <= 0) {
|
|
471
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "response_byte_limit must be a positive integer");
|
|
472
|
+
}
|
|
473
|
+
if (!Number.isSafeInteger(bounds.time_budget_ms) || bounds.time_budget_ms <= 0) {
|
|
474
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "time_budget_ms must be a positive integer");
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
function assertWithinBounds(value, bounds, startedAt) {
|
|
478
|
+
const bytes = responseBytes(value);
|
|
479
|
+
if (bytes > bounds.response_byte_limit) {
|
|
480
|
+
throw new MementosProjectRegistrationError("MEMENTOS_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 });
|
|
481
|
+
}
|
|
482
|
+
const elapsed = Date.now() - startedAt;
|
|
483
|
+
if (elapsed > bounds.time_budget_ms) {
|
|
484
|
+
throw new MementosProjectRegistrationError("MEMENTOS_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 });
|
|
485
|
+
}
|
|
486
|
+
return { response_bytes: bytes, elapsed_ms: elapsed };
|
|
487
|
+
}
|
|
488
|
+
function withResponseControl(receipt, bounds, startedAt) {
|
|
489
|
+
const result = {
|
|
490
|
+
receipt,
|
|
491
|
+
response_control: {
|
|
492
|
+
response_byte_limit: bounds.response_byte_limit,
|
|
493
|
+
time_budget_ms: bounds.time_budget_ms,
|
|
494
|
+
response_bytes: 0,
|
|
495
|
+
elapsed_ms: 0,
|
|
496
|
+
complete: true,
|
|
497
|
+
truncated: false
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
for (let attempt = 0;attempt < 8; attempt += 1) {
|
|
501
|
+
const measured2 = assertWithinBounds(result, bounds, startedAt);
|
|
502
|
+
const stable = result.response_control.response_bytes === measured2.response_bytes && result.response_control.elapsed_ms === measured2.elapsed_ms;
|
|
503
|
+
result.response_control.response_bytes = measured2.response_bytes;
|
|
504
|
+
result.response_control.elapsed_ms = measured2.elapsed_ms;
|
|
505
|
+
if (stable)
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
const measured = assertWithinBounds(result, bounds, startedAt);
|
|
509
|
+
result.response_control.response_bytes = measured.response_bytes;
|
|
510
|
+
result.response_control.elapsed_ms = measured.elapsed_ms;
|
|
511
|
+
return result;
|
|
512
|
+
}
|
|
513
|
+
function requireString(value, field, options = {}) {
|
|
514
|
+
const min = options.min ?? 1;
|
|
515
|
+
const max = options.max ?? 512;
|
|
516
|
+
if (typeof value !== "string" || value.length < min || value.length > max || /[\u0000-\u001f]/.test(value) || options.pattern && !options.pattern.test(value)) {
|
|
517
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field} is not a valid bounded registration identifier`);
|
|
518
|
+
}
|
|
519
|
+
return value;
|
|
520
|
+
}
|
|
521
|
+
function exactKeys(value, expected, field) {
|
|
522
|
+
const actual = Object.keys(value).sort();
|
|
523
|
+
const wanted = [...expected].sort();
|
|
524
|
+
if (actual.length !== wanted.length || actual.some((key, i) => key !== wanted[i])) {
|
|
525
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field} must contain exactly: ${wanted.join(", ")}`);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
function ownedPath(target) {
|
|
529
|
+
if (!target || typeof target.withOwnedPath !== "function") {
|
|
530
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "target must be a package-owned path handle");
|
|
531
|
+
}
|
|
532
|
+
const path = target.withOwnedPath((value) => value);
|
|
533
|
+
requireString(path, "target path", { max: 4096 });
|
|
534
|
+
if (path !== resolve(path)) {
|
|
535
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "target path must already be canonical and absolute");
|
|
536
|
+
}
|
|
537
|
+
return path;
|
|
538
|
+
}
|
|
539
|
+
function normalizedCallDigest(request) {
|
|
540
|
+
return digestMementosProjectRegistrationValue({
|
|
541
|
+
authority_route: request.authority_route,
|
|
542
|
+
package_version: request.package_version,
|
|
543
|
+
authority_id: request.authority_id,
|
|
544
|
+
tenant_id: request.tenant_id,
|
|
545
|
+
corpus_id: request.corpus_id,
|
|
546
|
+
operation_id: request.operation_id,
|
|
547
|
+
step_id: request.step_id,
|
|
548
|
+
resource_kind: request.resource_kind,
|
|
549
|
+
direction: request.direction,
|
|
550
|
+
target_selector: request.target_selector,
|
|
551
|
+
idempotency_key: request.idempotency_key,
|
|
552
|
+
request_digest: request.request_digest,
|
|
553
|
+
precondition_digest: request.precondition_digest,
|
|
554
|
+
project_id: request.project_id,
|
|
555
|
+
project_slug: request.project_slug,
|
|
556
|
+
project_name: request.project_name,
|
|
557
|
+
desired: request.desired,
|
|
558
|
+
accepted_receipt_id: request.accepted_receipt?.receipt_id ?? null
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
function publicReceipt(row) {
|
|
562
|
+
const { target_selector: _selector, normalized_call_digest: _digest, ...receipt } = row;
|
|
563
|
+
return receipt;
|
|
564
|
+
}
|
|
565
|
+
function timestampString(value) {
|
|
566
|
+
return value instanceof Date ? value.toISOString() : String(value);
|
|
567
|
+
}
|
|
568
|
+
function receiptFromRow(row) {
|
|
569
|
+
return {
|
|
570
|
+
...row,
|
|
571
|
+
authority: "mementos",
|
|
572
|
+
resource_kind: "project",
|
|
573
|
+
created_by_operation: row["created_by_operation"] === true || Number(row["created_by_operation"]) === 1,
|
|
574
|
+
created_at: timestampString(row["created_at"])
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
function bindingFromRow(row) {
|
|
578
|
+
return {
|
|
579
|
+
...row,
|
|
580
|
+
created_at: timestampString(row["created_at"]),
|
|
581
|
+
updated_at: timestampString(row["updated_at"])
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
function getStoredReceipt(db, receiptId) {
|
|
585
|
+
const row = db.get("SELECT * FROM mementos_project_registration_receipts WHERE receipt_id = ? LIMIT 1", receiptId);
|
|
586
|
+
return row ? receiptFromRow(row) : null;
|
|
587
|
+
}
|
|
588
|
+
function getAcceptedReceipt(db, request) {
|
|
589
|
+
const row = db.get(`
|
|
590
|
+
SELECT * FROM mementos_project_registration_receipts
|
|
591
|
+
WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
592
|
+
AND operation_id = ? AND step_id = ? AND resource_kind = 'project'
|
|
593
|
+
AND direction = ? AND outcome = 'accepted'
|
|
594
|
+
ORDER BY created_at ASC, receipt_id ASC
|
|
595
|
+
LIMIT 1
|
|
596
|
+
`, request.authority_id, request.tenant_id, request.corpus_id, request.operation_id, request.step_id, request.direction);
|
|
597
|
+
return row ? receiptFromRow(row) : null;
|
|
598
|
+
}
|
|
599
|
+
function getReceiptForLookup(db, request) {
|
|
600
|
+
const row = db.get(`
|
|
601
|
+
SELECT * FROM mementos_project_registration_receipts
|
|
602
|
+
WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
603
|
+
AND operation_id = ? AND step_id = ? AND resource_kind = 'project'
|
|
604
|
+
AND direction = ? AND idempotency_key = ? AND target_selector = ?
|
|
605
|
+
ORDER BY CASE outcome
|
|
606
|
+
WHEN 'terminal_nonacceptance' THEN 0
|
|
607
|
+
WHEN 'duplicate_of_accepted' THEN 1
|
|
608
|
+
ELSE 2
|
|
609
|
+
END, created_at DESC, receipt_id DESC
|
|
610
|
+
LIMIT 1
|
|
611
|
+
`, request.authority_id, request.tenant_id, request.corpus_id, request.operation_id, request.step_id, request.direction, request.idempotency_key, request.target_selector);
|
|
612
|
+
return row ? receiptFromRow(row) : null;
|
|
613
|
+
}
|
|
614
|
+
function getBinding(db, capability, targetSelector) {
|
|
615
|
+
const row = db.get(`
|
|
616
|
+
SELECT * FROM mementos_project_registration_bindings
|
|
617
|
+
WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
618
|
+
AND resource_kind = 'project' AND target_selector = ?
|
|
619
|
+
LIMIT 1
|
|
620
|
+
`, capability.authority_id, capability.tenant_id, capability.corpus_id, targetSelector);
|
|
621
|
+
return row ? bindingFromRow(row) : null;
|
|
622
|
+
}
|
|
623
|
+
function getProjectByExactId(db, id) {
|
|
624
|
+
const row = db.get("SELECT * FROM projects WHERE id = ? LIMIT 1", id);
|
|
625
|
+
if (!row)
|
|
626
|
+
return null;
|
|
627
|
+
return {
|
|
628
|
+
id: String(row["id"]),
|
|
629
|
+
name: String(row["name"]),
|
|
630
|
+
path: String(row["path"]),
|
|
631
|
+
description: row["description"] === null ? null : String(row["description"]),
|
|
632
|
+
memory_prefix: row["memory_prefix"] === null ? null : String(row["memory_prefix"]),
|
|
633
|
+
created_at: timestampString(row["created_at"]),
|
|
634
|
+
updated_at: timestampString(row["updated_at"])
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
function getProjectByPath(db, path) {
|
|
638
|
+
const row = db.get("SELECT * FROM projects WHERE path = ? LIMIT 1", path);
|
|
639
|
+
return row ? getProjectByExactId(db, String(row["id"])) : null;
|
|
640
|
+
}
|
|
641
|
+
function projectRecord(db, project, references = mementosProjectReferenceCounts(db, project.id)) {
|
|
642
|
+
return {
|
|
643
|
+
target_id: project.id,
|
|
644
|
+
revision: project.updated_at,
|
|
645
|
+
digest: digestMementosProjectRegistrationValue({
|
|
646
|
+
id: project.id,
|
|
647
|
+
name: project.name,
|
|
648
|
+
path: project.path,
|
|
649
|
+
description: project.description,
|
|
650
|
+
memory_prefix: project.memory_prefix,
|
|
651
|
+
created_at: project.created_at,
|
|
652
|
+
updated_at: project.updated_at,
|
|
653
|
+
references
|
|
654
|
+
})
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
function targetId(capability, selector) {
|
|
658
|
+
return `mm_project_${digestMementosProjectRegistrationValue({
|
|
659
|
+
authority_id: capability.authority_id,
|
|
660
|
+
tenant_id: capability.tenant_id,
|
|
661
|
+
corpus_id: capability.corpus_id,
|
|
662
|
+
selector
|
|
663
|
+
}).slice(0, 40)}`;
|
|
664
|
+
}
|
|
665
|
+
function receiptId(input) {
|
|
666
|
+
return `mmpr_${digestMementosProjectRegistrationValue(input).slice(0, 40)}`;
|
|
667
|
+
}
|
|
668
|
+
function makeReceipt(request, capability, callDigest, now, values) {
|
|
669
|
+
const withoutId = {
|
|
670
|
+
authority: "mementos",
|
|
671
|
+
route: MEMENTOS_PROJECT_REGISTRATION_ROUTE,
|
|
672
|
+
package_version: capability.package_version,
|
|
673
|
+
authority_id: capability.authority_id,
|
|
674
|
+
tenant_id: capability.tenant_id,
|
|
675
|
+
corpus_id: capability.corpus_id,
|
|
676
|
+
operation_id: request.operation_id,
|
|
677
|
+
step_id: request.step_id,
|
|
678
|
+
resource_kind: "project",
|
|
679
|
+
direction: request.direction,
|
|
680
|
+
target_selector: values.target_selector ?? request.target_selector,
|
|
681
|
+
idempotency_key: request.idempotency_key,
|
|
682
|
+
request_digest: request.request_digest,
|
|
683
|
+
precondition_digest: request.precondition_digest,
|
|
684
|
+
normalized_call_digest: callDigest,
|
|
685
|
+
outcome: values.outcome,
|
|
686
|
+
reason: values.reason ?? null,
|
|
687
|
+
target_id: values.target_id ?? null,
|
|
688
|
+
result_revision: values.result_revision ?? null,
|
|
689
|
+
result_digest: values.result_digest ?? null,
|
|
690
|
+
duplicate_of_receipt_id: values.duplicate_of_receipt_id ?? null,
|
|
691
|
+
accepted_receipt_id: values.accepted_receipt_id ?? null,
|
|
692
|
+
created_by_operation: values.created_by_operation ?? false
|
|
693
|
+
};
|
|
694
|
+
return {
|
|
695
|
+
receipt_id: receiptId(withoutId),
|
|
696
|
+
...withoutId,
|
|
697
|
+
created_at: now
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
function insertReceipt(db, receipt) {
|
|
701
|
+
db.run(`
|
|
702
|
+
INSERT INTO mementos_project_registration_receipts (
|
|
703
|
+
receipt_id, authority, route, package_version, authority_id, tenant_id,
|
|
704
|
+
corpus_id, operation_id, step_id, resource_kind, direction,
|
|
705
|
+
target_selector, idempotency_key, request_digest, precondition_digest,
|
|
706
|
+
normalized_call_digest, outcome, reason, target_id, result_revision,
|
|
707
|
+
result_digest, duplicate_of_receipt_id, accepted_receipt_id,
|
|
708
|
+
created_by_operation, created_at
|
|
709
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
710
|
+
ON CONFLICT DO NOTHING
|
|
711
|
+
`, 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, receipt.created_at);
|
|
712
|
+
const stored = getStoredReceipt(db, receipt.receipt_id);
|
|
713
|
+
const withoutCreatedAt = (value) => {
|
|
714
|
+
const { created_at: _createdAt, ...logical } = value;
|
|
715
|
+
return logical;
|
|
716
|
+
};
|
|
717
|
+
if (!stored || canonicalMementosProjectRegistrationJson(withoutCreatedAt(stored)) !== canonicalMementosProjectRegistrationJson(withoutCreatedAt(receipt))) {
|
|
718
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "immutable receipt id resolved to different content");
|
|
719
|
+
}
|
|
720
|
+
return stored;
|
|
721
|
+
}
|
|
722
|
+
function insertBinding(db, request, callDigest, now) {
|
|
723
|
+
const result = db.run(`
|
|
724
|
+
INSERT INTO mementos_project_registration_bindings (
|
|
725
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_selector,
|
|
726
|
+
operation_id, step_id, direction, idempotency_key, request_digest,
|
|
727
|
+
precondition_digest, normalized_call_digest, state, target_id,
|
|
728
|
+
accepted_receipt_id, result_revision, result_digest, removed_receipt_id,
|
|
729
|
+
created_at, updated_at
|
|
730
|
+
) VALUES (?, ?, ?, 'project', ?, ?, ?, 'forward', ?, ?, ?, ?, 'pending',
|
|
731
|
+
NULL, NULL, NULL, NULL, NULL, ?, ?)
|
|
732
|
+
ON CONFLICT DO NOTHING
|
|
733
|
+
`, request.authority_id, request.tenant_id, request.corpus_id, request.target_selector, request.operation_id, request.step_id, request.idempotency_key, request.request_digest, request.precondition_digest, callDigest, now, now);
|
|
734
|
+
return result.changes === 1;
|
|
735
|
+
}
|
|
736
|
+
function lockBinding(db, capability, targetSelector) {
|
|
737
|
+
const result = db.run(`
|
|
738
|
+
UPDATE mementos_project_registration_bindings
|
|
739
|
+
SET updated_at = updated_at
|
|
740
|
+
WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
741
|
+
AND resource_kind = 'project' AND target_selector = ?
|
|
742
|
+
`, capability.authority_id, capability.tenant_id, capability.corpus_id, targetSelector);
|
|
743
|
+
return result.changes === 1;
|
|
744
|
+
}
|
|
745
|
+
function setBindingAccepted(db, request, receipt, now) {
|
|
746
|
+
const result = db.run(`
|
|
747
|
+
UPDATE mementos_project_registration_bindings
|
|
748
|
+
SET state = 'accepted', target_id = ?, accepted_receipt_id = ?,
|
|
749
|
+
result_revision = ?, result_digest = ?, updated_at = ?
|
|
750
|
+
WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
751
|
+
AND resource_kind = 'project' AND target_selector = ? AND state = 'pending'
|
|
752
|
+
`, receipt.target_id, receipt.receipt_id, receipt.result_revision, receipt.result_digest, now, request.authority_id, request.tenant_id, request.corpus_id, request.target_selector);
|
|
753
|
+
if (result.changes !== 1) {
|
|
754
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "registration binding was not pending at acceptance");
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
function setBindingRemoved(db, accepted, inverseReceiptId, now) {
|
|
758
|
+
const result = db.run(`
|
|
759
|
+
UPDATE mementos_project_registration_bindings
|
|
760
|
+
SET state = 'removed', removed_receipt_id = ?, updated_at = ?
|
|
761
|
+
WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
762
|
+
AND resource_kind = 'project' AND target_selector = ?
|
|
763
|
+
AND state = 'accepted' AND accepted_receipt_id = ?
|
|
764
|
+
`, inverseReceiptId, now, accepted.authority_id, accepted.tenant_id, accepted.corpus_id, accepted.target_selector, accepted.receipt_id);
|
|
765
|
+
if (result.changes !== 1) {
|
|
766
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "registration binding was not accepted at inverse");
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
function capabilityMatches(request, capability) {
|
|
770
|
+
return request.authority_route === capability.route && request.package_version === capability.package_version && request.authority_id === capability.authority_id && request.tenant_id === capability.tenant_id && request.corpus_id === capability.corpus_id;
|
|
771
|
+
}
|
|
772
|
+
function assertCommonRequest(request, capability) {
|
|
773
|
+
assertBounds(request);
|
|
774
|
+
if (request.resource_kind !== "project") {
|
|
775
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "resource_kind must be project");
|
|
776
|
+
}
|
|
777
|
+
if (request.direction !== "forward" && request.direction !== "inverse") {
|
|
778
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "direction must be forward or inverse");
|
|
779
|
+
}
|
|
780
|
+
if (!capabilityMatches(request, capability)) {
|
|
781
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH", "registration request does not match this authority capability identity");
|
|
782
|
+
}
|
|
783
|
+
requireString(request.operation_id, "operation_id", {
|
|
784
|
+
min: 8,
|
|
785
|
+
max: 128,
|
|
786
|
+
pattern: OPERATION_PATTERN
|
|
787
|
+
});
|
|
788
|
+
requireString(request.step_id, "step_id", { min: 3, max: 128, pattern: STEP_PATTERN });
|
|
789
|
+
requireString(request.target_selector, "target_selector", { max: 512 });
|
|
790
|
+
requireString(request.project_id, "project_id", {
|
|
791
|
+
min: 16,
|
|
792
|
+
max: 128,
|
|
793
|
+
pattern: WORKSPACE_ID_PATTERN
|
|
794
|
+
});
|
|
795
|
+
requireString(request.project_name, "project_name", { max: 256 });
|
|
796
|
+
requireString(request.project_slug, "project_slug", { max: 128 });
|
|
797
|
+
requireString(request.request_digest, "request_digest", {
|
|
798
|
+
min: 64,
|
|
799
|
+
max: 64,
|
|
800
|
+
pattern: SHA256_PATTERN
|
|
801
|
+
});
|
|
802
|
+
requireString(request.precondition_digest, "precondition_digest", {
|
|
803
|
+
min: 64,
|
|
804
|
+
max: 64,
|
|
805
|
+
pattern: SHA256_PATTERN
|
|
806
|
+
});
|
|
807
|
+
requireString(request.idempotency_key, "idempotency_key", {
|
|
808
|
+
min: 52,
|
|
809
|
+
max: 52,
|
|
810
|
+
pattern: IDEMPOTENCY_PATTERN
|
|
811
|
+
});
|
|
812
|
+
if (!request.desired || typeof request.desired !== "object" || Array.isArray(request.desired)) {
|
|
813
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "desired must be a JSON object");
|
|
814
|
+
}
|
|
815
|
+
if (digestMementosProjectRegistrationValue(request.desired) !== request.request_digest) {
|
|
816
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "request_digest does not match the canonical desired payload");
|
|
817
|
+
}
|
|
818
|
+
const expectedKey = deriveMementosProjectRegistrationIdempotencyKey(request);
|
|
819
|
+
if (request.idempotency_key !== expectedKey) {
|
|
820
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_IDEMPOTENCY_MISMATCH", "idempotency_key does not match the deterministic request", { expected: expectedKey });
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
function assertForwardRequest(request, capability) {
|
|
824
|
+
assertCommonRequest(request, capability);
|
|
825
|
+
if (request.direction !== "forward" || request.accepted_receipt !== undefined) {
|
|
826
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "conditional create requires a forward request without an accepted receipt");
|
|
827
|
+
}
|
|
828
|
+
exactKeys(request.desired, [
|
|
829
|
+
"name",
|
|
830
|
+
"source_project_id",
|
|
831
|
+
"source_project_slug",
|
|
832
|
+
"target_path_digest"
|
|
833
|
+
], "forward desired");
|
|
834
|
+
if (request.target_selector !== request.project_id || request.desired["source_project_id"] !== request.project_id || request.desired["source_project_slug"] !== request.project_slug || request.desired["name"] !== request.project_name) {
|
|
835
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "forward project identity fields disagree");
|
|
836
|
+
}
|
|
837
|
+
const expectedPrecondition = digestMementosProjectRegistrationValue({
|
|
838
|
+
target_selector: request.target_selector,
|
|
839
|
+
expected: "absent"
|
|
840
|
+
});
|
|
841
|
+
if (request.precondition_digest !== expectedPrecondition) {
|
|
842
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "forward precondition must bind expected absence");
|
|
843
|
+
}
|
|
844
|
+
const path = ownedPath(request.target);
|
|
845
|
+
if (request.desired["target_path_digest"] !== digestOwnedPath(path)) {
|
|
846
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "target path does not match the package-owned digest");
|
|
847
|
+
}
|
|
848
|
+
return path;
|
|
849
|
+
}
|
|
850
|
+
function sameReceipt(left, right) {
|
|
851
|
+
return canonicalMementosProjectRegistrationJson(publicReceipt(left)) === canonicalMementosProjectRegistrationJson(right);
|
|
852
|
+
}
|
|
853
|
+
function assertInverseRequest(request, capability) {
|
|
854
|
+
assertCommonRequest(request, capability);
|
|
855
|
+
if (request.direction !== "inverse" || !request.accepted_receipt) {
|
|
856
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "conditional inverse requires the accepted forward receipt");
|
|
857
|
+
}
|
|
858
|
+
const accepted = request.accepted_receipt;
|
|
859
|
+
if (accepted.authority !== "mementos" || accepted.route !== capability.route || accepted.package_version !== capability.package_version || accepted.authority_id !== capability.authority_id || accepted.tenant_id !== capability.tenant_id || accepted.corpus_id !== capability.corpus_id || accepted.operation_id !== request.operation_id || accepted.step_id !== request.step_id || accepted.resource_kind !== "project" || accepted.direction !== "forward" || accepted.outcome !== "accepted" || !accepted.created_by_operation || !accepted.target_id || !accepted.result_revision || !accepted.result_digest || request.target_selector !== accepted.target_id) {
|
|
860
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "accepted receipt is not an exact attempt-created forward receipt");
|
|
861
|
+
}
|
|
862
|
+
exactKeys(request.desired, ["accepted_receipt_id", "target_id"], "inverse desired");
|
|
863
|
+
if (request.desired["accepted_receipt_id"] !== accepted.receipt_id || request.desired["target_id"] !== accepted.target_id) {
|
|
864
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "inverse desired payload does not bind the accepted receipt");
|
|
865
|
+
}
|
|
866
|
+
const expectedPrecondition = digestMementosProjectRegistrationValue({
|
|
867
|
+
expected_revision: accepted.result_revision,
|
|
868
|
+
expected_digest: accepted.result_digest
|
|
869
|
+
});
|
|
870
|
+
if (request.precondition_digest !== expectedPrecondition) {
|
|
871
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "inverse precondition does not bind the accepted object state");
|
|
872
|
+
}
|
|
873
|
+
return accepted;
|
|
874
|
+
}
|
|
875
|
+
function assertLookup(request, capability) {
|
|
876
|
+
assertBounds(request);
|
|
877
|
+
if (request.max_items !== 1) {
|
|
878
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "terminal receipt lookup requires max_items exactly 1");
|
|
879
|
+
}
|
|
880
|
+
if (request.authority !== "mementos" || request.resource_kind !== "project" || request.authority_route !== capability.route || request.package_version !== capability.package_version || request.authority_id !== capability.authority_id || request.tenant_id !== capability.tenant_id || request.corpus_id !== capability.corpus_id) {
|
|
881
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH", "receipt lookup does not match this authority capability identity");
|
|
882
|
+
}
|
|
883
|
+
requireString(request.operation_id, "operation_id", { pattern: OPERATION_PATTERN });
|
|
884
|
+
requireString(request.step_id, "step_id", { pattern: STEP_PATTERN });
|
|
885
|
+
requireString(request.target_selector, "target_selector");
|
|
886
|
+
requireString(request.idempotency_key, "idempotency_key", {
|
|
887
|
+
min: 52,
|
|
888
|
+
max: 52,
|
|
889
|
+
pattern: IDEMPOTENCY_PATTERN
|
|
890
|
+
});
|
|
891
|
+
if (request.target_id !== undefined) {
|
|
892
|
+
requireString(request.target_id, "target_id", {
|
|
893
|
+
min: 51,
|
|
894
|
+
max: 51,
|
|
895
|
+
pattern: PROJECT_ID_PATTERN
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
class PackageOwnedMementosProjectRegistrationAuthority {
|
|
901
|
+
db;
|
|
902
|
+
authority = "mementos";
|
|
903
|
+
capabilityValue;
|
|
904
|
+
now;
|
|
905
|
+
faultInjector;
|
|
906
|
+
constructor(db, options = {}) {
|
|
907
|
+
this.db = db;
|
|
908
|
+
this.now = options.now ?? (() => new Date().toISOString());
|
|
909
|
+
this.faultInjector = options.faultInjector;
|
|
910
|
+
this.capabilityValue = {
|
|
911
|
+
authority: "mementos",
|
|
912
|
+
route: MEMENTOS_PROJECT_REGISTRATION_ROUTE,
|
|
913
|
+
package_version: options.packageVersion ?? getMementosPackageVersion(),
|
|
914
|
+
authority_id: options.authorityId ?? "mementos",
|
|
915
|
+
tenant_id: options.tenantId ?? "default",
|
|
916
|
+
corpus_id: options.corpusId ?? "default",
|
|
917
|
+
supported_resources: ["project"],
|
|
918
|
+
conditional_create: true,
|
|
919
|
+
immutable_receipts: true,
|
|
920
|
+
exact_terminal_lookup: true,
|
|
921
|
+
exact_readback: true,
|
|
922
|
+
conditional_inverse: true,
|
|
923
|
+
ambiguous_outcome_reconciliation: true,
|
|
924
|
+
guarded_update: true,
|
|
925
|
+
no_write_dry_run: true,
|
|
926
|
+
expected_revision_compare_and_swap: true,
|
|
927
|
+
caller_idempotency: true,
|
|
928
|
+
exact_inverse_rollback: true
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
fault(point, request) {
|
|
932
|
+
this.faultInjector?.(point, {
|
|
933
|
+
operation_id: request.operation_id,
|
|
934
|
+
step_id: request.step_id,
|
|
935
|
+
resource_kind: "project",
|
|
936
|
+
direction: request.direction
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
writeBoundary(point, request) {
|
|
940
|
+
try {
|
|
941
|
+
this.fault(point, request);
|
|
942
|
+
} catch (cause) {
|
|
943
|
+
throw new WriteBoundaryError(point, cause);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
terminal(request, callDigest, reason, values = {}) {
|
|
947
|
+
return insertReceipt(this.db, makeReceipt(request, this.capabilityValue, callDigest, this.now(), {
|
|
948
|
+
outcome: "terminal_nonacceptance",
|
|
949
|
+
reason,
|
|
950
|
+
target_id: values.target_id,
|
|
951
|
+
accepted_receipt_id: values.accepted_receipt_id,
|
|
952
|
+
target_selector: values.target_selector
|
|
953
|
+
}));
|
|
954
|
+
}
|
|
955
|
+
duplicateForwardOrTerminal(request, callDigest, path, accepted) {
|
|
956
|
+
if (accepted.normalized_call_digest !== callDigest) {
|
|
957
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_IDEMPOTENCY_MISMATCH", "accepted operation/step identity is bound to a different request");
|
|
958
|
+
}
|
|
959
|
+
const project = accepted.target_id ? getProjectByExactId(this.db, accepted.target_id) : null;
|
|
960
|
+
if (!project || project.path !== path) {
|
|
961
|
+
return this.terminal(request, callDigest, "accepted_target_missing_or_replaced", {
|
|
962
|
+
target_id: accepted.target_id
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
const record = projectRecord(this.db, project);
|
|
966
|
+
if (record.revision !== accepted.result_revision || record.digest !== accepted.result_digest) {
|
|
967
|
+
return this.terminal(request, callDigest, "accepted_target_drifted", {
|
|
968
|
+
target_id: accepted.target_id
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
return insertReceipt(this.db, makeReceipt(request, this.capabilityValue, callDigest, this.now(), {
|
|
972
|
+
outcome: "duplicate_of_accepted",
|
|
973
|
+
target_id: accepted.target_id,
|
|
974
|
+
result_revision: accepted.result_revision,
|
|
975
|
+
result_digest: accepted.result_digest,
|
|
976
|
+
duplicate_of_receipt_id: accepted.receipt_id,
|
|
977
|
+
created_by_operation: false
|
|
978
|
+
}));
|
|
979
|
+
}
|
|
980
|
+
async capability() {
|
|
981
|
+
return {
|
|
982
|
+
...this.capabilityValue,
|
|
983
|
+
supported_resources: ["project"]
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
async create(request) {
|
|
987
|
+
const startedAt = Date.now();
|
|
988
|
+
const path = assertForwardRequest(request, this.capabilityValue);
|
|
989
|
+
const callDigest = normalizedCallDigest(request);
|
|
990
|
+
let stored;
|
|
991
|
+
try {
|
|
992
|
+
stored = this.db.transaction(() => {
|
|
993
|
+
const exact = getReceiptForLookup(this.db, {
|
|
994
|
+
...request,
|
|
995
|
+
authority: "mementos",
|
|
996
|
+
max_items: 1
|
|
997
|
+
});
|
|
998
|
+
if (exact?.outcome === "terminal_nonacceptance")
|
|
999
|
+
return exact;
|
|
1000
|
+
const accepted = getAcceptedReceipt(this.db, request);
|
|
1001
|
+
if (accepted) {
|
|
1002
|
+
return this.duplicateForwardOrTerminal(request, callDigest, path, accepted);
|
|
1003
|
+
}
|
|
1004
|
+
const existing = getProjectByPath(this.db, path) ?? getProjectByExactId(this.db, targetId(this.capabilityValue, request.target_selector));
|
|
1005
|
+
if (existing) {
|
|
1006
|
+
return this.terminal(request, callDigest, "target_preexists", {
|
|
1007
|
+
target_id: existing.id
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
const now = this.now();
|
|
1011
|
+
const insertedBinding = insertBinding(this.db, request, callDigest, now);
|
|
1012
|
+
if (!lockBinding(this.db, this.capabilityValue, request.target_selector)) {
|
|
1013
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "registration binding disappeared before it could be locked");
|
|
1014
|
+
}
|
|
1015
|
+
const ownedBinding = getBinding(this.db, this.capabilityValue, request.target_selector);
|
|
1016
|
+
if (!ownedBinding || ownedBinding.normalized_call_digest !== callDigest) {
|
|
1017
|
+
return this.terminal(request, callDigest, "target_selector_preexists", {
|
|
1018
|
+
target_id: ownedBinding?.target_id
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
const acceptedAfterBinding = getAcceptedReceipt(this.db, request);
|
|
1022
|
+
if (acceptedAfterBinding) {
|
|
1023
|
+
return this.duplicateForwardOrTerminal(request, callDigest, path, acceptedAfterBinding);
|
|
1024
|
+
}
|
|
1025
|
+
if (!insertedBinding || ownedBinding.state !== "pending") {
|
|
1026
|
+
return this.terminal(request, callDigest, "target_selector_preexists", {
|
|
1027
|
+
target_id: ownedBinding.target_id
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
const id = targetId(this.capabilityValue, request.target_selector);
|
|
1031
|
+
this.writeBoundary("before_object_write", request);
|
|
1032
|
+
try {
|
|
1033
|
+
this.db.run(`
|
|
1034
|
+
INSERT INTO projects (
|
|
1035
|
+
id, name, path, description, memory_prefix, created_at, updated_at
|
|
1036
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1037
|
+
`, id, request.project_name, path, null, request.project_slug, now, now);
|
|
1038
|
+
} catch (cause) {
|
|
1039
|
+
throw new WriteBoundaryError("before_object_write", cause);
|
|
1040
|
+
}
|
|
1041
|
+
this.writeBoundary("after_object_write", request);
|
|
1042
|
+
const project = getProjectByExactId(this.db, id);
|
|
1043
|
+
if (!project || project.path !== path) {
|
|
1044
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "project write did not read back by its full id");
|
|
1045
|
+
}
|
|
1046
|
+
const record = projectRecord(this.db, project);
|
|
1047
|
+
const acceptedReceipt = makeReceipt(request, this.capabilityValue, callDigest, now, {
|
|
1048
|
+
outcome: "accepted",
|
|
1049
|
+
target_id: record.target_id,
|
|
1050
|
+
result_revision: record.revision,
|
|
1051
|
+
result_digest: record.digest,
|
|
1052
|
+
created_by_operation: true
|
|
1053
|
+
});
|
|
1054
|
+
this.writeBoundary("before_receipt_write", request);
|
|
1055
|
+
let inserted;
|
|
1056
|
+
try {
|
|
1057
|
+
inserted = insertReceipt(this.db, acceptedReceipt);
|
|
1058
|
+
} catch (cause) {
|
|
1059
|
+
throw new WriteBoundaryError("before_receipt_write", cause);
|
|
1060
|
+
}
|
|
1061
|
+
this.writeBoundary("after_receipt_write", request);
|
|
1062
|
+
setBindingAccepted(this.db, request, inserted, now);
|
|
1063
|
+
return inserted;
|
|
1064
|
+
});
|
|
1065
|
+
} catch (error) {
|
|
1066
|
+
if (!(error instanceof WriteBoundaryError))
|
|
1067
|
+
throw error;
|
|
1068
|
+
stored = this.db.transaction(() => this.terminal(request, callDigest, `write_failed:${error.point}`));
|
|
1069
|
+
}
|
|
1070
|
+
this.fault("after_commit", request);
|
|
1071
|
+
const receipt = publicReceipt(stored);
|
|
1072
|
+
assertWithinBounds(receipt, request, startedAt);
|
|
1073
|
+
return receipt;
|
|
1074
|
+
}
|
|
1075
|
+
async readExact(request) {
|
|
1076
|
+
const startedAt = Date.now();
|
|
1077
|
+
assertBounds(request);
|
|
1078
|
+
if (request.resource_kind !== "project") {
|
|
1079
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "resource_kind must be project");
|
|
1080
|
+
}
|
|
1081
|
+
requireString(request.target_id, "target_id", {
|
|
1082
|
+
min: 51,
|
|
1083
|
+
max: 51,
|
|
1084
|
+
pattern: PROJECT_ID_PATTERN
|
|
1085
|
+
});
|
|
1086
|
+
const path = ownedPath(request.target);
|
|
1087
|
+
const project = getProjectByExactId(this.db, request.target_id);
|
|
1088
|
+
if (!project || project.path !== path) {
|
|
1089
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_RECORD_NOT_FOUND", "exact full-id project readback did not resolve the owned path");
|
|
1090
|
+
}
|
|
1091
|
+
const record = projectRecord(this.db, project);
|
|
1092
|
+
assertWithinBounds(record, request, startedAt);
|
|
1093
|
+
return record;
|
|
1094
|
+
}
|
|
1095
|
+
async lookupReceipt(request) {
|
|
1096
|
+
const startedAt = Date.now();
|
|
1097
|
+
assertLookup(request, this.capabilityValue);
|
|
1098
|
+
const receipt = getReceiptForLookup(this.db, request);
|
|
1099
|
+
if (!receipt || request.target_id !== undefined && receipt.target_id !== request.target_id) {
|
|
1100
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND", "exact immutable terminal receipt was not found");
|
|
1101
|
+
}
|
|
1102
|
+
return withResponseControl(publicReceipt(receipt), request, startedAt);
|
|
1103
|
+
}
|
|
1104
|
+
async compensate(request) {
|
|
1105
|
+
const startedAt = Date.now();
|
|
1106
|
+
const acceptedPublic = assertInverseRequest(request, this.capabilityValue);
|
|
1107
|
+
const callDigest = normalizedCallDigest(request);
|
|
1108
|
+
const storedAccepted = getStoredReceipt(this.db, acceptedPublic.receipt_id);
|
|
1109
|
+
if (!storedAccepted || !sameReceipt(storedAccepted, acceptedPublic)) {
|
|
1110
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND", "accepted forward receipt is not stored immutably in this authority");
|
|
1111
|
+
}
|
|
1112
|
+
const path = ownedPath(request.target);
|
|
1113
|
+
let stored;
|
|
1114
|
+
try {
|
|
1115
|
+
stored = this.db.transaction(() => {
|
|
1116
|
+
const exact = getReceiptForLookup(this.db, {
|
|
1117
|
+
...request,
|
|
1118
|
+
authority: "mementos",
|
|
1119
|
+
max_items: 1
|
|
1120
|
+
});
|
|
1121
|
+
if (exact?.outcome === "terminal_nonacceptance")
|
|
1122
|
+
return exact;
|
|
1123
|
+
if (!lockBinding(this.db, this.capabilityValue, storedAccepted.target_selector)) {
|
|
1124
|
+
return this.terminal(request, callDigest, "accepted_binding_missing", {
|
|
1125
|
+
target_id: storedAccepted.target_id,
|
|
1126
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
const binding = getBinding(this.db, this.capabilityValue, storedAccepted.target_selector);
|
|
1130
|
+
if (!binding || binding.accepted_receipt_id !== storedAccepted.receipt_id) {
|
|
1131
|
+
return this.terminal(request, callDigest, "accepted_binding_replaced", {
|
|
1132
|
+
target_id: storedAccepted.target_id,
|
|
1133
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
const prior = getAcceptedReceipt(this.db, request);
|
|
1137
|
+
if (prior) {
|
|
1138
|
+
if (prior.normalized_call_digest !== callDigest || prior.accepted_receipt_id !== storedAccepted.receipt_id) {
|
|
1139
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_IDEMPOTENCY_MISMATCH", "inverse operation/step identity is bound to a different request");
|
|
1140
|
+
}
|
|
1141
|
+
return insertReceipt(this.db, makeReceipt(request, this.capabilityValue, callDigest, this.now(), {
|
|
1142
|
+
outcome: "duplicate_of_accepted",
|
|
1143
|
+
target_id: prior.target_id,
|
|
1144
|
+
result_revision: prior.result_revision,
|
|
1145
|
+
result_digest: prior.result_digest,
|
|
1146
|
+
duplicate_of_receipt_id: prior.receipt_id,
|
|
1147
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
1148
|
+
}));
|
|
1149
|
+
}
|
|
1150
|
+
if (binding.state !== "accepted") {
|
|
1151
|
+
return this.terminal(request, callDigest, "accepted_binding_not_active", {
|
|
1152
|
+
target_id: storedAccepted.target_id,
|
|
1153
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
const project = getProjectByExactId(this.db, storedAccepted.target_id);
|
|
1157
|
+
if (!project) {
|
|
1158
|
+
return this.terminal(request, callDigest, "target_missing", {
|
|
1159
|
+
target_id: storedAccepted.target_id,
|
|
1160
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
if (project.path !== path) {
|
|
1164
|
+
return this.terminal(request, callDigest, "target_drifted", {
|
|
1165
|
+
target_id: storedAccepted.target_id,
|
|
1166
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
const references = mementosProjectReferenceCounts(this.db, project.id);
|
|
1170
|
+
if (hasMementosProjectReferences(references)) {
|
|
1171
|
+
return this.terminal(request, callDigest, "target_has_dependents", {
|
|
1172
|
+
target_id: storedAccepted.target_id,
|
|
1173
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
const current = projectRecord(this.db, project, references);
|
|
1177
|
+
if (current.revision !== storedAccepted.result_revision || current.digest !== storedAccepted.result_digest) {
|
|
1178
|
+
return this.terminal(request, callDigest, "target_drifted", {
|
|
1179
|
+
target_id: storedAccepted.target_id,
|
|
1180
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
this.writeBoundary("before_object_write", request);
|
|
1184
|
+
let deleted;
|
|
1185
|
+
try {
|
|
1186
|
+
deleted = deleteMementosProjectIfUnreferenced(this.db, storedAccepted.target_id);
|
|
1187
|
+
} catch (cause) {
|
|
1188
|
+
throw new WriteBoundaryError("before_object_write", cause);
|
|
1189
|
+
}
|
|
1190
|
+
if (deleted !== 1) {
|
|
1191
|
+
const remaining = getProjectByExactId(this.db, storedAccepted.target_id);
|
|
1192
|
+
if (remaining && hasMementosProjectReferences(mementosProjectReferenceCounts(this.db, storedAccepted.target_id))) {
|
|
1193
|
+
return this.terminal(request, callDigest, "target_has_dependents", {
|
|
1194
|
+
target_id: storedAccepted.target_id,
|
|
1195
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
throw new WriteBoundaryError("before_object_write", new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "conditional inverse did not delete exactly one unreferenced project"));
|
|
1199
|
+
}
|
|
1200
|
+
this.writeBoundary("after_object_write", request);
|
|
1201
|
+
const inverseRecord = {
|
|
1202
|
+
target_id: storedAccepted.target_id,
|
|
1203
|
+
revision: "absent",
|
|
1204
|
+
digest: digestMementosProjectRegistrationValue({
|
|
1205
|
+
target_id: storedAccepted.target_id,
|
|
1206
|
+
accepted_receipt_id: storedAccepted.receipt_id,
|
|
1207
|
+
absent: true
|
|
1208
|
+
})
|
|
1209
|
+
};
|
|
1210
|
+
const inverseReceipt = makeReceipt(request, this.capabilityValue, callDigest, this.now(), {
|
|
1211
|
+
outcome: "accepted",
|
|
1212
|
+
target_id: inverseRecord.target_id,
|
|
1213
|
+
result_revision: inverseRecord.revision,
|
|
1214
|
+
result_digest: inverseRecord.digest,
|
|
1215
|
+
accepted_receipt_id: storedAccepted.receipt_id,
|
|
1216
|
+
created_by_operation: false
|
|
1217
|
+
});
|
|
1218
|
+
this.writeBoundary("before_receipt_write", request);
|
|
1219
|
+
let inserted;
|
|
1220
|
+
try {
|
|
1221
|
+
inserted = insertReceipt(this.db, inverseReceipt);
|
|
1222
|
+
} catch (cause) {
|
|
1223
|
+
throw new WriteBoundaryError("before_receipt_write", cause);
|
|
1224
|
+
}
|
|
1225
|
+
this.writeBoundary("after_receipt_write", request);
|
|
1226
|
+
setBindingRemoved(this.db, storedAccepted, inserted.receipt_id, this.now());
|
|
1227
|
+
return inserted;
|
|
1228
|
+
});
|
|
1229
|
+
} catch (error) {
|
|
1230
|
+
if (!(error instanceof WriteBoundaryError))
|
|
1231
|
+
throw error;
|
|
1232
|
+
stored = this.db.transaction(() => this.terminal(request, callDigest, `write_failed:${error.point}`, {
|
|
1233
|
+
target_id: storedAccepted.target_id,
|
|
1234
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
1235
|
+
}));
|
|
1236
|
+
}
|
|
1237
|
+
this.fault("after_commit", request);
|
|
1238
|
+
const receipt = publicReceipt(stored);
|
|
1239
|
+
assertWithinBounds(receipt, request, startedAt);
|
|
1240
|
+
return receipt;
|
|
1241
|
+
}
|
|
1242
|
+
async verifyInverse(request) {
|
|
1243
|
+
const startedAt = Date.now();
|
|
1244
|
+
const accepted = assertInverseRequest(request, this.capabilityValue);
|
|
1245
|
+
const storedAccepted = getStoredReceipt(this.db, accepted.receipt_id);
|
|
1246
|
+
if (!storedAccepted || !sameReceipt(storedAccepted, accepted)) {
|
|
1247
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND", "accepted forward receipt is not stored immutably in this authority");
|
|
1248
|
+
}
|
|
1249
|
+
const inverse = getAcceptedReceipt(this.db, request);
|
|
1250
|
+
if (!inverse || inverse.outcome !== "accepted" || inverse.accepted_receipt_id !== accepted.receipt_id || inverse.result_revision !== "absent" || !inverse.result_digest) {
|
|
1251
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND", "accepted conditional inverse receipt was not found");
|
|
1252
|
+
}
|
|
1253
|
+
if (getProjectByExactId(this.db, accepted.target_id)) {
|
|
1254
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "inverse verification found the accepted target still present");
|
|
1255
|
+
}
|
|
1256
|
+
const verification = {
|
|
1257
|
+
target_id: accepted.target_id,
|
|
1258
|
+
accepted_receipt_id: accepted.receipt_id,
|
|
1259
|
+
absent: true,
|
|
1260
|
+
digest: digestMementosProjectRegistrationValue({
|
|
1261
|
+
target_id: accepted.target_id,
|
|
1262
|
+
accepted_receipt_id: accepted.receipt_id,
|
|
1263
|
+
absent: true
|
|
1264
|
+
})
|
|
1265
|
+
};
|
|
1266
|
+
assertWithinBounds(verification, request, startedAt);
|
|
1267
|
+
if (verification.digest !== inverse.result_digest) {
|
|
1268
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "inverse verification digest does not match its immutable receipt");
|
|
1269
|
+
}
|
|
1270
|
+
return verification;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
function createMementosProjectRegistrationAuthority(db, options = {}) {
|
|
1274
|
+
return new PackageOwnedMementosProjectRegistrationAuthority(db, options);
|
|
1275
|
+
}
|
|
1276
|
+
var createLocalMementosProjectRegistrationAuthority = createMementosProjectRegistrationAuthority;
|
|
1277
|
+
// src/project-registration/http.ts
|
|
1278
|
+
var JSON_HEADERS = { "Content-Type": "application/json" };
|
|
1279
|
+
|
|
1280
|
+
class WirePathHandle {
|
|
1281
|
+
absolutePath;
|
|
1282
|
+
constructor(absolutePath) {
|
|
1283
|
+
this.absolutePath = absolutePath;
|
|
1284
|
+
}
|
|
1285
|
+
withOwnedPath(consumer) {
|
|
1286
|
+
return consumer(this.absolutePath);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
function json(body, status = 200) {
|
|
1290
|
+
return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS });
|
|
1291
|
+
}
|
|
1292
|
+
function errorStatus(error) {
|
|
1293
|
+
switch (error.code) {
|
|
1294
|
+
case "MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT":
|
|
1295
|
+
case "MEMENTOS_PROJECT_REGISTRATION_INVALID_BOUNDS":
|
|
1296
|
+
case "MEMENTOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH":
|
|
1297
|
+
case "MEMENTOS_PROJECT_REGISTRATION_DIGEST_MISMATCH":
|
|
1298
|
+
case "MEMENTOS_PROJECT_REGISTRATION_IDEMPOTENCY_MISMATCH":
|
|
1299
|
+
case "MEMENTOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED":
|
|
1300
|
+
return 400;
|
|
1301
|
+
case "MEMENTOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND":
|
|
1302
|
+
case "MEMENTOS_PROJECT_REGISTRATION_RECORD_NOT_FOUND":
|
|
1303
|
+
case "MEMENTOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND":
|
|
1304
|
+
return 404;
|
|
1305
|
+
case "MEMENTOS_PROJECT_REGISTRATION_RESPONSE_TOO_LARGE":
|
|
1306
|
+
return 413;
|
|
1307
|
+
case "MEMENTOS_PROJECT_REGISTRATION_TIME_BUDGET_EXCEEDED":
|
|
1308
|
+
return 408;
|
|
1309
|
+
case "MEMENTOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE":
|
|
1310
|
+
return 503;
|
|
1311
|
+
default:
|
|
1312
|
+
return 409;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
async function readJson(request) {
|
|
1316
|
+
try {
|
|
1317
|
+
const value = await request.json();
|
|
1318
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1319
|
+
} catch {
|
|
1320
|
+
return null;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
function fromWireRequest(body) {
|
|
1324
|
+
const { canonical_path: canonicalPath, ...request } = body;
|
|
1325
|
+
if (typeof canonicalPath !== "string") {
|
|
1326
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "canonical_path is required on the private registration transport");
|
|
1327
|
+
}
|
|
1328
|
+
return {
|
|
1329
|
+
...request,
|
|
1330
|
+
target: new WirePathHandle(canonicalPath)
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
function fromWireReadRequest(body) {
|
|
1334
|
+
const { canonical_path: canonicalPath, ...request } = body;
|
|
1335
|
+
if (typeof canonicalPath !== "string") {
|
|
1336
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "canonical_path is required on the private exact-read transport");
|
|
1337
|
+
}
|
|
1338
|
+
return {
|
|
1339
|
+
...request,
|
|
1340
|
+
target: new WirePathHandle(canonicalPath)
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
async function handleMementosProjectRegistrationHttpRequest(request, url, authority, basePath = "/v1/project-registration") {
|
|
1344
|
+
const path = url.pathname;
|
|
1345
|
+
if (path !== basePath && !path.startsWith(`${basePath}/`))
|
|
1346
|
+
return null;
|
|
1347
|
+
const action = path.slice(basePath.length).split("/").filter(Boolean).join("/");
|
|
1348
|
+
const method = request.method.toUpperCase();
|
|
1349
|
+
try {
|
|
1350
|
+
if ((action === "" || action === "capability") && method === "GET") {
|
|
1351
|
+
return json({ capability: await authority.capability() });
|
|
1352
|
+
}
|
|
1353
|
+
if (method !== "POST")
|
|
1354
|
+
return json({ error: "method not allowed" }, 405);
|
|
1355
|
+
const body = await readJson(request);
|
|
1356
|
+
if (!body) {
|
|
1357
|
+
return json({
|
|
1358
|
+
error: "invalid JSON body",
|
|
1359
|
+
code: "MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT"
|
|
1360
|
+
}, 400);
|
|
1361
|
+
}
|
|
1362
|
+
if (action === "create") {
|
|
1363
|
+
return json({ receipt: await authority.create(fromWireRequest(body)) }, 201);
|
|
1364
|
+
}
|
|
1365
|
+
if (action === "receipts/lookup") {
|
|
1366
|
+
return json(await authority.lookupReceipt(body));
|
|
1367
|
+
}
|
|
1368
|
+
if (action === "read-exact") {
|
|
1369
|
+
return json({ record: await authority.readExact(fromWireReadRequest(body)) });
|
|
1370
|
+
}
|
|
1371
|
+
if (action === "compensate") {
|
|
1372
|
+
return json({ receipt: await authority.compensate(fromWireRequest(body)) }, 201);
|
|
1373
|
+
}
|
|
1374
|
+
if (action === "verify-inverse") {
|
|
1375
|
+
return json({ verification: await authority.verifyInverse(fromWireRequest(body)) });
|
|
1376
|
+
}
|
|
1377
|
+
return json({
|
|
1378
|
+
error: "unknown Mementos project-registration route",
|
|
1379
|
+
code: "MEMENTOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND"
|
|
1380
|
+
}, 404);
|
|
1381
|
+
} catch (cause) {
|
|
1382
|
+
if (cause instanceof MementosProjectRegistrationError) {
|
|
1383
|
+
return json({
|
|
1384
|
+
error: cause.message,
|
|
1385
|
+
code: cause.code,
|
|
1386
|
+
details: cause.details,
|
|
1387
|
+
authoritative: true
|
|
1388
|
+
}, errorStatus(cause));
|
|
1389
|
+
}
|
|
1390
|
+
return json({
|
|
1391
|
+
error: cause instanceof Error ? cause.message : "internal registration error",
|
|
1392
|
+
code: "MEMENTOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE"
|
|
1393
|
+
}, 500);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
function extractPath(target) {
|
|
1397
|
+
if (!target || typeof target.withOwnedPath !== "function") {
|
|
1398
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "target must be a package-owned path handle");
|
|
1399
|
+
}
|
|
1400
|
+
return target.withOwnedPath((absolutePath) => absolutePath);
|
|
1401
|
+
}
|
|
1402
|
+
function toWireRequest(request) {
|
|
1403
|
+
const { target, ...serializable } = request;
|
|
1404
|
+
return {
|
|
1405
|
+
...serializable,
|
|
1406
|
+
canonical_path: extractPath(target)
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
class MementosProjectRegistrationHttpClient {
|
|
1411
|
+
authority = "mementos";
|
|
1412
|
+
baseUrl;
|
|
1413
|
+
fetchImpl;
|
|
1414
|
+
headers;
|
|
1415
|
+
constructor(options) {
|
|
1416
|
+
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
1417
|
+
this.fetchImpl = options.fetch ?? globalThis.fetch;
|
|
1418
|
+
this.headers = {
|
|
1419
|
+
...options.headers,
|
|
1420
|
+
...options.apiKey ? { Authorization: `Bearer ${options.apiKey}` } : {},
|
|
1421
|
+
"Content-Type": "application/json"
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
async request(action, init = {}) {
|
|
1425
|
+
const response = await this.fetchImpl(`${this.baseUrl}/v1/project-registration${action}`, {
|
|
1426
|
+
...init,
|
|
1427
|
+
headers: { ...this.headers, ...init.headers ?? {} }
|
|
1428
|
+
});
|
|
1429
|
+
let body;
|
|
1430
|
+
try {
|
|
1431
|
+
body = await response.json();
|
|
1432
|
+
} catch {
|
|
1433
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE", `Mementos project registration HTTP ${response.status} returned non-JSON`);
|
|
1434
|
+
}
|
|
1435
|
+
if (!response.ok) {
|
|
1436
|
+
throw new MementosProjectRegistrationError(typeof body["code"] === "string" ? body["code"] : "MEMENTOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE", typeof body["error"] === "string" ? body["error"] : `Mementos project registration HTTP ${response.status}`, body["details"] && typeof body["details"] === "object" ? body["details"] : {});
|
|
1437
|
+
}
|
|
1438
|
+
return body;
|
|
1439
|
+
}
|
|
1440
|
+
async capability() {
|
|
1441
|
+
const body = await this.request("/capability");
|
|
1442
|
+
return body.capability;
|
|
1443
|
+
}
|
|
1444
|
+
async create(request) {
|
|
1445
|
+
const body = await this.request("/create", { method: "POST", body: JSON.stringify(toWireRequest(request)) });
|
|
1446
|
+
return body.receipt;
|
|
1447
|
+
}
|
|
1448
|
+
async readExact(request) {
|
|
1449
|
+
const { target, ...serializable } = request;
|
|
1450
|
+
const body = await this.request("/read-exact", {
|
|
1451
|
+
method: "POST",
|
|
1452
|
+
body: JSON.stringify({ ...serializable, canonical_path: extractPath(target) })
|
|
1453
|
+
});
|
|
1454
|
+
return body.record;
|
|
1455
|
+
}
|
|
1456
|
+
async lookupReceipt(request) {
|
|
1457
|
+
return this.request("/receipts/lookup", { method: "POST", body: JSON.stringify(request) });
|
|
1458
|
+
}
|
|
1459
|
+
async compensate(request) {
|
|
1460
|
+
const body = await this.request("/compensate", { method: "POST", body: JSON.stringify(toWireRequest(request)) });
|
|
1461
|
+
return body.receipt;
|
|
1462
|
+
}
|
|
1463
|
+
async verifyInverse(request) {
|
|
1464
|
+
const body = await this.request("/verify-inverse", {
|
|
1465
|
+
method: "POST",
|
|
1466
|
+
body: JSON.stringify(toWireRequest(request))
|
|
1467
|
+
});
|
|
1468
|
+
return body.verification;
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
function createMementosProjectRegistrationHttpClient(options) {
|
|
1472
|
+
return new MementosProjectRegistrationHttpClient(options);
|
|
1473
|
+
}
|
|
1474
|
+
export {
|
|
1475
|
+
sqliteMementosProjectRegistrationSchemaSql,
|
|
1476
|
+
sqliteMementosProjectGuardedUpdateSchemaSql,
|
|
1477
|
+
postgresMementosProjectRegistrationSchemaSql,
|
|
1478
|
+
postgresMementosProjectGuardedUpdateSchemaSql,
|
|
1479
|
+
mementosProjectReferenceCounts,
|
|
1480
|
+
hasMementosProjectReferences,
|
|
1481
|
+
handleMementosProjectRegistrationHttpRequest,
|
|
1482
|
+
digestMementosProjectRegistrationValue,
|
|
1483
|
+
deriveMementosProjectRegistrationIdempotencyKey,
|
|
1484
|
+
createMementosProjectRegistrationHttpClient,
|
|
1485
|
+
createMementosProjectRegistrationAuthority,
|
|
1486
|
+
createLocalMementosProjectRegistrationAuthority,
|
|
1487
|
+
canonicalMementosProjectRegistrationJson,
|
|
1488
|
+
PackageOwnedMementosProjectRegistrationAuthority,
|
|
1489
|
+
MementosProjectRegistrationHttpClient,
|
|
1490
|
+
MementosProjectRegistrationError,
|
|
1491
|
+
MEMENTOS_PROJECT_REGISTRATION_SCHEMA_VERSION,
|
|
1492
|
+
MEMENTOS_PROJECT_REGISTRATION_ROUTE,
|
|
1493
|
+
MEMENTOS_PROJECT_REGISTRATION_CALLER_ROUTE,
|
|
1494
|
+
MEMENTOS_PROJECT_REFERENCE_SURFACES,
|
|
1495
|
+
MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE
|
|
1496
|
+
};
|