@hasna/mementos 0.14.75 → 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/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 +2009 -334
- package/dist/db/__fixtures__/fail-closed-stub-server.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 +2828 -163
- package/dist/lib/package-version.d.ts +3 -0
- package/dist/lib/package-version.d.ts.map +1 -0
- package/dist/mcp/index.js +414 -0
- 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 +2573 -138
- 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
package/dist/index.js
CHANGED
|
@@ -1178,6 +1178,429 @@ var init_api_mode = __esm(() => {
|
|
|
1178
1178
|
};
|
|
1179
1179
|
});
|
|
1180
1180
|
|
|
1181
|
+
// src/project-registration/schema.ts
|
|
1182
|
+
function sqliteMementosProjectRegistrationSchemaSql() {
|
|
1183
|
+
return `
|
|
1184
|
+
CREATE TABLE IF NOT EXISTS mementos_project_registration_receipts (
|
|
1185
|
+
receipt_id TEXT PRIMARY KEY,
|
|
1186
|
+
authority TEXT NOT NULL CHECK(authority = 'mementos'),
|
|
1187
|
+
route TEXT NOT NULL,
|
|
1188
|
+
package_version TEXT NOT NULL,
|
|
1189
|
+
authority_id TEXT NOT NULL,
|
|
1190
|
+
tenant_id TEXT NOT NULL,
|
|
1191
|
+
corpus_id TEXT NOT NULL,
|
|
1192
|
+
operation_id TEXT NOT NULL,
|
|
1193
|
+
step_id TEXT NOT NULL,
|
|
1194
|
+
resource_kind TEXT NOT NULL CHECK(resource_kind = 'project'),
|
|
1195
|
+
direction TEXT NOT NULL CHECK(direction IN ('forward', 'inverse')),
|
|
1196
|
+
target_selector TEXT NOT NULL,
|
|
1197
|
+
idempotency_key TEXT NOT NULL,
|
|
1198
|
+
request_digest TEXT NOT NULL,
|
|
1199
|
+
precondition_digest TEXT NOT NULL,
|
|
1200
|
+
normalized_call_digest TEXT NOT NULL,
|
|
1201
|
+
outcome TEXT NOT NULL CHECK(outcome IN (
|
|
1202
|
+
'accepted', 'duplicate_of_accepted', 'terminal_nonacceptance'
|
|
1203
|
+
)),
|
|
1204
|
+
reason TEXT,
|
|
1205
|
+
target_id TEXT,
|
|
1206
|
+
result_revision TEXT,
|
|
1207
|
+
result_digest TEXT,
|
|
1208
|
+
duplicate_of_receipt_id TEXT,
|
|
1209
|
+
accepted_receipt_id TEXT,
|
|
1210
|
+
created_by_operation INTEGER NOT NULL CHECK(created_by_operation IN (0, 1)),
|
|
1211
|
+
created_at TEXT NOT NULL
|
|
1212
|
+
);
|
|
1213
|
+
|
|
1214
|
+
CREATE INDEX IF NOT EXISTS idx_mementos_project_registration_receipts_lookup
|
|
1215
|
+
ON mementos_project_registration_receipts (
|
|
1216
|
+
authority_id, tenant_id, corpus_id, operation_id, step_id,
|
|
1217
|
+
resource_kind, direction, idempotency_key, target_selector
|
|
1218
|
+
);
|
|
1219
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_mementos_project_registration_receipts_accepted_step
|
|
1220
|
+
ON mementos_project_registration_receipts (
|
|
1221
|
+
authority_id, tenant_id, corpus_id, operation_id, step_id,
|
|
1222
|
+
resource_kind, direction
|
|
1223
|
+
)
|
|
1224
|
+
WHERE outcome = 'accepted';
|
|
1225
|
+
|
|
1226
|
+
CREATE TABLE IF NOT EXISTS mementos_project_registration_bindings (
|
|
1227
|
+
authority_id TEXT NOT NULL,
|
|
1228
|
+
tenant_id TEXT NOT NULL,
|
|
1229
|
+
corpus_id TEXT NOT NULL,
|
|
1230
|
+
resource_kind TEXT NOT NULL CHECK(resource_kind = 'project'),
|
|
1231
|
+
target_selector TEXT NOT NULL,
|
|
1232
|
+
operation_id TEXT NOT NULL,
|
|
1233
|
+
step_id TEXT NOT NULL,
|
|
1234
|
+
direction TEXT NOT NULL CHECK(direction = 'forward'),
|
|
1235
|
+
idempotency_key TEXT NOT NULL,
|
|
1236
|
+
request_digest TEXT NOT NULL,
|
|
1237
|
+
precondition_digest TEXT NOT NULL,
|
|
1238
|
+
normalized_call_digest TEXT NOT NULL,
|
|
1239
|
+
state TEXT NOT NULL CHECK(state IN (
|
|
1240
|
+
'pending', 'accepted', 'terminal_nonacceptance', 'removed'
|
|
1241
|
+
)),
|
|
1242
|
+
target_id TEXT,
|
|
1243
|
+
accepted_receipt_id TEXT,
|
|
1244
|
+
result_revision TEXT,
|
|
1245
|
+
result_digest TEXT,
|
|
1246
|
+
removed_receipt_id TEXT,
|
|
1247
|
+
created_at TEXT NOT NULL,
|
|
1248
|
+
updated_at TEXT NOT NULL,
|
|
1249
|
+
PRIMARY KEY(authority_id, tenant_id, corpus_id, resource_kind, target_selector),
|
|
1250
|
+
UNIQUE(accepted_receipt_id)
|
|
1251
|
+
);
|
|
1252
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_mementos_project_registration_binding_target
|
|
1253
|
+
ON mementos_project_registration_bindings(
|
|
1254
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_id
|
|
1255
|
+
)
|
|
1256
|
+
WHERE target_id IS NOT NULL;
|
|
1257
|
+
|
|
1258
|
+
CREATE TRIGGER IF NOT EXISTS mementos_project_registration_receipts_immutable_update
|
|
1259
|
+
BEFORE UPDATE ON mementos_project_registration_receipts
|
|
1260
|
+
BEGIN
|
|
1261
|
+
SELECT RAISE(ABORT, 'mementos project registration receipts are immutable');
|
|
1262
|
+
END;
|
|
1263
|
+
|
|
1264
|
+
CREATE TRIGGER IF NOT EXISTS mementos_project_registration_receipts_immutable_delete
|
|
1265
|
+
BEFORE DELETE ON mementos_project_registration_receipts
|
|
1266
|
+
BEGIN
|
|
1267
|
+
SELECT RAISE(ABORT, 'mementos project registration receipts are immutable');
|
|
1268
|
+
END;
|
|
1269
|
+
`;
|
|
1270
|
+
}
|
|
1271
|
+
function postgresMementosProjectRegistrationSchemaSql() {
|
|
1272
|
+
return `
|
|
1273
|
+
CREATE TABLE IF NOT EXISTS mementos_project_registration_receipts (
|
|
1274
|
+
receipt_id TEXT PRIMARY KEY,
|
|
1275
|
+
authority TEXT NOT NULL CHECK(authority = 'mementos'),
|
|
1276
|
+
route TEXT NOT NULL,
|
|
1277
|
+
package_version TEXT NOT NULL,
|
|
1278
|
+
authority_id TEXT NOT NULL,
|
|
1279
|
+
tenant_id TEXT NOT NULL,
|
|
1280
|
+
corpus_id TEXT NOT NULL,
|
|
1281
|
+
operation_id TEXT NOT NULL,
|
|
1282
|
+
step_id TEXT NOT NULL,
|
|
1283
|
+
resource_kind TEXT NOT NULL CHECK(resource_kind = 'project'),
|
|
1284
|
+
direction TEXT NOT NULL CHECK(direction IN ('forward', 'inverse')),
|
|
1285
|
+
target_selector TEXT NOT NULL,
|
|
1286
|
+
idempotency_key TEXT NOT NULL,
|
|
1287
|
+
request_digest TEXT NOT NULL,
|
|
1288
|
+
precondition_digest TEXT NOT NULL,
|
|
1289
|
+
normalized_call_digest TEXT NOT NULL,
|
|
1290
|
+
outcome TEXT NOT NULL CHECK(outcome IN (
|
|
1291
|
+
'accepted', 'duplicate_of_accepted', 'terminal_nonacceptance'
|
|
1292
|
+
)),
|
|
1293
|
+
reason TEXT,
|
|
1294
|
+
target_id TEXT,
|
|
1295
|
+
result_revision TEXT,
|
|
1296
|
+
result_digest TEXT,
|
|
1297
|
+
duplicate_of_receipt_id TEXT,
|
|
1298
|
+
accepted_receipt_id TEXT,
|
|
1299
|
+
created_by_operation BOOLEAN NOT NULL,
|
|
1300
|
+
created_at TIMESTAMPTZ NOT NULL
|
|
1301
|
+
);
|
|
1302
|
+
|
|
1303
|
+
CREATE INDEX IF NOT EXISTS idx_mementos_project_registration_receipts_lookup
|
|
1304
|
+
ON mementos_project_registration_receipts (
|
|
1305
|
+
authority_id, tenant_id, corpus_id, operation_id, step_id,
|
|
1306
|
+
resource_kind, direction, idempotency_key, target_selector
|
|
1307
|
+
);
|
|
1308
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_mementos_project_registration_receipts_accepted_step
|
|
1309
|
+
ON mementos_project_registration_receipts (
|
|
1310
|
+
authority_id, tenant_id, corpus_id, operation_id, step_id,
|
|
1311
|
+
resource_kind, direction
|
|
1312
|
+
)
|
|
1313
|
+
WHERE outcome = 'accepted';
|
|
1314
|
+
|
|
1315
|
+
CREATE TABLE IF NOT EXISTS mementos_project_registration_bindings (
|
|
1316
|
+
authority_id TEXT NOT NULL,
|
|
1317
|
+
tenant_id TEXT NOT NULL,
|
|
1318
|
+
corpus_id TEXT NOT NULL,
|
|
1319
|
+
resource_kind TEXT NOT NULL CHECK(resource_kind = 'project'),
|
|
1320
|
+
target_selector TEXT NOT NULL,
|
|
1321
|
+
operation_id TEXT NOT NULL,
|
|
1322
|
+
step_id TEXT NOT NULL,
|
|
1323
|
+
direction TEXT NOT NULL CHECK(direction = 'forward'),
|
|
1324
|
+
idempotency_key TEXT NOT NULL,
|
|
1325
|
+
request_digest TEXT NOT NULL,
|
|
1326
|
+
precondition_digest TEXT NOT NULL,
|
|
1327
|
+
normalized_call_digest TEXT NOT NULL,
|
|
1328
|
+
state TEXT NOT NULL CHECK(state IN (
|
|
1329
|
+
'pending', 'accepted', 'terminal_nonacceptance', 'removed'
|
|
1330
|
+
)),
|
|
1331
|
+
target_id TEXT,
|
|
1332
|
+
accepted_receipt_id TEXT UNIQUE,
|
|
1333
|
+
result_revision TEXT,
|
|
1334
|
+
result_digest TEXT,
|
|
1335
|
+
removed_receipt_id TEXT,
|
|
1336
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
1337
|
+
updated_at TIMESTAMPTZ NOT NULL,
|
|
1338
|
+
PRIMARY KEY(authority_id, tenant_id, corpus_id, resource_kind, target_selector)
|
|
1339
|
+
);
|
|
1340
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_mementos_project_registration_binding_target
|
|
1341
|
+
ON mementos_project_registration_bindings(
|
|
1342
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_id
|
|
1343
|
+
)
|
|
1344
|
+
WHERE target_id IS NOT NULL;
|
|
1345
|
+
|
|
1346
|
+
CREATE OR REPLACE FUNCTION mementos_project_registration_receipts_immutable()
|
|
1347
|
+
RETURNS trigger
|
|
1348
|
+
LANGUAGE plpgsql
|
|
1349
|
+
AS $$
|
|
1350
|
+
BEGIN
|
|
1351
|
+
RAISE EXCEPTION 'mementos project registration receipts are immutable';
|
|
1352
|
+
END;
|
|
1353
|
+
$$;
|
|
1354
|
+
DROP TRIGGER IF EXISTS mementos_project_registration_receipts_immutable
|
|
1355
|
+
ON mementos_project_registration_receipts;
|
|
1356
|
+
CREATE TRIGGER mementos_project_registration_receipts_immutable
|
|
1357
|
+
BEFORE UPDATE OR DELETE ON mementos_project_registration_receipts
|
|
1358
|
+
FOR EACH ROW EXECUTE FUNCTION mementos_project_registration_receipts_immutable();
|
|
1359
|
+
`;
|
|
1360
|
+
}
|
|
1361
|
+
function sqliteMementosProjectGuardedUpdateSchemaSql() {
|
|
1362
|
+
return `
|
|
1363
|
+
CREATE TABLE IF NOT EXISTS mementos_project_update_receipts (
|
|
1364
|
+
receipt_id TEXT PRIMARY KEY,
|
|
1365
|
+
authority TEXT NOT NULL CHECK(authority = 'mementos'),
|
|
1366
|
+
route TEXT NOT NULL CHECK(route = 'mementos.project-guarded-update.v1'),
|
|
1367
|
+
package_version TEXT NOT NULL,
|
|
1368
|
+
authority_id TEXT NOT NULL,
|
|
1369
|
+
tenant_id TEXT NOT NULL,
|
|
1370
|
+
corpus_id TEXT NOT NULL,
|
|
1371
|
+
operation_id TEXT NOT NULL,
|
|
1372
|
+
step_id TEXT NOT NULL,
|
|
1373
|
+
direction TEXT NOT NULL CHECK(direction IN ('forward', 'rollback')),
|
|
1374
|
+
idempotency_key TEXT NOT NULL,
|
|
1375
|
+
request_digest TEXT NOT NULL,
|
|
1376
|
+
outcome TEXT NOT NULL CHECK(outcome = 'accepted'),
|
|
1377
|
+
target_id TEXT NOT NULL,
|
|
1378
|
+
expected_revision TEXT NOT NULL,
|
|
1379
|
+
result_revision TEXT NOT NULL,
|
|
1380
|
+
result_digest TEXT NOT NULL,
|
|
1381
|
+
accepted_receipt_id TEXT,
|
|
1382
|
+
before_project_json TEXT NOT NULL,
|
|
1383
|
+
after_project_json TEXT NOT NULL,
|
|
1384
|
+
created_at TEXT NOT NULL,
|
|
1385
|
+
UNIQUE(authority_id, tenant_id, corpus_id, direction, idempotency_key)
|
|
1386
|
+
);
|
|
1387
|
+
|
|
1388
|
+
CREATE INDEX IF NOT EXISTS idx_mementos_project_update_receipts_target
|
|
1389
|
+
ON mementos_project_update_receipts(
|
|
1390
|
+
authority_id, tenant_id, corpus_id, target_id, created_at
|
|
1391
|
+
);
|
|
1392
|
+
|
|
1393
|
+
CREATE TRIGGER IF NOT EXISTS mementos_project_update_receipts_immutable_update
|
|
1394
|
+
BEFORE UPDATE ON mementos_project_update_receipts
|
|
1395
|
+
BEGIN
|
|
1396
|
+
SELECT RAISE(ABORT, 'mementos project update receipts are immutable');
|
|
1397
|
+
END;
|
|
1398
|
+
|
|
1399
|
+
CREATE TRIGGER IF NOT EXISTS mementos_project_update_receipts_immutable_delete
|
|
1400
|
+
BEFORE DELETE ON mementos_project_update_receipts
|
|
1401
|
+
BEGIN
|
|
1402
|
+
SELECT RAISE(ABORT, 'mementos project update receipts are immutable');
|
|
1403
|
+
END;
|
|
1404
|
+
`;
|
|
1405
|
+
}
|
|
1406
|
+
function postgresMementosProjectGuardedUpdateSchemaSql() {
|
|
1407
|
+
return `
|
|
1408
|
+
CREATE TABLE IF NOT EXISTS mementos_project_update_receipts (
|
|
1409
|
+
receipt_id TEXT PRIMARY KEY,
|
|
1410
|
+
authority TEXT NOT NULL CHECK(authority = 'mementos'),
|
|
1411
|
+
route TEXT NOT NULL CHECK(route = 'mementos.project-guarded-update.v1'),
|
|
1412
|
+
package_version TEXT NOT NULL,
|
|
1413
|
+
authority_id TEXT NOT NULL,
|
|
1414
|
+
tenant_id TEXT NOT NULL,
|
|
1415
|
+
corpus_id TEXT NOT NULL,
|
|
1416
|
+
operation_id TEXT NOT NULL,
|
|
1417
|
+
step_id TEXT NOT NULL,
|
|
1418
|
+
direction TEXT NOT NULL CHECK(direction IN ('forward', 'rollback')),
|
|
1419
|
+
idempotency_key TEXT NOT NULL,
|
|
1420
|
+
request_digest TEXT NOT NULL,
|
|
1421
|
+
outcome TEXT NOT NULL CHECK(outcome = 'accepted'),
|
|
1422
|
+
target_id TEXT NOT NULL,
|
|
1423
|
+
expected_revision TEXT NOT NULL,
|
|
1424
|
+
result_revision TEXT NOT NULL,
|
|
1425
|
+
result_digest TEXT NOT NULL,
|
|
1426
|
+
accepted_receipt_id TEXT,
|
|
1427
|
+
before_project_json JSONB NOT NULL,
|
|
1428
|
+
after_project_json JSONB NOT NULL,
|
|
1429
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
1430
|
+
UNIQUE(authority_id, tenant_id, corpus_id, direction, idempotency_key)
|
|
1431
|
+
);
|
|
1432
|
+
|
|
1433
|
+
CREATE INDEX IF NOT EXISTS idx_mementos_project_update_receipts_target
|
|
1434
|
+
ON mementos_project_update_receipts(
|
|
1435
|
+
authority_id, tenant_id, corpus_id, target_id, created_at
|
|
1436
|
+
);
|
|
1437
|
+
|
|
1438
|
+
CREATE OR REPLACE FUNCTION mementos_project_update_receipts_immutable()
|
|
1439
|
+
RETURNS trigger
|
|
1440
|
+
LANGUAGE plpgsql
|
|
1441
|
+
AS $$
|
|
1442
|
+
BEGIN
|
|
1443
|
+
RAISE EXCEPTION 'mementos project update receipts are immutable';
|
|
1444
|
+
END;
|
|
1445
|
+
$$;
|
|
1446
|
+
DROP TRIGGER IF EXISTS mementos_project_update_receipts_immutable
|
|
1447
|
+
ON mementos_project_update_receipts;
|
|
1448
|
+
CREATE TRIGGER mementos_project_update_receipts_immutable
|
|
1449
|
+
BEFORE UPDATE OR DELETE ON mementos_project_update_receipts
|
|
1450
|
+
FOR EACH ROW EXECUTE FUNCTION mementos_project_update_receipts_immutable();
|
|
1451
|
+
`;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
// src/memory-project-link/schema.ts
|
|
1455
|
+
function sqliteMementosMemoryProjectLinkSchemaSql() {
|
|
1456
|
+
return `
|
|
1457
|
+
CREATE TABLE IF NOT EXISTS mementos_memory_project_link_receipts (
|
|
1458
|
+
receipt_id TEXT PRIMARY KEY,
|
|
1459
|
+
authority TEXT NOT NULL CHECK(authority = 'mementos'),
|
|
1460
|
+
route TEXT NOT NULL CHECK(route = 'mementos.memory-project-link.v1'),
|
|
1461
|
+
package_version TEXT NOT NULL,
|
|
1462
|
+
authority_id TEXT NOT NULL,
|
|
1463
|
+
tenant_id TEXT NOT NULL,
|
|
1464
|
+
corpus_id TEXT NOT NULL,
|
|
1465
|
+
operation_id TEXT NOT NULL,
|
|
1466
|
+
step_id TEXT NOT NULL,
|
|
1467
|
+
direction TEXT NOT NULL CHECK(direction IN ('forward', 'rollback')),
|
|
1468
|
+
idempotency_key TEXT NOT NULL,
|
|
1469
|
+
request_digest TEXT NOT NULL,
|
|
1470
|
+
outcome TEXT NOT NULL CHECK(outcome IN ('accepted', 'no_change')),
|
|
1471
|
+
target_memory_id TEXT NOT NULL,
|
|
1472
|
+
requested_project_id TEXT NOT NULL,
|
|
1473
|
+
expected_memory_version INTEGER NOT NULL,
|
|
1474
|
+
expected_memory_revision TEXT NOT NULL,
|
|
1475
|
+
expected_project_revision TEXT,
|
|
1476
|
+
result_memory_version INTEGER NOT NULL,
|
|
1477
|
+
result_memory_revision TEXT NOT NULL,
|
|
1478
|
+
result_memory_digest TEXT NOT NULL,
|
|
1479
|
+
result_project_revision TEXT,
|
|
1480
|
+
result_project_digest TEXT,
|
|
1481
|
+
accepted_receipt_id TEXT,
|
|
1482
|
+
before_link_json TEXT NOT NULL,
|
|
1483
|
+
after_link_json TEXT NOT NULL,
|
|
1484
|
+
before_project_revision TEXT,
|
|
1485
|
+
before_project_digest TEXT,
|
|
1486
|
+
after_project_revision TEXT,
|
|
1487
|
+
after_project_digest TEXT,
|
|
1488
|
+
created_at TEXT NOT NULL,
|
|
1489
|
+
UNIQUE(authority_id, tenant_id, corpus_id, direction, idempotency_key)
|
|
1490
|
+
);
|
|
1491
|
+
|
|
1492
|
+
CREATE INDEX IF NOT EXISTS idx_mementos_memory_project_link_receipts_target
|
|
1493
|
+
ON mementos_memory_project_link_receipts(
|
|
1494
|
+
authority_id, tenant_id, corpus_id, target_memory_id, created_at
|
|
1495
|
+
);
|
|
1496
|
+
|
|
1497
|
+
CREATE TRIGGER IF NOT EXISTS mementos_memory_project_link_receipts_immutable_update
|
|
1498
|
+
BEFORE UPDATE ON mementos_memory_project_link_receipts
|
|
1499
|
+
BEGIN
|
|
1500
|
+
SELECT RAISE(ABORT, 'mementos memory project link receipts are immutable');
|
|
1501
|
+
END;
|
|
1502
|
+
|
|
1503
|
+
CREATE TRIGGER IF NOT EXISTS mementos_memory_project_link_receipts_immutable_delete
|
|
1504
|
+
BEFORE DELETE ON mementos_memory_project_link_receipts
|
|
1505
|
+
BEGIN
|
|
1506
|
+
SELECT RAISE(ABORT, 'mementos memory project link receipts are immutable');
|
|
1507
|
+
END;
|
|
1508
|
+
`;
|
|
1509
|
+
}
|
|
1510
|
+
function postgresMementosMemoryProjectLinkSchemaSql() {
|
|
1511
|
+
return `
|
|
1512
|
+
CREATE TABLE IF NOT EXISTS mementos_memory_project_link_receipts (
|
|
1513
|
+
receipt_id TEXT PRIMARY KEY,
|
|
1514
|
+
authority TEXT NOT NULL CHECK(authority = 'mementos'),
|
|
1515
|
+
route TEXT NOT NULL CHECK(route = 'mementos.memory-project-link.v1'),
|
|
1516
|
+
package_version TEXT NOT NULL,
|
|
1517
|
+
authority_id TEXT NOT NULL,
|
|
1518
|
+
tenant_id TEXT NOT NULL,
|
|
1519
|
+
corpus_id TEXT NOT NULL,
|
|
1520
|
+
operation_id TEXT NOT NULL,
|
|
1521
|
+
step_id TEXT NOT NULL,
|
|
1522
|
+
direction TEXT NOT NULL CHECK(direction IN ('forward', 'rollback')),
|
|
1523
|
+
idempotency_key TEXT NOT NULL,
|
|
1524
|
+
request_digest TEXT NOT NULL,
|
|
1525
|
+
outcome TEXT NOT NULL CHECK(outcome IN ('accepted', 'no_change')),
|
|
1526
|
+
target_memory_id TEXT NOT NULL,
|
|
1527
|
+
requested_project_id TEXT NOT NULL,
|
|
1528
|
+
expected_memory_version INTEGER NOT NULL,
|
|
1529
|
+
expected_memory_revision TEXT NOT NULL,
|
|
1530
|
+
expected_project_revision TEXT,
|
|
1531
|
+
result_memory_version INTEGER NOT NULL,
|
|
1532
|
+
result_memory_revision TEXT NOT NULL,
|
|
1533
|
+
result_memory_digest TEXT NOT NULL,
|
|
1534
|
+
result_project_revision TEXT,
|
|
1535
|
+
result_project_digest TEXT,
|
|
1536
|
+
accepted_receipt_id TEXT,
|
|
1537
|
+
before_link_json JSONB NOT NULL,
|
|
1538
|
+
after_link_json JSONB NOT NULL,
|
|
1539
|
+
before_project_revision TEXT,
|
|
1540
|
+
before_project_digest TEXT,
|
|
1541
|
+
after_project_revision TEXT,
|
|
1542
|
+
after_project_digest TEXT,
|
|
1543
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
1544
|
+
UNIQUE(authority_id, tenant_id, corpus_id, direction, idempotency_key)
|
|
1545
|
+
);
|
|
1546
|
+
|
|
1547
|
+
CREATE INDEX IF NOT EXISTS idx_mementos_memory_project_link_receipts_target
|
|
1548
|
+
ON mementos_memory_project_link_receipts(
|
|
1549
|
+
authority_id, tenant_id, corpus_id, target_memory_id, created_at
|
|
1550
|
+
);
|
|
1551
|
+
|
|
1552
|
+
CREATE OR REPLACE FUNCTION mementos_memory_project_link_receipts_immutable()
|
|
1553
|
+
RETURNS trigger
|
|
1554
|
+
LANGUAGE plpgsql
|
|
1555
|
+
AS $$
|
|
1556
|
+
BEGIN
|
|
1557
|
+
RAISE EXCEPTION 'mementos memory project link receipts are immutable';
|
|
1558
|
+
END;
|
|
1559
|
+
$$;
|
|
1560
|
+
DROP TRIGGER IF EXISTS mementos_memory_project_link_receipts_immutable
|
|
1561
|
+
ON mementos_memory_project_link_receipts;
|
|
1562
|
+
CREATE TRIGGER mementos_memory_project_link_receipts_immutable
|
|
1563
|
+
BEFORE UPDATE OR DELETE ON mementos_memory_project_link_receipts
|
|
1564
|
+
FOR EACH ROW EXECUTE FUNCTION mementos_memory_project_link_receipts_immutable();
|
|
1565
|
+
`;
|
|
1566
|
+
}
|
|
1567
|
+
var MEMENTOS_MEMORY_PROJECT_LINK_ROUTE = "mementos.memory-project-link.v1", MEMORY_PROJECT_LINK_RECEIPT_COLUMNS;
|
|
1568
|
+
var init_schema = __esm(() => {
|
|
1569
|
+
MEMORY_PROJECT_LINK_RECEIPT_COLUMNS = [
|
|
1570
|
+
"receipt_id",
|
|
1571
|
+
"authority",
|
|
1572
|
+
"route",
|
|
1573
|
+
"package_version",
|
|
1574
|
+
"authority_id",
|
|
1575
|
+
"tenant_id",
|
|
1576
|
+
"corpus_id",
|
|
1577
|
+
"operation_id",
|
|
1578
|
+
"step_id",
|
|
1579
|
+
"direction",
|
|
1580
|
+
"idempotency_key",
|
|
1581
|
+
"request_digest",
|
|
1582
|
+
"outcome",
|
|
1583
|
+
"target_memory_id",
|
|
1584
|
+
"requested_project_id",
|
|
1585
|
+
"expected_memory_version",
|
|
1586
|
+
"expected_memory_revision",
|
|
1587
|
+
"expected_project_revision",
|
|
1588
|
+
"result_memory_version",
|
|
1589
|
+
"result_memory_revision",
|
|
1590
|
+
"result_memory_digest",
|
|
1591
|
+
"result_project_revision",
|
|
1592
|
+
"result_project_digest",
|
|
1593
|
+
"accepted_receipt_id",
|
|
1594
|
+
"before_link_json",
|
|
1595
|
+
"after_link_json",
|
|
1596
|
+
"before_project_revision",
|
|
1597
|
+
"before_project_digest",
|
|
1598
|
+
"after_project_revision",
|
|
1599
|
+
"after_project_digest",
|
|
1600
|
+
"created_at"
|
|
1601
|
+
];
|
|
1602
|
+
});
|
|
1603
|
+
|
|
1181
1604
|
// src/db/migrations.ts
|
|
1182
1605
|
var MEMORY_VERSION_SNAPSHOT_TRIGGER = `
|
|
1183
1606
|
CREATE TRIGGER IF NOT EXISTS memories_version_snapshot
|
|
@@ -1198,6 +1621,7 @@ BEGIN
|
|
|
1198
1621
|
END;
|
|
1199
1622
|
`, MIGRATIONS;
|
|
1200
1623
|
var init_migrations = __esm(() => {
|
|
1624
|
+
init_schema();
|
|
1201
1625
|
MIGRATIONS = [
|
|
1202
1626
|
`
|
|
1203
1627
|
CREATE TABLE IF NOT EXISTS projects (
|
|
@@ -2131,6 +2555,18 @@ UPDATE memory_audit_log SET new_value_hash = NULL
|
|
|
2131
2555
|
AND new_value_hash NOT GLOB '*[^0-9A-F]*';
|
|
2132
2556
|
|
|
2133
2557
|
INSERT OR IGNORE INTO _migrations (id) VALUES (37);
|
|
2558
|
+
`,
|
|
2559
|
+
`
|
|
2560
|
+
${sqliteMementosProjectRegistrationSchemaSql()}
|
|
2561
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (38);
|
|
2562
|
+
`,
|
|
2563
|
+
`
|
|
2564
|
+
${sqliteMementosProjectGuardedUpdateSchemaSql()}
|
|
2565
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (39);
|
|
2566
|
+
`,
|
|
2567
|
+
`
|
|
2568
|
+
${sqliteMementosMemoryProjectLinkSchemaSql()}
|
|
2569
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (40);
|
|
2134
2570
|
`
|
|
2135
2571
|
];
|
|
2136
2572
|
});
|
|
@@ -2501,8 +2937,8 @@ var init_util = __esm(() => {
|
|
|
2501
2937
|
|
|
2502
2938
|
// node_modules/zod/v3/ZodError.js
|
|
2503
2939
|
var ZodIssueCode, quotelessJson = (obj) => {
|
|
2504
|
-
const
|
|
2505
|
-
return
|
|
2940
|
+
const json2 = JSON.stringify(obj, null, 2);
|
|
2941
|
+
return json2.replace(/"([^"]+)":/g, "$1:");
|
|
2506
2942
|
}, ZodError;
|
|
2507
2943
|
var init_ZodError = __esm(() => {
|
|
2508
2944
|
init_util();
|
|
@@ -15694,24 +16130,24 @@ class JSONSchemaGenerator {
|
|
|
15694
16130
|
const _json = result.schema;
|
|
15695
16131
|
switch (def.type) {
|
|
15696
16132
|
case "string": {
|
|
15697
|
-
const
|
|
15698
|
-
|
|
16133
|
+
const json2 = _json;
|
|
16134
|
+
json2.type = "string";
|
|
15699
16135
|
const { minimum, maximum, format, patterns, contentEncoding } = schema._zod.bag;
|
|
15700
16136
|
if (typeof minimum === "number")
|
|
15701
|
-
|
|
16137
|
+
json2.minLength = minimum;
|
|
15702
16138
|
if (typeof maximum === "number")
|
|
15703
|
-
|
|
16139
|
+
json2.maxLength = maximum;
|
|
15704
16140
|
if (format) {
|
|
15705
|
-
|
|
15706
|
-
if (
|
|
15707
|
-
delete
|
|
16141
|
+
json2.format = formatMap[format] ?? format;
|
|
16142
|
+
if (json2.format === "")
|
|
16143
|
+
delete json2.format;
|
|
15708
16144
|
}
|
|
15709
16145
|
if (contentEncoding)
|
|
15710
|
-
|
|
16146
|
+
json2.contentEncoding = contentEncoding;
|
|
15711
16147
|
if (patterns && patterns.size > 0) {
|
|
15712
16148
|
const regexes = [...patterns];
|
|
15713
16149
|
if (regexes.length === 1)
|
|
15714
|
-
|
|
16150
|
+
json2.pattern = regexes[0].source;
|
|
15715
16151
|
else if (regexes.length > 1) {
|
|
15716
16152
|
result.schema.allOf = [
|
|
15717
16153
|
...regexes.map((regex) => ({
|
|
@@ -15724,41 +16160,41 @@ class JSONSchemaGenerator {
|
|
|
15724
16160
|
break;
|
|
15725
16161
|
}
|
|
15726
16162
|
case "number": {
|
|
15727
|
-
const
|
|
16163
|
+
const json2 = _json;
|
|
15728
16164
|
const { minimum, maximum, format, multipleOf, exclusiveMaximum, exclusiveMinimum } = schema._zod.bag;
|
|
15729
16165
|
if (typeof format === "string" && format.includes("int"))
|
|
15730
|
-
|
|
16166
|
+
json2.type = "integer";
|
|
15731
16167
|
else
|
|
15732
|
-
|
|
16168
|
+
json2.type = "number";
|
|
15733
16169
|
if (typeof exclusiveMinimum === "number")
|
|
15734
|
-
|
|
16170
|
+
json2.exclusiveMinimum = exclusiveMinimum;
|
|
15735
16171
|
if (typeof minimum === "number") {
|
|
15736
|
-
|
|
16172
|
+
json2.minimum = minimum;
|
|
15737
16173
|
if (typeof exclusiveMinimum === "number") {
|
|
15738
16174
|
if (exclusiveMinimum >= minimum)
|
|
15739
|
-
delete
|
|
16175
|
+
delete json2.minimum;
|
|
15740
16176
|
else
|
|
15741
|
-
delete
|
|
16177
|
+
delete json2.exclusiveMinimum;
|
|
15742
16178
|
}
|
|
15743
16179
|
}
|
|
15744
16180
|
if (typeof exclusiveMaximum === "number")
|
|
15745
|
-
|
|
16181
|
+
json2.exclusiveMaximum = exclusiveMaximum;
|
|
15746
16182
|
if (typeof maximum === "number") {
|
|
15747
|
-
|
|
16183
|
+
json2.maximum = maximum;
|
|
15748
16184
|
if (typeof exclusiveMaximum === "number") {
|
|
15749
16185
|
if (exclusiveMaximum <= maximum)
|
|
15750
|
-
delete
|
|
16186
|
+
delete json2.maximum;
|
|
15751
16187
|
else
|
|
15752
|
-
delete
|
|
16188
|
+
delete json2.exclusiveMaximum;
|
|
15753
16189
|
}
|
|
15754
16190
|
}
|
|
15755
16191
|
if (typeof multipleOf === "number")
|
|
15756
|
-
|
|
16192
|
+
json2.multipleOf = multipleOf;
|
|
15757
16193
|
break;
|
|
15758
16194
|
}
|
|
15759
16195
|
case "boolean": {
|
|
15760
|
-
const
|
|
15761
|
-
|
|
16196
|
+
const json2 = _json;
|
|
16197
|
+
json2.type = "boolean";
|
|
15762
16198
|
break;
|
|
15763
16199
|
}
|
|
15764
16200
|
case "bigint": {
|
|
@@ -15806,23 +16242,23 @@ class JSONSchemaGenerator {
|
|
|
15806
16242
|
break;
|
|
15807
16243
|
}
|
|
15808
16244
|
case "array": {
|
|
15809
|
-
const
|
|
16245
|
+
const json2 = _json;
|
|
15810
16246
|
const { minimum, maximum } = schema._zod.bag;
|
|
15811
16247
|
if (typeof minimum === "number")
|
|
15812
|
-
|
|
16248
|
+
json2.minItems = minimum;
|
|
15813
16249
|
if (typeof maximum === "number")
|
|
15814
|
-
|
|
15815
|
-
|
|
15816
|
-
|
|
16250
|
+
json2.maxItems = maximum;
|
|
16251
|
+
json2.type = "array";
|
|
16252
|
+
json2.items = this.process(def.element, { ...params, path: [...params.path, "items"] });
|
|
15817
16253
|
break;
|
|
15818
16254
|
}
|
|
15819
16255
|
case "object": {
|
|
15820
|
-
const
|
|
15821
|
-
|
|
15822
|
-
|
|
16256
|
+
const json2 = _json;
|
|
16257
|
+
json2.type = "object";
|
|
16258
|
+
json2.properties = {};
|
|
15823
16259
|
const shape = def.shape;
|
|
15824
16260
|
for (const key in shape) {
|
|
15825
|
-
|
|
16261
|
+
json2.properties[key] = this.process(shape[key], {
|
|
15826
16262
|
...params,
|
|
15827
16263
|
path: [...params.path, "properties", key]
|
|
15828
16264
|
});
|
|
@@ -15837,15 +16273,15 @@ class JSONSchemaGenerator {
|
|
|
15837
16273
|
}
|
|
15838
16274
|
}));
|
|
15839
16275
|
if (requiredKeys.size > 0) {
|
|
15840
|
-
|
|
16276
|
+
json2.required = Array.from(requiredKeys);
|
|
15841
16277
|
}
|
|
15842
16278
|
if (def.catchall?._zod.def.type === "never") {
|
|
15843
|
-
|
|
16279
|
+
json2.additionalProperties = false;
|
|
15844
16280
|
} else if (!def.catchall) {
|
|
15845
16281
|
if (this.io === "output")
|
|
15846
|
-
|
|
16282
|
+
json2.additionalProperties = false;
|
|
15847
16283
|
} else if (def.catchall) {
|
|
15848
|
-
|
|
16284
|
+
json2.additionalProperties = this.process(def.catchall, {
|
|
15849
16285
|
...params,
|
|
15850
16286
|
path: [...params.path, "additionalProperties"]
|
|
15851
16287
|
});
|
|
@@ -15853,15 +16289,15 @@ class JSONSchemaGenerator {
|
|
|
15853
16289
|
break;
|
|
15854
16290
|
}
|
|
15855
16291
|
case "union": {
|
|
15856
|
-
const
|
|
15857
|
-
|
|
16292
|
+
const json2 = _json;
|
|
16293
|
+
json2.anyOf = def.options.map((x, i) => this.process(x, {
|
|
15858
16294
|
...params,
|
|
15859
16295
|
path: [...params.path, "anyOf", i]
|
|
15860
16296
|
}));
|
|
15861
16297
|
break;
|
|
15862
16298
|
}
|
|
15863
16299
|
case "intersection": {
|
|
15864
|
-
const
|
|
16300
|
+
const json2 = _json;
|
|
15865
16301
|
const a = this.process(def.left, {
|
|
15866
16302
|
...params,
|
|
15867
16303
|
path: [...params.path, "allOf", 0]
|
|
@@ -15875,17 +16311,17 @@ class JSONSchemaGenerator {
|
|
|
15875
16311
|
...isSimpleIntersection(a) ? a.allOf : [a],
|
|
15876
16312
|
...isSimpleIntersection(b) ? b.allOf : [b]
|
|
15877
16313
|
];
|
|
15878
|
-
|
|
16314
|
+
json2.allOf = allOf;
|
|
15879
16315
|
break;
|
|
15880
16316
|
}
|
|
15881
16317
|
case "tuple": {
|
|
15882
|
-
const
|
|
15883
|
-
|
|
16318
|
+
const json2 = _json;
|
|
16319
|
+
json2.type = "array";
|
|
15884
16320
|
const prefixItems = def.items.map((x, i) => this.process(x, { ...params, path: [...params.path, "prefixItems", i] }));
|
|
15885
16321
|
if (this.target === "draft-2020-12") {
|
|
15886
|
-
|
|
16322
|
+
json2.prefixItems = prefixItems;
|
|
15887
16323
|
} else {
|
|
15888
|
-
|
|
16324
|
+
json2.items = prefixItems;
|
|
15889
16325
|
}
|
|
15890
16326
|
if (def.rest) {
|
|
15891
16327
|
const rest = this.process(def.rest, {
|
|
@@ -15893,29 +16329,29 @@ class JSONSchemaGenerator {
|
|
|
15893
16329
|
path: [...params.path, "items"]
|
|
15894
16330
|
});
|
|
15895
16331
|
if (this.target === "draft-2020-12") {
|
|
15896
|
-
|
|
16332
|
+
json2.items = rest;
|
|
15897
16333
|
} else {
|
|
15898
|
-
|
|
16334
|
+
json2.additionalItems = rest;
|
|
15899
16335
|
}
|
|
15900
16336
|
}
|
|
15901
16337
|
if (def.rest) {
|
|
15902
|
-
|
|
16338
|
+
json2.items = this.process(def.rest, {
|
|
15903
16339
|
...params,
|
|
15904
16340
|
path: [...params.path, "items"]
|
|
15905
16341
|
});
|
|
15906
16342
|
}
|
|
15907
16343
|
const { minimum, maximum } = schema._zod.bag;
|
|
15908
16344
|
if (typeof minimum === "number")
|
|
15909
|
-
|
|
16345
|
+
json2.minItems = minimum;
|
|
15910
16346
|
if (typeof maximum === "number")
|
|
15911
|
-
|
|
16347
|
+
json2.maxItems = maximum;
|
|
15912
16348
|
break;
|
|
15913
16349
|
}
|
|
15914
16350
|
case "record": {
|
|
15915
|
-
const
|
|
15916
|
-
|
|
15917
|
-
|
|
15918
|
-
|
|
16351
|
+
const json2 = _json;
|
|
16352
|
+
json2.type = "object";
|
|
16353
|
+
json2.propertyNames = this.process(def.keyType, { ...params, path: [...params.path, "propertyNames"] });
|
|
16354
|
+
json2.additionalProperties = this.process(def.valueType, {
|
|
15919
16355
|
...params,
|
|
15920
16356
|
path: [...params.path, "additionalProperties"]
|
|
15921
16357
|
});
|
|
@@ -15934,17 +16370,17 @@ class JSONSchemaGenerator {
|
|
|
15934
16370
|
break;
|
|
15935
16371
|
}
|
|
15936
16372
|
case "enum": {
|
|
15937
|
-
const
|
|
16373
|
+
const json2 = _json;
|
|
15938
16374
|
const values = getEnumValues(def.entries);
|
|
15939
16375
|
if (values.every((v) => typeof v === "number"))
|
|
15940
|
-
|
|
16376
|
+
json2.type = "number";
|
|
15941
16377
|
if (values.every((v) => typeof v === "string"))
|
|
15942
|
-
|
|
15943
|
-
|
|
16378
|
+
json2.type = "string";
|
|
16379
|
+
json2.enum = values;
|
|
15944
16380
|
break;
|
|
15945
16381
|
}
|
|
15946
16382
|
case "literal": {
|
|
15947
|
-
const
|
|
16383
|
+
const json2 = _json;
|
|
15948
16384
|
const vals = [];
|
|
15949
16385
|
for (const val of def.values) {
|
|
15950
16386
|
if (val === undefined) {
|
|
@@ -15963,23 +16399,23 @@ class JSONSchemaGenerator {
|
|
|
15963
16399
|
}
|
|
15964
16400
|
if (vals.length === 0) {} else if (vals.length === 1) {
|
|
15965
16401
|
const val = vals[0];
|
|
15966
|
-
|
|
15967
|
-
|
|
16402
|
+
json2.type = val === null ? "null" : typeof val;
|
|
16403
|
+
json2.const = val;
|
|
15968
16404
|
} else {
|
|
15969
16405
|
if (vals.every((v) => typeof v === "number"))
|
|
15970
|
-
|
|
16406
|
+
json2.type = "number";
|
|
15971
16407
|
if (vals.every((v) => typeof v === "string"))
|
|
15972
|
-
|
|
16408
|
+
json2.type = "string";
|
|
15973
16409
|
if (vals.every((v) => typeof v === "boolean"))
|
|
15974
|
-
|
|
16410
|
+
json2.type = "string";
|
|
15975
16411
|
if (vals.every((v) => v === null))
|
|
15976
|
-
|
|
15977
|
-
|
|
16412
|
+
json2.type = "null";
|
|
16413
|
+
json2.enum = vals;
|
|
15978
16414
|
}
|
|
15979
16415
|
break;
|
|
15980
16416
|
}
|
|
15981
16417
|
case "file": {
|
|
15982
|
-
const
|
|
16418
|
+
const json2 = _json;
|
|
15983
16419
|
const file = {
|
|
15984
16420
|
type: "string",
|
|
15985
16421
|
format: "binary",
|
|
@@ -15993,15 +16429,15 @@ class JSONSchemaGenerator {
|
|
|
15993
16429
|
if (mime) {
|
|
15994
16430
|
if (mime.length === 1) {
|
|
15995
16431
|
file.contentMediaType = mime[0];
|
|
15996
|
-
Object.assign(
|
|
16432
|
+
Object.assign(json2, file);
|
|
15997
16433
|
} else {
|
|
15998
|
-
|
|
16434
|
+
json2.anyOf = mime.map((m) => {
|
|
15999
16435
|
const mFile = { ...file, contentMediaType: m };
|
|
16000
16436
|
return mFile;
|
|
16001
16437
|
});
|
|
16002
16438
|
}
|
|
16003
16439
|
} else {
|
|
16004
|
-
Object.assign(
|
|
16440
|
+
Object.assign(json2, file);
|
|
16005
16441
|
}
|
|
16006
16442
|
break;
|
|
16007
16443
|
}
|
|
@@ -16022,8 +16458,8 @@ class JSONSchemaGenerator {
|
|
|
16022
16458
|
break;
|
|
16023
16459
|
}
|
|
16024
16460
|
case "success": {
|
|
16025
|
-
const
|
|
16026
|
-
|
|
16461
|
+
const json2 = _json;
|
|
16462
|
+
json2.type = "boolean";
|
|
16027
16463
|
break;
|
|
16028
16464
|
}
|
|
16029
16465
|
case "default": {
|
|
@@ -16058,12 +16494,12 @@ class JSONSchemaGenerator {
|
|
|
16058
16494
|
break;
|
|
16059
16495
|
}
|
|
16060
16496
|
case "template_literal": {
|
|
16061
|
-
const
|
|
16497
|
+
const json2 = _json;
|
|
16062
16498
|
const pattern = schema._zod.pattern;
|
|
16063
16499
|
if (!pattern)
|
|
16064
16500
|
throw new Error("Pattern not found in template literal");
|
|
16065
|
-
|
|
16066
|
-
|
|
16501
|
+
json2.type = "string";
|
|
16502
|
+
json2.pattern = pattern.source;
|
|
16067
16503
|
break;
|
|
16068
16504
|
}
|
|
16069
16505
|
case "pipe": {
|
|
@@ -17156,7 +17592,7 @@ function _instanceof(cls, params = {
|
|
|
17156
17592
|
inst._zod.bag.Class = cls;
|
|
17157
17593
|
return inst;
|
|
17158
17594
|
}
|
|
17159
|
-
function
|
|
17595
|
+
function json2(params) {
|
|
17160
17596
|
const jsonSchema = lazy(() => {
|
|
17161
17597
|
return union([string2(params), number2(), boolean2(), _null3(), array(jsonSchema), record(string2(), jsonSchema)]);
|
|
17162
17598
|
});
|
|
@@ -17843,7 +18279,7 @@ __export(exports_external2, {
|
|
|
17843
18279
|
ksuid: () => ksuid2,
|
|
17844
18280
|
keyof: () => keyof,
|
|
17845
18281
|
jwt: () => jwt,
|
|
17846
|
-
json: () =>
|
|
18282
|
+
json: () => json2,
|
|
17847
18283
|
iso: () => exports_iso,
|
|
17848
18284
|
ipv6: () => ipv62,
|
|
17849
18285
|
ipv4: () => ipv42,
|
|
@@ -19703,7 +20139,7 @@ function createProviderToolFactoryWithOutputSchema({
|
|
|
19703
20139
|
supportsDeferredResults
|
|
19704
20140
|
});
|
|
19705
20141
|
}
|
|
19706
|
-
async function
|
|
20142
|
+
async function resolve4(value) {
|
|
19707
20143
|
if (typeof value === "function") {
|
|
19708
20144
|
value = value();
|
|
19709
20145
|
}
|
|
@@ -22525,11 +22961,11 @@ var VERSION2 = "3.0.82", anthropicErrorDataSchema, anthropicFailedResponseHandle
|
|
|
22525
22961
|
betas,
|
|
22526
22962
|
headers
|
|
22527
22963
|
}) {
|
|
22528
|
-
return combineHeaders(await
|
|
22964
|
+
return combineHeaders(await resolve4(this.config.headers), headers, betas.size > 0 ? { "anthropic-beta": Array.from(betas).join(",") } : {});
|
|
22529
22965
|
}
|
|
22530
22966
|
async getBetasFromHeaders(requestHeaders) {
|
|
22531
22967
|
var _a16, _b16;
|
|
22532
|
-
const configHeaders = await
|
|
22968
|
+
const configHeaders = await resolve4(this.config.headers);
|
|
22533
22969
|
const configBetaHeader = (_a16 = configHeaders["anthropic-beta"]) != null ? _a16 : "";
|
|
22534
22970
|
const requestBetaHeader = (_b16 = requestHeaders == null ? undefined : requestHeaders["anthropic-beta"]) != null ? _b16 : "";
|
|
22535
22971
|
return new Set([
|
|
@@ -33199,24 +33635,24 @@ var require_oauth = __commonJS((exports, module) => {
|
|
|
33199
33635
|
});
|
|
33200
33636
|
}
|
|
33201
33637
|
async function processTokenResponse(response) {
|
|
33202
|
-
const
|
|
33638
|
+
const json3 = await response.json();
|
|
33203
33639
|
if (!response.ok) {
|
|
33204
|
-
const errorMsg = typeof
|
|
33640
|
+
const errorMsg = typeof json3 === "object" && json3 && "error" in json3 ? String(json3.error) : "Token refresh failed";
|
|
33205
33641
|
return [new Error(errorMsg)];
|
|
33206
33642
|
}
|
|
33207
|
-
if (typeof
|
|
33643
|
+
if (typeof json3 !== "object" || json3 === null) {
|
|
33208
33644
|
return [new Error("Invalid token response")];
|
|
33209
33645
|
}
|
|
33210
|
-
if (typeof
|
|
33646
|
+
if (typeof json3.access_token !== "string") {
|
|
33211
33647
|
return [new Error("Missing access_token in response")];
|
|
33212
33648
|
}
|
|
33213
|
-
if (
|
|
33649
|
+
if (json3.token_type !== "Bearer") {
|
|
33214
33650
|
return [new Error("Invalid token_type in response")];
|
|
33215
33651
|
}
|
|
33216
|
-
if (typeof
|
|
33652
|
+
if (typeof json3.expires_in !== "number") {
|
|
33217
33653
|
return [new Error("Missing expires_in in response")];
|
|
33218
33654
|
}
|
|
33219
|
-
return [null,
|
|
33655
|
+
return [null, json3];
|
|
33220
33656
|
}
|
|
33221
33657
|
});
|
|
33222
33658
|
|
|
@@ -33953,7 +34389,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
33953
34389
|
try {
|
|
33954
34390
|
const { value } = await getFromApi({
|
|
33955
34391
|
url: `${this.config.baseURL}/config`,
|
|
33956
|
-
headers: await
|
|
34392
|
+
headers: await resolve4(this.config.headers()),
|
|
33957
34393
|
successfulResponseHandler: createJsonResponseHandler(gatewayAvailableModelsResponseSchema),
|
|
33958
34394
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
33959
34395
|
errorSchema: exports_external2.any(),
|
|
@@ -33971,7 +34407,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
33971
34407
|
const baseUrl = new URL(this.config.baseURL);
|
|
33972
34408
|
const { value } = await getFromApi({
|
|
33973
34409
|
url: `${baseUrl.origin}/v1/credits`,
|
|
33974
|
-
headers: await
|
|
34410
|
+
headers: await resolve4(this.config.headers()),
|
|
33975
34411
|
successfulResponseHandler: createJsonResponseHandler(gatewayCreditsResponseSchema),
|
|
33976
34412
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
33977
34413
|
errorSchema: exports_external2.any(),
|
|
@@ -34017,7 +34453,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
34017
34453
|
}
|
|
34018
34454
|
const { value } = await getFromApi({
|
|
34019
34455
|
url: `${baseUrl.origin}/v1/report?${searchParams.toString()}`,
|
|
34020
|
-
headers: await
|
|
34456
|
+
headers: await resolve4(this.config.headers()),
|
|
34021
34457
|
successfulResponseHandler: createJsonResponseHandler(gatewaySpendReportResponseSchema),
|
|
34022
34458
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
34023
34459
|
errorSchema: exports_external2.any(),
|
|
@@ -34039,7 +34475,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
34039
34475
|
const baseUrl = new URL(this.config.baseURL);
|
|
34040
34476
|
const { value } = await getFromApi({
|
|
34041
34477
|
url: `${baseUrl.origin}/v1/generation?id=${encodeURIComponent(params.id)}`,
|
|
34042
|
-
headers: await
|
|
34478
|
+
headers: await resolve4(this.config.headers()),
|
|
34043
34479
|
successfulResponseHandler: createJsonResponseHandler(gatewayGenerationInfoResponseSchema),
|
|
34044
34480
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
34045
34481
|
errorSchema: exports_external2.any(),
|
|
@@ -34072,7 +34508,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
34072
34508
|
async doGenerate(options) {
|
|
34073
34509
|
const { args, warnings } = await this.getArgs(options);
|
|
34074
34510
|
const { abortSignal } = options;
|
|
34075
|
-
const resolvedHeaders = await
|
|
34511
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
34076
34512
|
try {
|
|
34077
34513
|
const {
|
|
34078
34514
|
responseHeaders,
|
|
@@ -34080,7 +34516,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
34080
34516
|
rawValue: rawResponse
|
|
34081
34517
|
} = await postJsonToApi({
|
|
34082
34518
|
url: this.getUrl(),
|
|
34083
|
-
headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, false), await
|
|
34519
|
+
headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, false), await resolve4(this.config.o11yHeaders)),
|
|
34084
34520
|
body: args,
|
|
34085
34521
|
successfulResponseHandler: createJsonResponseHandler(exports_external2.any()),
|
|
34086
34522
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
@@ -34103,11 +34539,11 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
34103
34539
|
async doStream(options) {
|
|
34104
34540
|
const { args, warnings } = await this.getArgs(options);
|
|
34105
34541
|
const { abortSignal } = options;
|
|
34106
|
-
const resolvedHeaders = await
|
|
34542
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
34107
34543
|
try {
|
|
34108
34544
|
const { value: response, responseHeaders } = await postJsonToApi({
|
|
34109
34545
|
url: this.getUrl(),
|
|
34110
|
-
headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, true), await
|
|
34546
|
+
headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, true), await resolve4(this.config.o11yHeaders)),
|
|
34111
34547
|
body: args,
|
|
34112
34548
|
successfulResponseHandler: createEventSourceResponseHandler(exports_external2.any()),
|
|
34113
34549
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
@@ -34192,7 +34628,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
34192
34628
|
providerOptions
|
|
34193
34629
|
}) {
|
|
34194
34630
|
var _a102;
|
|
34195
|
-
const resolvedHeaders = await
|
|
34631
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
34196
34632
|
try {
|
|
34197
34633
|
const {
|
|
34198
34634
|
responseHeaders,
|
|
@@ -34200,7 +34636,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
34200
34636
|
rawValue
|
|
34201
34637
|
} = await postJsonToApi({
|
|
34202
34638
|
url: this.getUrl(),
|
|
34203
|
-
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await
|
|
34639
|
+
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders)),
|
|
34204
34640
|
body: {
|
|
34205
34641
|
values,
|
|
34206
34642
|
...providerOptions ? { providerOptions } : {}
|
|
@@ -34256,7 +34692,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
34256
34692
|
abortSignal
|
|
34257
34693
|
}) {
|
|
34258
34694
|
var _a102, _b102, _c, _d;
|
|
34259
|
-
const resolvedHeaders = await
|
|
34695
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
34260
34696
|
try {
|
|
34261
34697
|
const {
|
|
34262
34698
|
responseHeaders,
|
|
@@ -34264,7 +34700,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
34264
34700
|
rawValue
|
|
34265
34701
|
} = await postJsonToApi({
|
|
34266
34702
|
url: this.getUrl(),
|
|
34267
|
-
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await
|
|
34703
|
+
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders)),
|
|
34268
34704
|
body: {
|
|
34269
34705
|
prompt,
|
|
34270
34706
|
n,
|
|
@@ -34339,11 +34775,11 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
34339
34775
|
abortSignal
|
|
34340
34776
|
}) {
|
|
34341
34777
|
var _a102;
|
|
34342
|
-
const resolvedHeaders = await
|
|
34778
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
34343
34779
|
try {
|
|
34344
34780
|
const { responseHeaders, value: responseBody } = await postJsonToApi({
|
|
34345
34781
|
url: this.getUrl(),
|
|
34346
|
-
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await
|
|
34782
|
+
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders), { accept: "text/event-stream" }),
|
|
34347
34783
|
body: {
|
|
34348
34784
|
prompt,
|
|
34349
34785
|
n,
|
|
@@ -34466,7 +34902,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
34466
34902
|
abortSignal,
|
|
34467
34903
|
providerOptions
|
|
34468
34904
|
}) {
|
|
34469
|
-
const resolvedHeaders = await
|
|
34905
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
34470
34906
|
try {
|
|
34471
34907
|
const {
|
|
34472
34908
|
responseHeaders,
|
|
@@ -34474,7 +34910,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
34474
34910
|
rawValue
|
|
34475
34911
|
} = await postJsonToApi({
|
|
34476
34912
|
url: this.getUrl(),
|
|
34477
|
-
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await
|
|
34913
|
+
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders)),
|
|
34478
34914
|
body: {
|
|
34479
34915
|
documents,
|
|
34480
34916
|
query,
|
|
@@ -44078,7 +44514,7 @@ var import_api2, import_api3, __defProp2, __export2 = (target, all) => {
|
|
|
44078
44514
|
const schema = asSchema(inputSchema);
|
|
44079
44515
|
return {
|
|
44080
44516
|
name: "object",
|
|
44081
|
-
responseFormat:
|
|
44517
|
+
responseFormat: resolve4(schema.jsonSchema).then((jsonSchema2) => ({
|
|
44082
44518
|
type: "json",
|
|
44083
44519
|
schema: jsonSchema2,
|
|
44084
44520
|
...name21 != null && { name: name21 },
|
|
@@ -44139,7 +44575,7 @@ var import_api2, import_api3, __defProp2, __export2 = (target, all) => {
|
|
|
44139
44575
|
const elementSchema = asSchema(inputElementSchema);
|
|
44140
44576
|
return {
|
|
44141
44577
|
name: "array",
|
|
44142
|
-
responseFormat:
|
|
44578
|
+
responseFormat: resolve4(elementSchema.jsonSchema).then((jsonSchema2) => {
|
|
44143
44579
|
const { $schema, ...itemSchema } = jsonSchema2;
|
|
44144
44580
|
return {
|
|
44145
44581
|
type: "json",
|
|
@@ -44316,7 +44752,7 @@ var import_api2, import_api3, __defProp2, __export2 = (target, all) => {
|
|
|
44316
44752
|
return;
|
|
44317
44753
|
}
|
|
44318
44754
|
};
|
|
44319
|
-
},
|
|
44755
|
+
}, json3 = ({
|
|
44320
44756
|
name: name21,
|
|
44321
44757
|
description
|
|
44322
44758
|
} = {}) => {
|
|
@@ -47052,9 +47488,9 @@ var import_api2, import_api3, __defProp2, __export2 = (target, all) => {
|
|
|
47052
47488
|
...options
|
|
47053
47489
|
}) {
|
|
47054
47490
|
var _a21, _b16, _c, _d, _e;
|
|
47055
|
-
const resolvedBody = await
|
|
47056
|
-
const resolvedHeaders = await
|
|
47057
|
-
const resolvedCredentials = await
|
|
47491
|
+
const resolvedBody = await resolve4(this.body);
|
|
47492
|
+
const resolvedHeaders = await resolve4(this.headers);
|
|
47493
|
+
const resolvedCredentials = await resolve4(this.credentials);
|
|
47058
47494
|
const baseHeaders = {
|
|
47059
47495
|
...normalizeHeaders(resolvedHeaders),
|
|
47060
47496
|
...normalizeHeaders(options.headers)
|
|
@@ -47102,9 +47538,9 @@ var import_api2, import_api3, __defProp2, __export2 = (target, all) => {
|
|
|
47102
47538
|
}
|
|
47103
47539
|
async reconnectToStream(options) {
|
|
47104
47540
|
var _a21, _b16, _c, _d, _e;
|
|
47105
|
-
const resolvedBody = await
|
|
47106
|
-
const resolvedHeaders = await
|
|
47107
|
-
const resolvedCredentials = await
|
|
47541
|
+
const resolvedBody = await resolve4(this.body);
|
|
47542
|
+
const resolvedHeaders = await resolve4(this.headers);
|
|
47543
|
+
const resolvedCredentials = await resolve4(this.credentials);
|
|
47108
47544
|
const baseHeaders = {
|
|
47109
47545
|
...normalizeHeaders(resolvedHeaders),
|
|
47110
47546
|
...normalizeHeaders(options.headers)
|
|
@@ -48384,7 +48820,7 @@ var init_dist8 = __esm(() => {
|
|
|
48384
48820
|
__export2(output_exports, {
|
|
48385
48821
|
array: () => array2,
|
|
48386
48822
|
choice: () => choice,
|
|
48387
|
-
json: () =>
|
|
48823
|
+
json: () => json3,
|
|
48388
48824
|
object: () => object2,
|
|
48389
48825
|
text: () => text
|
|
48390
48826
|
});
|
|
@@ -50024,6 +50460,651 @@ function getMemoryVersions(memoryId, db) {
|
|
|
50024
50460
|
return [];
|
|
50025
50461
|
}
|
|
50026
50462
|
}
|
|
50463
|
+
// src/db/memory-project-link.ts
|
|
50464
|
+
init_storage();
|
|
50465
|
+
init_api_mode();
|
|
50466
|
+
init_database();
|
|
50467
|
+
import { createHash } from "crypto";
|
|
50468
|
+
|
|
50469
|
+
// src/lib/package-version.ts
|
|
50470
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
50471
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
50472
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
50473
|
+
function getMementosPackageVersion() {
|
|
50474
|
+
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
50475
|
+
for (const candidate of [
|
|
50476
|
+
join4(here, "..", "..", "package.json"),
|
|
50477
|
+
join4(here, "..", "package.json")
|
|
50478
|
+
]) {
|
|
50479
|
+
try {
|
|
50480
|
+
const parsed = JSON.parse(readFileSync2(candidate, "utf8"));
|
|
50481
|
+
if (typeof parsed.version === "string" && parsed.version.trim())
|
|
50482
|
+
return parsed.version;
|
|
50483
|
+
} catch {}
|
|
50484
|
+
}
|
|
50485
|
+
return "0.0.0";
|
|
50486
|
+
}
|
|
50487
|
+
|
|
50488
|
+
// src/db/memory-project-link.ts
|
|
50489
|
+
init_schema();
|
|
50490
|
+
|
|
50491
|
+
class MemoryProjectLinkError extends Error {
|
|
50492
|
+
code;
|
|
50493
|
+
details;
|
|
50494
|
+
constructor(code, message, details = {}) {
|
|
50495
|
+
super(message);
|
|
50496
|
+
this.code = code;
|
|
50497
|
+
this.details = details;
|
|
50498
|
+
this.name = "MemoryProjectLinkError";
|
|
50499
|
+
}
|
|
50500
|
+
}
|
|
50501
|
+
var LINK_AUTHORITY = {
|
|
50502
|
+
authority_id: "mementos",
|
|
50503
|
+
tenant_id: "default",
|
|
50504
|
+
corpus_id: "default"
|
|
50505
|
+
};
|
|
50506
|
+
var BOUNDED_IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
|
|
50507
|
+
function canonicalize(value) {
|
|
50508
|
+
if (Array.isArray(value))
|
|
50509
|
+
return value.map(canonicalize);
|
|
50510
|
+
if (!value || typeof value !== "object")
|
|
50511
|
+
return value;
|
|
50512
|
+
const output = {};
|
|
50513
|
+
for (const key of Object.keys(value).sort()) {
|
|
50514
|
+
const item = value[key];
|
|
50515
|
+
if (item !== undefined)
|
|
50516
|
+
output[key] = canonicalize(item);
|
|
50517
|
+
}
|
|
50518
|
+
return output;
|
|
50519
|
+
}
|
|
50520
|
+
function canonicalJson(value) {
|
|
50521
|
+
return JSON.stringify(canonicalize(value));
|
|
50522
|
+
}
|
|
50523
|
+
function digest(value) {
|
|
50524
|
+
return createHash("sha256").update(canonicalJson(value)).digest("hex");
|
|
50525
|
+
}
|
|
50526
|
+
function timestampString(value) {
|
|
50527
|
+
return value instanceof Date ? value.toISOString() : String(value);
|
|
50528
|
+
}
|
|
50529
|
+
function nullableTimestamp(value) {
|
|
50530
|
+
return value === null || value === undefined ? null : timestampString(value);
|
|
50531
|
+
}
|
|
50532
|
+
function normalizeMemoryTimestamps(memory) {
|
|
50533
|
+
return {
|
|
50534
|
+
...memory,
|
|
50535
|
+
created_at: timestampString(memory.created_at),
|
|
50536
|
+
updated_at: timestampString(memory.updated_at),
|
|
50537
|
+
accessed_at: nullableTimestamp(memory.accessed_at),
|
|
50538
|
+
expires_at: nullableTimestamp(memory.expires_at),
|
|
50539
|
+
valid_from: nullableTimestamp(memory.valid_from),
|
|
50540
|
+
valid_until: nullableTimestamp(memory.valid_until),
|
|
50541
|
+
ingested_at: nullableTimestamp(memory.ingested_at)
|
|
50542
|
+
};
|
|
50543
|
+
}
|
|
50544
|
+
function parseProjectRow(row) {
|
|
50545
|
+
return {
|
|
50546
|
+
id: String(row["id"]),
|
|
50547
|
+
name: String(row["name"]),
|
|
50548
|
+
path: String(row["path"]),
|
|
50549
|
+
description: row["description"] === null ? null : String(row["description"] ?? "") || null,
|
|
50550
|
+
memory_prefix: row["memory_prefix"] === null ? null : String(row["memory_prefix"] ?? "") || null,
|
|
50551
|
+
created_at: timestampString(row["created_at"]),
|
|
50552
|
+
updated_at: timestampString(row["updated_at"])
|
|
50553
|
+
};
|
|
50554
|
+
}
|
|
50555
|
+
function memoryMutationState(memory) {
|
|
50556
|
+
const { access_count: _accessCount, accessed_at: _accessedAt, ...state } = memory;
|
|
50557
|
+
return state;
|
|
50558
|
+
}
|
|
50559
|
+
function memoryDigest(memory) {
|
|
50560
|
+
return digest(memoryMutationState(memory));
|
|
50561
|
+
}
|
|
50562
|
+
function projectDigest(project) {
|
|
50563
|
+
return digest(project);
|
|
50564
|
+
}
|
|
50565
|
+
function snapshot(memory) {
|
|
50566
|
+
return {
|
|
50567
|
+
memory_id: memory.id,
|
|
50568
|
+
project_id: memory.project_id,
|
|
50569
|
+
memory_version: memory.version,
|
|
50570
|
+
memory_revision: timestampString(memory.updated_at),
|
|
50571
|
+
memory_digest: memoryDigest(memory)
|
|
50572
|
+
};
|
|
50573
|
+
}
|
|
50574
|
+
function parseSnapshot(value) {
|
|
50575
|
+
const parsed = typeof value === "string" ? JSON.parse(value) : value;
|
|
50576
|
+
return parsed;
|
|
50577
|
+
}
|
|
50578
|
+
function receiptFromRow(row) {
|
|
50579
|
+
return {
|
|
50580
|
+
receipt_id: String(row["receipt_id"]),
|
|
50581
|
+
authority: "mementos",
|
|
50582
|
+
route: MEMENTOS_MEMORY_PROJECT_LINK_ROUTE,
|
|
50583
|
+
package_version: String(row["package_version"]),
|
|
50584
|
+
authority_id: String(row["authority_id"]),
|
|
50585
|
+
tenant_id: String(row["tenant_id"]),
|
|
50586
|
+
corpus_id: String(row["corpus_id"]),
|
|
50587
|
+
operation_id: String(row["operation_id"]),
|
|
50588
|
+
step_id: String(row["step_id"]),
|
|
50589
|
+
direction: row["direction"],
|
|
50590
|
+
idempotency_key: String(row["idempotency_key"]),
|
|
50591
|
+
request_digest: String(row["request_digest"]),
|
|
50592
|
+
outcome: row["outcome"],
|
|
50593
|
+
target_memory_id: String(row["target_memory_id"]),
|
|
50594
|
+
requested_project_id: String(row["requested_project_id"]),
|
|
50595
|
+
expected_memory_version: Number(row["expected_memory_version"]),
|
|
50596
|
+
expected_memory_revision: timestampString(row["expected_memory_revision"]),
|
|
50597
|
+
expected_project_revision: nullableTimestamp(row["expected_project_revision"]),
|
|
50598
|
+
result_memory_version: Number(row["result_memory_version"]),
|
|
50599
|
+
result_memory_revision: timestampString(row["result_memory_revision"]),
|
|
50600
|
+
result_memory_digest: String(row["result_memory_digest"]),
|
|
50601
|
+
result_project_revision: nullableTimestamp(row["result_project_revision"]),
|
|
50602
|
+
result_project_digest: row["result_project_digest"] === null ? null : String(row["result_project_digest"]),
|
|
50603
|
+
accepted_receipt_id: row["accepted_receipt_id"] === null ? null : String(row["accepted_receipt_id"]),
|
|
50604
|
+
before_link: parseSnapshot(row["before_link_json"]),
|
|
50605
|
+
after_link: parseSnapshot(row["after_link_json"]),
|
|
50606
|
+
before_project_revision: nullableTimestamp(row["before_project_revision"]),
|
|
50607
|
+
before_project_digest: row["before_project_digest"] === null ? null : String(row["before_project_digest"]),
|
|
50608
|
+
after_project_revision: nullableTimestamp(row["after_project_revision"]),
|
|
50609
|
+
after_project_digest: row["after_project_digest"] === null ? null : String(row["after_project_digest"]),
|
|
50610
|
+
created_at: timestampString(row["created_at"])
|
|
50611
|
+
};
|
|
50612
|
+
}
|
|
50613
|
+
function assertIdentity(identity) {
|
|
50614
|
+
if (identity.authority_id !== LINK_AUTHORITY.authority_id || identity.tenant_id !== LINK_AUTHORITY.tenant_id || identity.corpus_id !== LINK_AUTHORITY.corpus_id) {
|
|
50615
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_AUTHORITY_MISMATCH", "memory project link does not match this authority, tenant, and corpus");
|
|
50616
|
+
}
|
|
50617
|
+
}
|
|
50618
|
+
function assertBoundedIdentifier(value, field) {
|
|
50619
|
+
if (!BOUNDED_IDENTIFIER.test(value)) {
|
|
50620
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_INVALID_INPUT", `${field} must be an 8-128 character bounded identifier`);
|
|
50621
|
+
}
|
|
50622
|
+
}
|
|
50623
|
+
function assertMemoryGuard(version, revision) {
|
|
50624
|
+
if (!Number.isSafeInteger(version) || version < 1) {
|
|
50625
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_INVALID_INPUT", "expected_memory_version must be a positive safe integer");
|
|
50626
|
+
}
|
|
50627
|
+
if (!revision || revision.length > 128) {
|
|
50628
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_INVALID_INPUT", "expected_memory_revision is required and must be bounded");
|
|
50629
|
+
}
|
|
50630
|
+
}
|
|
50631
|
+
function assertForwardRequest(request) {
|
|
50632
|
+
assertIdentity(request);
|
|
50633
|
+
assertBoundedIdentifier(request.operation_id, "operation_id");
|
|
50634
|
+
assertBoundedIdentifier(request.step_id, "step_id");
|
|
50635
|
+
assertBoundedIdentifier(request.idempotency_key, "idempotency_key");
|
|
50636
|
+
assertBoundedIdentifier(request.target_project_id, "target_project_id");
|
|
50637
|
+
assertMemoryGuard(request.expected_memory_version, request.expected_memory_revision);
|
|
50638
|
+
if (!request.expected_project_revision || request.expected_project_revision.length > 128) {
|
|
50639
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_INVALID_INPUT", "expected_project_revision is required and must be bounded");
|
|
50640
|
+
}
|
|
50641
|
+
}
|
|
50642
|
+
function assertRollbackRequest(request) {
|
|
50643
|
+
assertIdentity(request);
|
|
50644
|
+
assertBoundedIdentifier(request.operation_id, "operation_id");
|
|
50645
|
+
assertBoundedIdentifier(request.step_id, "step_id");
|
|
50646
|
+
assertBoundedIdentifier(request.idempotency_key, "idempotency_key");
|
|
50647
|
+
assertBoundedIdentifier(request.accepted_receipt_id, "accepted_receipt_id");
|
|
50648
|
+
assertMemoryGuard(request.expected_memory_version, request.expected_memory_revision);
|
|
50649
|
+
}
|
|
50650
|
+
function forwardRequestDigest(memoryId, request) {
|
|
50651
|
+
return digest({
|
|
50652
|
+
authority_id: request.authority_id,
|
|
50653
|
+
tenant_id: request.tenant_id,
|
|
50654
|
+
corpus_id: request.corpus_id,
|
|
50655
|
+
operation_id: request.operation_id,
|
|
50656
|
+
step_id: request.step_id,
|
|
50657
|
+
idempotency_key: request.idempotency_key,
|
|
50658
|
+
expected_memory_version: request.expected_memory_version,
|
|
50659
|
+
expected_memory_revision: request.expected_memory_revision,
|
|
50660
|
+
target_project_id: request.target_project_id,
|
|
50661
|
+
expected_project_revision: request.expected_project_revision,
|
|
50662
|
+
direction: "forward",
|
|
50663
|
+
target_memory_id: memoryId
|
|
50664
|
+
});
|
|
50665
|
+
}
|
|
50666
|
+
function rollbackRequestDigest(memoryId, request) {
|
|
50667
|
+
return digest({
|
|
50668
|
+
authority_id: request.authority_id,
|
|
50669
|
+
tenant_id: request.tenant_id,
|
|
50670
|
+
corpus_id: request.corpus_id,
|
|
50671
|
+
operation_id: request.operation_id,
|
|
50672
|
+
step_id: request.step_id,
|
|
50673
|
+
idempotency_key: request.idempotency_key,
|
|
50674
|
+
expected_memory_version: request.expected_memory_version,
|
|
50675
|
+
expected_memory_revision: request.expected_memory_revision,
|
|
50676
|
+
accepted_receipt_id: request.accepted_receipt_id,
|
|
50677
|
+
direction: "rollback",
|
|
50678
|
+
target_memory_id: memoryId
|
|
50679
|
+
});
|
|
50680
|
+
}
|
|
50681
|
+
function forUpdateSuffix(db, lock) {
|
|
50682
|
+
return lock && db instanceof PgAdapter ? " FOR UPDATE" : "";
|
|
50683
|
+
}
|
|
50684
|
+
function getMemoryByExactId(id, db, lock = false) {
|
|
50685
|
+
const row = db.query(`SELECT * FROM memories WHERE id = ? LIMIT 1${forUpdateSuffix(db, lock)}`).get(id);
|
|
50686
|
+
return row ? normalizeMemoryTimestamps(parseMemoryRow(row)) : null;
|
|
50687
|
+
}
|
|
50688
|
+
function getProjectByExactId(id, db, lock = false) {
|
|
50689
|
+
const row = db.query(`SELECT * FROM projects WHERE id = ? LIMIT 1${forUpdateSuffix(db, lock)}`).get(id);
|
|
50690
|
+
return row ? parseProjectRow(row) : null;
|
|
50691
|
+
}
|
|
50692
|
+
function lockProjects(ids, db) {
|
|
50693
|
+
const projects = new Map;
|
|
50694
|
+
const uniqueIds = [...new Set(ids.filter((id) => Boolean(id)))].sort();
|
|
50695
|
+
for (const id of uniqueIds) {
|
|
50696
|
+
const project = getProjectByExactId(id, db, true);
|
|
50697
|
+
if (!project) {
|
|
50698
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_PROJECT_NOT_FOUND", `Project not found by exact stable ID: ${id}`);
|
|
50699
|
+
}
|
|
50700
|
+
projects.set(id, project);
|
|
50701
|
+
}
|
|
50702
|
+
return projects;
|
|
50703
|
+
}
|
|
50704
|
+
function assertMemoryPrecondition(memory, expectedVersion, expectedRevision) {
|
|
50705
|
+
if (memory.version !== expectedVersion || memory.updated_at !== expectedRevision) {
|
|
50706
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_STALE_MEMORY", "Memory changed before the guarded project link", {
|
|
50707
|
+
expected_memory_version: expectedVersion,
|
|
50708
|
+
current_memory_version: memory.version,
|
|
50709
|
+
expected_memory_revision: expectedRevision,
|
|
50710
|
+
current_memory_revision: memory.updated_at
|
|
50711
|
+
});
|
|
50712
|
+
}
|
|
50713
|
+
}
|
|
50714
|
+
function assertProjectPrecondition(project, expectedRevision) {
|
|
50715
|
+
if (project.updated_at !== expectedRevision) {
|
|
50716
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_STALE_PROJECT", "Project changed before the guarded memory link", {
|
|
50717
|
+
expected_project_revision: expectedRevision,
|
|
50718
|
+
current_project_revision: project.updated_at
|
|
50719
|
+
});
|
|
50720
|
+
}
|
|
50721
|
+
}
|
|
50722
|
+
function assertNoTargetBucketCollision(memory, projectId, db) {
|
|
50723
|
+
const collision = db.query(`
|
|
50724
|
+
SELECT id FROM memories
|
|
50725
|
+
WHERE id != ? AND key = ? AND scope = ?
|
|
50726
|
+
AND COALESCE(agent_id, '') = COALESCE(?, '')
|
|
50727
|
+
AND COALESCE(project_id, '') = COALESCE(?, '')
|
|
50728
|
+
AND COALESCE(session_id, '') = COALESCE(?, '')
|
|
50729
|
+
LIMIT 1
|
|
50730
|
+
`).get(memory.id, memory.key, memory.scope, memory.agent_id, projectId, memory.session_id);
|
|
50731
|
+
if (collision) {
|
|
50732
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_COLLISION", "Linking this memory would collide with another memory in the target project bucket", { collision_id: collision.id });
|
|
50733
|
+
}
|
|
50734
|
+
}
|
|
50735
|
+
function nextRevision(previous) {
|
|
50736
|
+
const candidate = now();
|
|
50737
|
+
const previousMs = Date.parse(previous);
|
|
50738
|
+
const candidateMs = Date.parse(candidate);
|
|
50739
|
+
if (Number.isFinite(previousMs) && Number.isFinite(candidateMs) && candidateMs <= previousMs) {
|
|
50740
|
+
return new Date(previousMs + 1).toISOString();
|
|
50741
|
+
}
|
|
50742
|
+
return candidate;
|
|
50743
|
+
}
|
|
50744
|
+
function findReceiptByKey(db, identity, direction, idempotencyKey) {
|
|
50745
|
+
const row = db.query(`
|
|
50746
|
+
SELECT * FROM mementos_memory_project_link_receipts
|
|
50747
|
+
WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
50748
|
+
AND direction = ? AND idempotency_key = ?
|
|
50749
|
+
LIMIT 1
|
|
50750
|
+
`).get(identity.authority_id, identity.tenant_id, identity.corpus_id, direction, idempotencyKey);
|
|
50751
|
+
return row ? receiptFromRow(row) : null;
|
|
50752
|
+
}
|
|
50753
|
+
function findReceiptById(db, identity, receiptId) {
|
|
50754
|
+
const row = db.query(`
|
|
50755
|
+
SELECT * FROM mementos_memory_project_link_receipts
|
|
50756
|
+
WHERE receipt_id = ? AND authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
50757
|
+
LIMIT 1
|
|
50758
|
+
`).get(receiptId, identity.authority_id, identity.tenant_id, identity.corpus_id);
|
|
50759
|
+
return row ? receiptFromRow(row) : null;
|
|
50760
|
+
}
|
|
50761
|
+
function insertReceipt(db, receipt) {
|
|
50762
|
+
db.run(`
|
|
50763
|
+
INSERT INTO mementos_memory_project_link_receipts (
|
|
50764
|
+
receipt_id, authority, route, package_version, authority_id, tenant_id,
|
|
50765
|
+
corpus_id, operation_id, step_id, direction, idempotency_key,
|
|
50766
|
+
request_digest, outcome, target_memory_id, requested_project_id,
|
|
50767
|
+
expected_memory_version, expected_memory_revision,
|
|
50768
|
+
expected_project_revision, result_memory_version,
|
|
50769
|
+
result_memory_revision, result_memory_digest, result_project_revision,
|
|
50770
|
+
result_project_digest, accepted_receipt_id, before_link_json,
|
|
50771
|
+
after_link_json, before_project_revision, before_project_digest,
|
|
50772
|
+
after_project_revision, after_project_digest, created_at
|
|
50773
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
50774
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
50775
|
+
`, [
|
|
50776
|
+
receipt.receipt_id,
|
|
50777
|
+
receipt.authority,
|
|
50778
|
+
receipt.route,
|
|
50779
|
+
receipt.package_version,
|
|
50780
|
+
receipt.authority_id,
|
|
50781
|
+
receipt.tenant_id,
|
|
50782
|
+
receipt.corpus_id,
|
|
50783
|
+
receipt.operation_id,
|
|
50784
|
+
receipt.step_id,
|
|
50785
|
+
receipt.direction,
|
|
50786
|
+
receipt.idempotency_key,
|
|
50787
|
+
receipt.request_digest,
|
|
50788
|
+
receipt.outcome,
|
|
50789
|
+
receipt.target_memory_id,
|
|
50790
|
+
receipt.requested_project_id,
|
|
50791
|
+
receipt.expected_memory_version,
|
|
50792
|
+
receipt.expected_memory_revision,
|
|
50793
|
+
receipt.expected_project_revision,
|
|
50794
|
+
receipt.result_memory_version,
|
|
50795
|
+
receipt.result_memory_revision,
|
|
50796
|
+
receipt.result_memory_digest,
|
|
50797
|
+
receipt.result_project_revision,
|
|
50798
|
+
receipt.result_project_digest,
|
|
50799
|
+
receipt.accepted_receipt_id,
|
|
50800
|
+
canonicalJson(receipt.before_link),
|
|
50801
|
+
canonicalJson(receipt.after_link),
|
|
50802
|
+
receipt.before_project_revision,
|
|
50803
|
+
receipt.before_project_digest,
|
|
50804
|
+
receipt.after_project_revision,
|
|
50805
|
+
receipt.after_project_digest,
|
|
50806
|
+
receipt.created_at
|
|
50807
|
+
]);
|
|
50808
|
+
}
|
|
50809
|
+
function makeReceipt(input) {
|
|
50810
|
+
const beforeLink = snapshot(input.beforeMemory);
|
|
50811
|
+
const afterLink = snapshot(input.afterMemory);
|
|
50812
|
+
const logical = {
|
|
50813
|
+
authority: "mementos",
|
|
50814
|
+
route: MEMENTOS_MEMORY_PROJECT_LINK_ROUTE,
|
|
50815
|
+
package_version: getMementosPackageVersion(),
|
|
50816
|
+
authority_id: input.request.authority_id,
|
|
50817
|
+
tenant_id: input.request.tenant_id,
|
|
50818
|
+
corpus_id: input.request.corpus_id,
|
|
50819
|
+
operation_id: input.request.operation_id,
|
|
50820
|
+
step_id: input.request.step_id,
|
|
50821
|
+
direction: input.direction,
|
|
50822
|
+
idempotency_key: input.request.idempotency_key,
|
|
50823
|
+
request_digest: input.requestDigest,
|
|
50824
|
+
outcome: input.outcome,
|
|
50825
|
+
target_memory_id: input.targetMemoryId,
|
|
50826
|
+
requested_project_id: input.requestedProjectId,
|
|
50827
|
+
expected_memory_version: input.request.expected_memory_version,
|
|
50828
|
+
expected_memory_revision: input.request.expected_memory_revision,
|
|
50829
|
+
expected_project_revision: input.expectedProjectRevision,
|
|
50830
|
+
result_memory_version: afterLink.memory_version,
|
|
50831
|
+
result_memory_revision: afterLink.memory_revision,
|
|
50832
|
+
result_memory_digest: afterLink.memory_digest,
|
|
50833
|
+
result_project_revision: input.afterProject?.updated_at ?? null,
|
|
50834
|
+
result_project_digest: input.afterProject ? projectDigest(input.afterProject) : null,
|
|
50835
|
+
accepted_receipt_id: input.acceptedReceiptId ?? null,
|
|
50836
|
+
before_link: beforeLink,
|
|
50837
|
+
after_link: afterLink,
|
|
50838
|
+
before_project_revision: input.beforeProject?.updated_at ?? null,
|
|
50839
|
+
before_project_digest: input.beforeProject ? projectDigest(input.beforeProject) : null,
|
|
50840
|
+
after_project_revision: input.afterProject?.updated_at ?? null,
|
|
50841
|
+
after_project_digest: input.afterProject ? projectDigest(input.afterProject) : null
|
|
50842
|
+
};
|
|
50843
|
+
return {
|
|
50844
|
+
receipt_id: `mmpl_${digest(logical).slice(0, 40)}`,
|
|
50845
|
+
...logical,
|
|
50846
|
+
created_at: now()
|
|
50847
|
+
};
|
|
50848
|
+
}
|
|
50849
|
+
function sameSnapshot(memory, expected) {
|
|
50850
|
+
return canonicalJson(snapshot(memory)) === canonicalJson(expected);
|
|
50851
|
+
}
|
|
50852
|
+
function assertProjectSnapshot(project, revision, expectedDigest) {
|
|
50853
|
+
if (!revision && !expectedDigest) {
|
|
50854
|
+
if (project) {
|
|
50855
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_ACCEPTED_TARGET_DRIFTED", "receipt expected no linked project but a project row was resolved");
|
|
50856
|
+
}
|
|
50857
|
+
return;
|
|
50858
|
+
}
|
|
50859
|
+
if (!project || project.updated_at !== revision || projectDigest(project) !== expectedDigest) {
|
|
50860
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_STALE_PROJECT", "Project no longer matches the immutable memory-link receipt", { expected_project_revision: revision, current_project_revision: project?.updated_at ?? null });
|
|
50861
|
+
}
|
|
50862
|
+
}
|
|
50863
|
+
function replayResult(db, receipt, requestDigest, memoryId) {
|
|
50864
|
+
if (receipt.request_digest !== requestDigest || receipt.target_memory_id !== memoryId) {
|
|
50865
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_IDEMPOTENCY_MISMATCH", "caller idempotency key is already bound to a different memory project-link request");
|
|
50866
|
+
}
|
|
50867
|
+
const memory = getMemoryByExactId(memoryId, db);
|
|
50868
|
+
if (!memory || !sameSnapshot(memory, receipt.after_link)) {
|
|
50869
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_ACCEPTED_TARGET_DRIFTED", "accepted memory project-link target drifted after its immutable receipt");
|
|
50870
|
+
}
|
|
50871
|
+
const project = receipt.after_link.project_id ? getProjectByExactId(receipt.after_link.project_id, db) : null;
|
|
50872
|
+
assertProjectSnapshot(project, receipt.after_project_revision, receipt.after_project_digest);
|
|
50873
|
+
return {
|
|
50874
|
+
dry_run: false,
|
|
50875
|
+
applied: receipt.outcome === "accepted",
|
|
50876
|
+
no_change: receipt.outcome === "no_change",
|
|
50877
|
+
memory,
|
|
50878
|
+
project,
|
|
50879
|
+
receipt
|
|
50880
|
+
};
|
|
50881
|
+
}
|
|
50882
|
+
function previewMemoryProjectLink(memoryId, request, db) {
|
|
50883
|
+
assertForwardRequest(request);
|
|
50884
|
+
assertBoundedIdentifier(memoryId, "memory_id");
|
|
50885
|
+
if (!db && isApiMode()) {
|
|
50886
|
+
const { data } = apiJson("POST", `/memories/${encodeURIComponent(memoryId)}/guarded-project-link`, { ...request, dry_run: true });
|
|
50887
|
+
return data;
|
|
50888
|
+
}
|
|
50889
|
+
const d = db || getDatabase();
|
|
50890
|
+
const memory = getMemoryByExactId(memoryId, d);
|
|
50891
|
+
if (!memory) {
|
|
50892
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_MEMORY_NOT_FOUND", `Memory not found by exact stable ID: ${memoryId}`);
|
|
50893
|
+
}
|
|
50894
|
+
const project = getProjectByExactId(request.target_project_id, d);
|
|
50895
|
+
if (!project) {
|
|
50896
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_PROJECT_NOT_FOUND", `Project not found by exact stable ID: ${request.target_project_id}`);
|
|
50897
|
+
}
|
|
50898
|
+
assertMemoryPrecondition(memory, request.expected_memory_version, request.expected_memory_revision);
|
|
50899
|
+
assertProjectPrecondition(project, request.expected_project_revision);
|
|
50900
|
+
const noChange = memory.project_id === project.id;
|
|
50901
|
+
if (!noChange)
|
|
50902
|
+
assertNoTargetBucketCollision(memory, project.id, d);
|
|
50903
|
+
return {
|
|
50904
|
+
dry_run: true,
|
|
50905
|
+
applied: false,
|
|
50906
|
+
no_change: noChange,
|
|
50907
|
+
memory: noChange ? memory : { ...memory, project_id: project.id },
|
|
50908
|
+
project,
|
|
50909
|
+
receipt: null
|
|
50910
|
+
};
|
|
50911
|
+
}
|
|
50912
|
+
function applyMemoryProjectLink(memoryId, request, db) {
|
|
50913
|
+
assertForwardRequest(request);
|
|
50914
|
+
assertBoundedIdentifier(memoryId, "memory_id");
|
|
50915
|
+
if (!db && isApiMode()) {
|
|
50916
|
+
const { data } = apiJson("POST", `/memories/${encodeURIComponent(memoryId)}/guarded-project-link`, { ...request, dry_run: false });
|
|
50917
|
+
return data;
|
|
50918
|
+
}
|
|
50919
|
+
const d = db || getDatabase();
|
|
50920
|
+
const requestDigest = forwardRequestDigest(memoryId, request);
|
|
50921
|
+
return d.transaction(() => {
|
|
50922
|
+
const prior = findReceiptByKey(d, request, "forward", request.idempotency_key);
|
|
50923
|
+
if (prior)
|
|
50924
|
+
return replayResult(d, prior, requestDigest, memoryId);
|
|
50925
|
+
const before = getMemoryByExactId(memoryId, d, true);
|
|
50926
|
+
if (!before) {
|
|
50927
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_MEMORY_NOT_FOUND", `Memory not found by exact stable ID: ${memoryId}`);
|
|
50928
|
+
}
|
|
50929
|
+
const committedPrior = findReceiptByKey(d, request, "forward", request.idempotency_key);
|
|
50930
|
+
if (committedPrior)
|
|
50931
|
+
return replayResult(d, committedPrior, requestDigest, memoryId);
|
|
50932
|
+
assertMemoryPrecondition(before, request.expected_memory_version, request.expected_memory_revision);
|
|
50933
|
+
const projects = lockProjects([before.project_id, request.target_project_id], d);
|
|
50934
|
+
const targetProject = projects.get(request.target_project_id);
|
|
50935
|
+
const beforeProject = before.project_id ? projects.get(before.project_id) : null;
|
|
50936
|
+
assertProjectPrecondition(targetProject, request.expected_project_revision);
|
|
50937
|
+
const noChange = before.project_id === request.target_project_id;
|
|
50938
|
+
if (noChange) {
|
|
50939
|
+
const receipt2 = makeReceipt({
|
|
50940
|
+
request,
|
|
50941
|
+
direction: "forward",
|
|
50942
|
+
requestDigest,
|
|
50943
|
+
outcome: "no_change",
|
|
50944
|
+
targetMemoryId: memoryId,
|
|
50945
|
+
requestedProjectId: request.target_project_id,
|
|
50946
|
+
expectedProjectRevision: request.expected_project_revision,
|
|
50947
|
+
beforeMemory: before,
|
|
50948
|
+
afterMemory: before,
|
|
50949
|
+
beforeProject,
|
|
50950
|
+
afterProject: targetProject
|
|
50951
|
+
});
|
|
50952
|
+
insertReceipt(d, receipt2);
|
|
50953
|
+
return {
|
|
50954
|
+
dry_run: false,
|
|
50955
|
+
applied: false,
|
|
50956
|
+
no_change: true,
|
|
50957
|
+
memory: before,
|
|
50958
|
+
project: targetProject,
|
|
50959
|
+
receipt: receipt2
|
|
50960
|
+
};
|
|
50961
|
+
}
|
|
50962
|
+
assertNoTargetBucketCollision(before, targetProject.id, d);
|
|
50963
|
+
const revision = nextRevision(before.updated_at);
|
|
50964
|
+
const result = d.run(`
|
|
50965
|
+
UPDATE memories
|
|
50966
|
+
SET project_id = ?, updated_at = ?
|
|
50967
|
+
WHERE id = ? AND version = ? AND updated_at = ?
|
|
50968
|
+
AND COALESCE(project_id, '') = COALESCE(?, '')
|
|
50969
|
+
`, [
|
|
50970
|
+
targetProject.id,
|
|
50971
|
+
revision,
|
|
50972
|
+
memoryId,
|
|
50973
|
+
before.version,
|
|
50974
|
+
before.updated_at,
|
|
50975
|
+
before.project_id
|
|
50976
|
+
]);
|
|
50977
|
+
if (result.changes === 0) {
|
|
50978
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_STALE_MEMORY", "Memory project-link compare-and-swap did not update exactly one row");
|
|
50979
|
+
}
|
|
50980
|
+
const after = getMemoryByExactId(memoryId, d);
|
|
50981
|
+
const expectedAfter = { ...before, project_id: targetProject.id, updated_at: revision };
|
|
50982
|
+
if (!after || canonicalJson(memoryMutationState(after)) !== canonicalJson(memoryMutationState(expectedAfter))) {
|
|
50983
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_ACCEPTED_TARGET_DRIFTED", "Memory project link did not read back exactly under the stable ID");
|
|
50984
|
+
}
|
|
50985
|
+
const receipt = makeReceipt({
|
|
50986
|
+
request,
|
|
50987
|
+
direction: "forward",
|
|
50988
|
+
requestDigest,
|
|
50989
|
+
outcome: "accepted",
|
|
50990
|
+
targetMemoryId: memoryId,
|
|
50991
|
+
requestedProjectId: request.target_project_id,
|
|
50992
|
+
expectedProjectRevision: request.expected_project_revision,
|
|
50993
|
+
beforeMemory: before,
|
|
50994
|
+
afterMemory: after,
|
|
50995
|
+
beforeProject,
|
|
50996
|
+
afterProject: targetProject
|
|
50997
|
+
});
|
|
50998
|
+
insertReceipt(d, receipt);
|
|
50999
|
+
return {
|
|
51000
|
+
dry_run: false,
|
|
51001
|
+
applied: true,
|
|
51002
|
+
no_change: false,
|
|
51003
|
+
memory: after,
|
|
51004
|
+
project: targetProject,
|
|
51005
|
+
receipt
|
|
51006
|
+
};
|
|
51007
|
+
});
|
|
51008
|
+
}
|
|
51009
|
+
function rollbackMemoryProjectLink(memoryId, request, db) {
|
|
51010
|
+
assertRollbackRequest(request);
|
|
51011
|
+
assertBoundedIdentifier(memoryId, "memory_id");
|
|
51012
|
+
if (!db && isApiMode()) {
|
|
51013
|
+
const { data } = apiJson("POST", `/memories/${encodeURIComponent(memoryId)}/guarded-project-link-rollback`, request);
|
|
51014
|
+
return data;
|
|
51015
|
+
}
|
|
51016
|
+
const d = db || getDatabase();
|
|
51017
|
+
const requestDigest = rollbackRequestDigest(memoryId, request);
|
|
51018
|
+
return d.transaction(() => {
|
|
51019
|
+
const prior = findReceiptByKey(d, request, "rollback", request.idempotency_key);
|
|
51020
|
+
if (prior)
|
|
51021
|
+
return replayResult(d, prior, requestDigest, memoryId);
|
|
51022
|
+
const accepted = findReceiptById(d, request, request.accepted_receipt_id);
|
|
51023
|
+
if (!accepted || accepted.direction !== "forward" || accepted.target_memory_id !== memoryId) {
|
|
51024
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_RECEIPT_NOT_FOUND", "accepted forward link receipt was not found for this exact memory");
|
|
51025
|
+
}
|
|
51026
|
+
if (accepted.outcome !== "accepted") {
|
|
51027
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_RECEIPT_NOT_ROLLBACKABLE", "a no-change memory project-link receipt has no mutation to roll back");
|
|
51028
|
+
}
|
|
51029
|
+
const current = getMemoryByExactId(memoryId, d, true);
|
|
51030
|
+
if (!current) {
|
|
51031
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_MEMORY_NOT_FOUND", `Memory not found by exact stable ID: ${memoryId}`);
|
|
51032
|
+
}
|
|
51033
|
+
const committedPrior = findReceiptByKey(d, request, "rollback", request.idempotency_key);
|
|
51034
|
+
if (committedPrior)
|
|
51035
|
+
return replayResult(d, committedPrior, requestDigest, memoryId);
|
|
51036
|
+
assertMemoryPrecondition(current, request.expected_memory_version, request.expected_memory_revision);
|
|
51037
|
+
if (!sameSnapshot(current, accepted.after_link)) {
|
|
51038
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_STALE_MEMORY", "Memory no longer matches the accepted forward link receipt");
|
|
51039
|
+
}
|
|
51040
|
+
const projects = lockProjects([accepted.after_link.project_id, accepted.before_link.project_id], d);
|
|
51041
|
+
const beforeProject = accepted.after_link.project_id ? projects.get(accepted.after_link.project_id) : null;
|
|
51042
|
+
const afterProject = accepted.before_link.project_id ? projects.get(accepted.before_link.project_id) : null;
|
|
51043
|
+
assertProjectSnapshot(beforeProject, accepted.after_project_revision, accepted.after_project_digest);
|
|
51044
|
+
assertProjectSnapshot(afterProject, accepted.before_project_revision, accepted.before_project_digest);
|
|
51045
|
+
assertNoTargetBucketCollision(current, accepted.before_link.project_id, d);
|
|
51046
|
+
const result = d.run(`
|
|
51047
|
+
UPDATE memories
|
|
51048
|
+
SET project_id = ?, updated_at = ?
|
|
51049
|
+
WHERE id = ? AND version = ? AND updated_at = ?
|
|
51050
|
+
AND COALESCE(project_id, '') = COALESCE(?, '')
|
|
51051
|
+
`, [
|
|
51052
|
+
accepted.before_link.project_id,
|
|
51053
|
+
accepted.before_link.memory_revision,
|
|
51054
|
+
memoryId,
|
|
51055
|
+
current.version,
|
|
51056
|
+
current.updated_at,
|
|
51057
|
+
current.project_id
|
|
51058
|
+
]);
|
|
51059
|
+
if (result.changes === 0) {
|
|
51060
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_STALE_MEMORY", "Memory project-link rollback compare-and-swap did not update exactly one row");
|
|
51061
|
+
}
|
|
51062
|
+
const restored = getMemoryByExactId(memoryId, d);
|
|
51063
|
+
if (!restored || !sameSnapshot(restored, accepted.before_link)) {
|
|
51064
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_ACCEPTED_TARGET_DRIFTED", "Memory project-link rollback did not restore the exact prior linkage");
|
|
51065
|
+
}
|
|
51066
|
+
const receipt = makeReceipt({
|
|
51067
|
+
request,
|
|
51068
|
+
direction: "rollback",
|
|
51069
|
+
requestDigest,
|
|
51070
|
+
outcome: "accepted",
|
|
51071
|
+
targetMemoryId: memoryId,
|
|
51072
|
+
requestedProjectId: accepted.requested_project_id,
|
|
51073
|
+
expectedProjectRevision: null,
|
|
51074
|
+
beforeMemory: current,
|
|
51075
|
+
afterMemory: restored,
|
|
51076
|
+
beforeProject,
|
|
51077
|
+
afterProject,
|
|
51078
|
+
acceptedReceiptId: accepted.receipt_id
|
|
51079
|
+
});
|
|
51080
|
+
insertReceipt(d, receipt);
|
|
51081
|
+
return {
|
|
51082
|
+
dry_run: false,
|
|
51083
|
+
applied: true,
|
|
51084
|
+
no_change: false,
|
|
51085
|
+
memory: restored,
|
|
51086
|
+
project: afterProject,
|
|
51087
|
+
receipt
|
|
51088
|
+
};
|
|
51089
|
+
});
|
|
51090
|
+
}
|
|
51091
|
+
function getMemoryProjectLinkReceipt(memoryId, receiptId, identity = LINK_AUTHORITY, db) {
|
|
51092
|
+
assertIdentity(identity);
|
|
51093
|
+
assertBoundedIdentifier(memoryId, "memory_id");
|
|
51094
|
+
assertBoundedIdentifier(receiptId, "receipt_id");
|
|
51095
|
+
if (!db && isApiMode()) {
|
|
51096
|
+
const { data } = apiJson("POST", `/memories/${encodeURIComponent(memoryId)}/project-link-receipts/lookup`, { ...identity, receipt_id: receiptId });
|
|
51097
|
+
return data;
|
|
51098
|
+
}
|
|
51099
|
+
const d = db || getDatabase();
|
|
51100
|
+
const receipt = findReceiptById(d, identity, receiptId);
|
|
51101
|
+
if (!receipt || receipt.target_memory_id !== memoryId) {
|
|
51102
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_RECEIPT_NOT_FOUND", "immutable memory project-link receipt was not found for this exact memory");
|
|
51103
|
+
}
|
|
51104
|
+
return receipt;
|
|
51105
|
+
}
|
|
51106
|
+
// src/memory-project-link/index.ts
|
|
51107
|
+
init_schema();
|
|
50027
51108
|
// src/db/agents.ts
|
|
50028
51109
|
init_database();
|
|
50029
51110
|
init_api_mode();
|
|
@@ -50471,7 +51552,27 @@ function focusFilterSQL(agentId, projectId) {
|
|
|
50471
51552
|
// src/db/projects.ts
|
|
50472
51553
|
init_database();
|
|
50473
51554
|
init_api_mode();
|
|
50474
|
-
|
|
51555
|
+
import { createHash as createHash2 } from "crypto";
|
|
51556
|
+
|
|
51557
|
+
// src/project-registration/types.ts
|
|
51558
|
+
var MEMENTOS_PROJECT_REGISTRATION_ROUTE = "mementos.project-registration.v1";
|
|
51559
|
+
var MEMENTOS_PROJECT_REGISTRATION_CALLER_ROUTE = "projects.full-registration.v1";
|
|
51560
|
+
var MEMENTOS_PROJECT_REGISTRATION_SCHEMA_VERSION = 1;
|
|
51561
|
+
var MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE = "mementos.project-guarded-update.v1";
|
|
51562
|
+
|
|
51563
|
+
class MementosProjectRegistrationError extends Error {
|
|
51564
|
+
code;
|
|
51565
|
+
details;
|
|
51566
|
+
constructor(code, message, details = {}) {
|
|
51567
|
+
super(message);
|
|
51568
|
+
this.code = code;
|
|
51569
|
+
this.details = details;
|
|
51570
|
+
this.name = "MementosProjectRegistrationError";
|
|
51571
|
+
}
|
|
51572
|
+
}
|
|
51573
|
+
|
|
51574
|
+
// src/db/projects.ts
|
|
51575
|
+
function parseProjectRow2(row) {
|
|
50475
51576
|
return {
|
|
50476
51577
|
id: row["id"],
|
|
50477
51578
|
name: row["name"],
|
|
@@ -50482,6 +51583,241 @@ function parseProjectRow(row) {
|
|
|
50482
51583
|
updated_at: row["updated_at"]
|
|
50483
51584
|
};
|
|
50484
51585
|
}
|
|
51586
|
+
|
|
51587
|
+
class ProjectCollisionError extends Error {
|
|
51588
|
+
field;
|
|
51589
|
+
value;
|
|
51590
|
+
constructor(field, value) {
|
|
51591
|
+
super(`Project ${field} already exists: ${value}`);
|
|
51592
|
+
this.field = field;
|
|
51593
|
+
this.value = value;
|
|
51594
|
+
this.name = "ProjectCollisionError";
|
|
51595
|
+
}
|
|
51596
|
+
}
|
|
51597
|
+
|
|
51598
|
+
class ProjectGuardedUpdateError extends Error {
|
|
51599
|
+
code;
|
|
51600
|
+
details;
|
|
51601
|
+
constructor(code, message, details = {}) {
|
|
51602
|
+
super(message);
|
|
51603
|
+
this.code = code;
|
|
51604
|
+
this.details = details;
|
|
51605
|
+
this.name = "ProjectGuardedUpdateError";
|
|
51606
|
+
}
|
|
51607
|
+
}
|
|
51608
|
+
var PROJECT_UPDATE_AUTHORITY = {
|
|
51609
|
+
authority_id: "mementos",
|
|
51610
|
+
tenant_id: "default",
|
|
51611
|
+
corpus_id: "default"
|
|
51612
|
+
};
|
|
51613
|
+
var BOUNDED_IDENTIFIER2 = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
|
|
51614
|
+
function canonicalizeProjectUpdateValue(value) {
|
|
51615
|
+
if (Array.isArray(value))
|
|
51616
|
+
return value.map(canonicalizeProjectUpdateValue);
|
|
51617
|
+
if (!value || typeof value !== "object")
|
|
51618
|
+
return value;
|
|
51619
|
+
const output = {};
|
|
51620
|
+
for (const key of Object.keys(value).sort()) {
|
|
51621
|
+
const item = value[key];
|
|
51622
|
+
if (item !== undefined)
|
|
51623
|
+
output[key] = canonicalizeProjectUpdateValue(item);
|
|
51624
|
+
}
|
|
51625
|
+
return output;
|
|
51626
|
+
}
|
|
51627
|
+
function canonicalProjectUpdateJson(value) {
|
|
51628
|
+
return JSON.stringify(canonicalizeProjectUpdateValue(value));
|
|
51629
|
+
}
|
|
51630
|
+
function digestProjectUpdateValue(value) {
|
|
51631
|
+
return createHash2("sha256").update(canonicalProjectUpdateJson(value)).digest("hex");
|
|
51632
|
+
}
|
|
51633
|
+
function timestampString2(value) {
|
|
51634
|
+
return value instanceof Date ? value.toISOString() : String(value);
|
|
51635
|
+
}
|
|
51636
|
+
function parseProjectJson(value) {
|
|
51637
|
+
const parsed = typeof value === "string" ? JSON.parse(value) : value;
|
|
51638
|
+
return parsed;
|
|
51639
|
+
}
|
|
51640
|
+
function projectUpdateReceiptFromRow(row) {
|
|
51641
|
+
return {
|
|
51642
|
+
receipt_id: String(row["receipt_id"]),
|
|
51643
|
+
authority: "mementos",
|
|
51644
|
+
route: MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE,
|
|
51645
|
+
package_version: String(row["package_version"]),
|
|
51646
|
+
authority_id: String(row["authority_id"]),
|
|
51647
|
+
tenant_id: String(row["tenant_id"]),
|
|
51648
|
+
corpus_id: String(row["corpus_id"]),
|
|
51649
|
+
operation_id: String(row["operation_id"]),
|
|
51650
|
+
step_id: String(row["step_id"]),
|
|
51651
|
+
direction: row["direction"],
|
|
51652
|
+
idempotency_key: String(row["idempotency_key"]),
|
|
51653
|
+
request_digest: String(row["request_digest"]),
|
|
51654
|
+
outcome: "accepted",
|
|
51655
|
+
target_id: String(row["target_id"]),
|
|
51656
|
+
expected_revision: timestampString2(row["expected_revision"]),
|
|
51657
|
+
result_revision: timestampString2(row["result_revision"]),
|
|
51658
|
+
result_digest: String(row["result_digest"]),
|
|
51659
|
+
accepted_receipt_id: row["accepted_receipt_id"] === null ? null : String(row["accepted_receipt_id"]),
|
|
51660
|
+
before_project: parseProjectJson(row["before_project_json"]),
|
|
51661
|
+
after_project: parseProjectJson(row["after_project_json"]),
|
|
51662
|
+
created_at: timestampString2(row["created_at"])
|
|
51663
|
+
};
|
|
51664
|
+
}
|
|
51665
|
+
function normalizeProjectUpdateInput(input) {
|
|
51666
|
+
const normalized = {};
|
|
51667
|
+
if (input.name !== undefined)
|
|
51668
|
+
normalized.name = input.name.trim();
|
|
51669
|
+
if (input.path !== undefined)
|
|
51670
|
+
normalized.path = input.path.trim();
|
|
51671
|
+
if (input.description !== undefined)
|
|
51672
|
+
normalized.description = input.description;
|
|
51673
|
+
if (input.memory_prefix !== undefined)
|
|
51674
|
+
normalized.memory_prefix = input.memory_prefix;
|
|
51675
|
+
if (Object.keys(normalized).length === 0) {
|
|
51676
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_INVALID_INPUT", "At least one project field must be provided");
|
|
51677
|
+
}
|
|
51678
|
+
if (normalized.name !== undefined && normalized.name.length === 0) {
|
|
51679
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_INVALID_INPUT", "Project name cannot be empty");
|
|
51680
|
+
}
|
|
51681
|
+
if (normalized.path !== undefined && normalized.path.length === 0) {
|
|
51682
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_INVALID_INPUT", "Project path cannot be empty");
|
|
51683
|
+
}
|
|
51684
|
+
return normalized;
|
|
51685
|
+
}
|
|
51686
|
+
function assertProjectUpdateIdentity(identity) {
|
|
51687
|
+
if (identity.authority_id !== PROJECT_UPDATE_AUTHORITY.authority_id || identity.tenant_id !== PROJECT_UPDATE_AUTHORITY.tenant_id || identity.corpus_id !== PROJECT_UPDATE_AUTHORITY.corpus_id) {
|
|
51688
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_AUTHORITY_MISMATCH", "guarded project update does not match this authority, tenant, and corpus");
|
|
51689
|
+
}
|
|
51690
|
+
}
|
|
51691
|
+
function assertBoundedIdentifier2(value, field) {
|
|
51692
|
+
if (!BOUNDED_IDENTIFIER2.test(value)) {
|
|
51693
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_INVALID_INPUT", `${field} must be an 8-128 character bounded identifier`);
|
|
51694
|
+
}
|
|
51695
|
+
}
|
|
51696
|
+
function assertProjectUpdateRequest(request) {
|
|
51697
|
+
assertProjectUpdateIdentity(request);
|
|
51698
|
+
assertBoundedIdentifier2(request.operation_id, "operation_id");
|
|
51699
|
+
assertBoundedIdentifier2(request.step_id, "step_id");
|
|
51700
|
+
assertBoundedIdentifier2(request.idempotency_key, "idempotency_key");
|
|
51701
|
+
if (!request.expected_revision || request.expected_revision.length > 128) {
|
|
51702
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_INVALID_INPUT", "expected_revision is required and must be bounded");
|
|
51703
|
+
}
|
|
51704
|
+
}
|
|
51705
|
+
function getProjectByExactId2(id, db) {
|
|
51706
|
+
const row = db.query("SELECT * FROM projects WHERE id = ? LIMIT 1").get(id);
|
|
51707
|
+
return row ? parseProjectRow2(row) : null;
|
|
51708
|
+
}
|
|
51709
|
+
function assertNoProjectCollision(id, input, db) {
|
|
51710
|
+
if (input.name !== undefined) {
|
|
51711
|
+
const collision = db.query("SELECT id FROM projects WHERE LOWER(name) = LOWER(?) AND id != ? LIMIT 1").get(input.name, id);
|
|
51712
|
+
if (collision) {
|
|
51713
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_COLLISION", `Project name already exists: ${input.name}`, { field: "name" });
|
|
51714
|
+
}
|
|
51715
|
+
}
|
|
51716
|
+
if (input.path !== undefined) {
|
|
51717
|
+
const collision = db.query("SELECT id FROM projects WHERE path = ? AND id != ? LIMIT 1").get(input.path, id);
|
|
51718
|
+
if (collision) {
|
|
51719
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_COLLISION", `Project path already exists: ${input.path}`, { field: "path" });
|
|
51720
|
+
}
|
|
51721
|
+
}
|
|
51722
|
+
}
|
|
51723
|
+
function nextProjectRevision(previous) {
|
|
51724
|
+
const candidate = now();
|
|
51725
|
+
const previousMs = Date.parse(previous);
|
|
51726
|
+
const candidateMs = Date.parse(candidate);
|
|
51727
|
+
if (Number.isFinite(previousMs) && Number.isFinite(candidateMs) && candidateMs <= previousMs) {
|
|
51728
|
+
return new Date(previousMs + 1).toISOString();
|
|
51729
|
+
}
|
|
51730
|
+
return candidate;
|
|
51731
|
+
}
|
|
51732
|
+
function projectWithUpdates(project, updates, revision) {
|
|
51733
|
+
return {
|
|
51734
|
+
...project,
|
|
51735
|
+
...updates.name !== undefined ? { name: updates.name } : {},
|
|
51736
|
+
...updates.path !== undefined ? { path: updates.path } : {},
|
|
51737
|
+
...updates.description !== undefined ? { description: updates.description } : {},
|
|
51738
|
+
...updates.memory_prefix !== undefined ? { memory_prefix: updates.memory_prefix } : {},
|
|
51739
|
+
updated_at: revision
|
|
51740
|
+
};
|
|
51741
|
+
}
|
|
51742
|
+
function findProjectUpdateReceiptByKey(db, identity, direction, idempotencyKey) {
|
|
51743
|
+
const row = db.query(`
|
|
51744
|
+
SELECT * FROM mementos_project_update_receipts
|
|
51745
|
+
WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
51746
|
+
AND direction = ? AND idempotency_key = ?
|
|
51747
|
+
LIMIT 1
|
|
51748
|
+
`).get(identity.authority_id, identity.tenant_id, identity.corpus_id, direction, idempotencyKey);
|
|
51749
|
+
return row ? projectUpdateReceiptFromRow(row) : null;
|
|
51750
|
+
}
|
|
51751
|
+
function findProjectUpdateReceiptById(db, identity, receiptId) {
|
|
51752
|
+
const row = db.query(`
|
|
51753
|
+
SELECT * FROM mementos_project_update_receipts
|
|
51754
|
+
WHERE receipt_id = ? AND authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
51755
|
+
LIMIT 1
|
|
51756
|
+
`).get(receiptId, identity.authority_id, identity.tenant_id, identity.corpus_id);
|
|
51757
|
+
return row ? projectUpdateReceiptFromRow(row) : null;
|
|
51758
|
+
}
|
|
51759
|
+
function insertProjectUpdateReceipt(db, receipt) {
|
|
51760
|
+
db.run(`
|
|
51761
|
+
INSERT INTO mementos_project_update_receipts (
|
|
51762
|
+
receipt_id, authority, route, package_version, authority_id, tenant_id,
|
|
51763
|
+
corpus_id, operation_id, step_id, direction, idempotency_key,
|
|
51764
|
+
request_digest, outcome, target_id, expected_revision, result_revision,
|
|
51765
|
+
result_digest, accepted_receipt_id, before_project_json,
|
|
51766
|
+
after_project_json, created_at
|
|
51767
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
51768
|
+
`, [
|
|
51769
|
+
receipt.receipt_id,
|
|
51770
|
+
receipt.authority,
|
|
51771
|
+
receipt.route,
|
|
51772
|
+
receipt.package_version,
|
|
51773
|
+
receipt.authority_id,
|
|
51774
|
+
receipt.tenant_id,
|
|
51775
|
+
receipt.corpus_id,
|
|
51776
|
+
receipt.operation_id,
|
|
51777
|
+
receipt.step_id,
|
|
51778
|
+
receipt.direction,
|
|
51779
|
+
receipt.idempotency_key,
|
|
51780
|
+
receipt.request_digest,
|
|
51781
|
+
receipt.outcome,
|
|
51782
|
+
receipt.target_id,
|
|
51783
|
+
receipt.expected_revision,
|
|
51784
|
+
receipt.result_revision,
|
|
51785
|
+
receipt.result_digest,
|
|
51786
|
+
receipt.accepted_receipt_id,
|
|
51787
|
+
canonicalProjectUpdateJson(receipt.before_project),
|
|
51788
|
+
canonicalProjectUpdateJson(receipt.after_project),
|
|
51789
|
+
receipt.created_at
|
|
51790
|
+
]);
|
|
51791
|
+
}
|
|
51792
|
+
function makeProjectUpdateReceipt(input) {
|
|
51793
|
+
const createdAt = now();
|
|
51794
|
+
const logical = {
|
|
51795
|
+
authority: "mementos",
|
|
51796
|
+
route: MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE,
|
|
51797
|
+
package_version: getMementosPackageVersion(),
|
|
51798
|
+
authority_id: input.request.authority_id,
|
|
51799
|
+
tenant_id: input.request.tenant_id,
|
|
51800
|
+
corpus_id: input.request.corpus_id,
|
|
51801
|
+
operation_id: input.request.operation_id,
|
|
51802
|
+
step_id: input.request.step_id,
|
|
51803
|
+
direction: input.direction,
|
|
51804
|
+
idempotency_key: input.request.idempotency_key,
|
|
51805
|
+
request_digest: input.request_digest,
|
|
51806
|
+
outcome: "accepted",
|
|
51807
|
+
target_id: input.target_id,
|
|
51808
|
+
expected_revision: input.request.expected_revision,
|
|
51809
|
+
result_revision: input.after_project.updated_at,
|
|
51810
|
+
result_digest: digestProjectUpdateValue(input.after_project),
|
|
51811
|
+
accepted_receipt_id: input.accepted_receipt_id ?? null,
|
|
51812
|
+
before_project: input.before_project,
|
|
51813
|
+
after_project: input.after_project,
|
|
51814
|
+
created_at: createdAt
|
|
51815
|
+
};
|
|
51816
|
+
return {
|
|
51817
|
+
receipt_id: `mpur_${digestProjectUpdateValue(logical).slice(0, 40)}`,
|
|
51818
|
+
...logical
|
|
51819
|
+
};
|
|
51820
|
+
}
|
|
50485
51821
|
function registerProject(name, path, description, memoryPrefix, db) {
|
|
50486
51822
|
if (!db && isApiMode()) {
|
|
50487
51823
|
const { data } = apiJson("POST", "/projects", {
|
|
@@ -50501,7 +51837,7 @@ function registerProject(name, path, description, memoryPrefix, db) {
|
|
|
50501
51837
|
timestamp,
|
|
50502
51838
|
existingId
|
|
50503
51839
|
]);
|
|
50504
|
-
return
|
|
51840
|
+
return parseProjectRow2(existing);
|
|
50505
51841
|
}
|
|
50506
51842
|
const id = uuid();
|
|
50507
51843
|
d.run("INSERT INTO projects (id, name, path, description, memory_prefix, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)", [id, name, path, description || null, memoryPrefix || null, timestamp, timestamp]);
|
|
@@ -50517,13 +51853,13 @@ function getProject(idOrPath, db) {
|
|
|
50517
51853
|
const d = db || getDatabase();
|
|
50518
51854
|
let row = d.query("SELECT * FROM projects WHERE id = ?").get(idOrPath);
|
|
50519
51855
|
if (row)
|
|
50520
|
-
return
|
|
51856
|
+
return parseProjectRow2(row);
|
|
50521
51857
|
row = d.query("SELECT * FROM projects WHERE path = ?").get(idOrPath);
|
|
50522
51858
|
if (row)
|
|
50523
|
-
return
|
|
51859
|
+
return parseProjectRow2(row);
|
|
50524
51860
|
row = d.query("SELECT * FROM projects WHERE LOWER(name) = ?").get(idOrPath.toLowerCase());
|
|
50525
51861
|
if (row)
|
|
50526
|
-
return
|
|
51862
|
+
return parseProjectRow2(row);
|
|
50527
51863
|
return null;
|
|
50528
51864
|
}
|
|
50529
51865
|
function listProjects(db) {
|
|
@@ -50533,7 +51869,1300 @@ function listProjects(db) {
|
|
|
50533
51869
|
}
|
|
50534
51870
|
const d = db || getDatabase();
|
|
50535
51871
|
const rows = d.query("SELECT * FROM projects ORDER BY updated_at DESC").all();
|
|
50536
|
-
return rows.map(
|
|
51872
|
+
return rows.map(parseProjectRow2);
|
|
51873
|
+
}
|
|
51874
|
+
function previewProjectUpdate(id, request, db) {
|
|
51875
|
+
assertProjectUpdateRequest(request);
|
|
51876
|
+
const normalized = normalizeProjectUpdateInput(request.updates);
|
|
51877
|
+
if (!db && isApiMode()) {
|
|
51878
|
+
const { data } = apiJson("POST", `/projects/${encodeURIComponent(id)}/guarded-update`, { ...request, updates: normalized, dry_run: true });
|
|
51879
|
+
return data;
|
|
51880
|
+
}
|
|
51881
|
+
const d = db || getDatabase();
|
|
51882
|
+
const project = getProjectByExactId2(id, d);
|
|
51883
|
+
if (!project) {
|
|
51884
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_NOT_FOUND", `Project not found by exact stable ID: ${id}`);
|
|
51885
|
+
}
|
|
51886
|
+
if (project.updated_at !== request.expected_revision) {
|
|
51887
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_STALE_REVISION", "Project revision changed before the guarded update dry run", { expected_revision: request.expected_revision, current_revision: project.updated_at });
|
|
51888
|
+
}
|
|
51889
|
+
assertNoProjectCollision(id, normalized, d);
|
|
51890
|
+
return {
|
|
51891
|
+
dry_run: true,
|
|
51892
|
+
applied: false,
|
|
51893
|
+
project: projectWithUpdates(project, normalized, project.updated_at),
|
|
51894
|
+
receipt: null
|
|
51895
|
+
};
|
|
51896
|
+
}
|
|
51897
|
+
function applyProjectUpdate(id, request, db) {
|
|
51898
|
+
assertProjectUpdateRequest(request);
|
|
51899
|
+
const normalized = normalizeProjectUpdateInput(request.updates);
|
|
51900
|
+
if (!db && isApiMode()) {
|
|
51901
|
+
const { data } = apiJson("POST", `/projects/${encodeURIComponent(id)}/guarded-update`, { ...request, updates: normalized, dry_run: false });
|
|
51902
|
+
return data;
|
|
51903
|
+
}
|
|
51904
|
+
const d = db || getDatabase();
|
|
51905
|
+
const requestDigest = digestProjectUpdateValue({
|
|
51906
|
+
...request,
|
|
51907
|
+
target_id: id,
|
|
51908
|
+
direction: "forward",
|
|
51909
|
+
updates: normalized
|
|
51910
|
+
});
|
|
51911
|
+
return d.transaction(() => {
|
|
51912
|
+
const prior = findProjectUpdateReceiptByKey(d, request, "forward", request.idempotency_key);
|
|
51913
|
+
if (prior) {
|
|
51914
|
+
if (prior.request_digest !== requestDigest || prior.target_id !== id) {
|
|
51915
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_IDEMPOTENCY_MISMATCH", "caller idempotency key is already bound to a different request");
|
|
51916
|
+
}
|
|
51917
|
+
const current = getProjectByExactId2(id, d);
|
|
51918
|
+
if (!current || canonicalProjectUpdateJson(current) !== canonicalProjectUpdateJson(prior.after_project)) {
|
|
51919
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_ACCEPTED_TARGET_DRIFTED", "accepted guarded update target drifted after its immutable receipt");
|
|
51920
|
+
}
|
|
51921
|
+
return { dry_run: false, applied: true, project: prior.after_project, receipt: prior };
|
|
51922
|
+
}
|
|
51923
|
+
const before = getProjectByExactId2(id, d);
|
|
51924
|
+
if (!before) {
|
|
51925
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_NOT_FOUND", `Project not found by exact stable ID: ${id}`);
|
|
51926
|
+
}
|
|
51927
|
+
if (before.updated_at !== request.expected_revision) {
|
|
51928
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_STALE_REVISION", "Project revision changed before the guarded update", { expected_revision: request.expected_revision, current_revision: before.updated_at });
|
|
51929
|
+
}
|
|
51930
|
+
assertNoProjectCollision(id, normalized, d);
|
|
51931
|
+
const revision = nextProjectRevision(before.updated_at);
|
|
51932
|
+
const after = projectWithUpdates(before, normalized, revision);
|
|
51933
|
+
const result = d.run(`
|
|
51934
|
+
UPDATE projects
|
|
51935
|
+
SET name = ?, path = ?, description = ?, memory_prefix = ?, updated_at = ?
|
|
51936
|
+
WHERE id = ? AND updated_at = ?
|
|
51937
|
+
`, [
|
|
51938
|
+
after.name,
|
|
51939
|
+
after.path,
|
|
51940
|
+
after.description,
|
|
51941
|
+
after.memory_prefix,
|
|
51942
|
+
after.updated_at,
|
|
51943
|
+
id,
|
|
51944
|
+
request.expected_revision
|
|
51945
|
+
]);
|
|
51946
|
+
if (result.changes !== 1) {
|
|
51947
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_STALE_REVISION", "Project compare-and-swap did not update exactly one row");
|
|
51948
|
+
}
|
|
51949
|
+
const readback = getProjectByExactId2(id, d);
|
|
51950
|
+
if (!readback || canonicalProjectUpdateJson(readback) !== canonicalProjectUpdateJson(after)) {
|
|
51951
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_ACCEPTED_TARGET_DRIFTED", "Project guarded update did not read back exactly under the stable ID");
|
|
51952
|
+
}
|
|
51953
|
+
const receipt = makeProjectUpdateReceipt({
|
|
51954
|
+
request: { ...request, updates: normalized },
|
|
51955
|
+
direction: "forward",
|
|
51956
|
+
request_digest: requestDigest,
|
|
51957
|
+
target_id: id,
|
|
51958
|
+
before_project: before,
|
|
51959
|
+
after_project: readback
|
|
51960
|
+
});
|
|
51961
|
+
insertProjectUpdateReceipt(d, receipt);
|
|
51962
|
+
return { dry_run: false, applied: true, project: readback, receipt };
|
|
51963
|
+
});
|
|
51964
|
+
}
|
|
51965
|
+
function rollbackProjectUpdate(id, request, db) {
|
|
51966
|
+
assertProjectUpdateRequest(request);
|
|
51967
|
+
assertBoundedIdentifier2(request.accepted_receipt_id, "accepted_receipt_id");
|
|
51968
|
+
if (!db && isApiMode()) {
|
|
51969
|
+
const { data } = apiJson("POST", `/projects/${encodeURIComponent(id)}/guarded-rollback`, request);
|
|
51970
|
+
return data;
|
|
51971
|
+
}
|
|
51972
|
+
const d = db || getDatabase();
|
|
51973
|
+
const requestDigest = digestProjectUpdateValue({
|
|
51974
|
+
...request,
|
|
51975
|
+
target_id: id,
|
|
51976
|
+
direction: "rollback"
|
|
51977
|
+
});
|
|
51978
|
+
return d.transaction(() => {
|
|
51979
|
+
const prior = findProjectUpdateReceiptByKey(d, request, "rollback", request.idempotency_key);
|
|
51980
|
+
if (prior) {
|
|
51981
|
+
if (prior.request_digest !== requestDigest || prior.target_id !== id) {
|
|
51982
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_IDEMPOTENCY_MISMATCH", "caller idempotency key is already bound to a different rollback request");
|
|
51983
|
+
}
|
|
51984
|
+
const current2 = getProjectByExactId2(id, d);
|
|
51985
|
+
if (!current2 || canonicalProjectUpdateJson(current2) !== canonicalProjectUpdateJson(prior.after_project)) {
|
|
51986
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_ACCEPTED_TARGET_DRIFTED", "accepted rollback target drifted after its immutable receipt");
|
|
51987
|
+
}
|
|
51988
|
+
return { dry_run: false, applied: true, project: prior.after_project, receipt: prior };
|
|
51989
|
+
}
|
|
51990
|
+
const accepted = findProjectUpdateReceiptById(d, request, request.accepted_receipt_id);
|
|
51991
|
+
if (!accepted || accepted.direction !== "forward" || accepted.target_id !== id) {
|
|
51992
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_RECEIPT_NOT_FOUND", "accepted forward update receipt was not found for this exact project");
|
|
51993
|
+
}
|
|
51994
|
+
const current = getProjectByExactId2(id, d);
|
|
51995
|
+
if (!current) {
|
|
51996
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_NOT_FOUND", `Project not found by exact stable ID: ${id}`);
|
|
51997
|
+
}
|
|
51998
|
+
if (current.updated_at !== request.expected_revision || canonicalProjectUpdateJson(current) !== canonicalProjectUpdateJson(accepted.after_project)) {
|
|
51999
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_STALE_REVISION", "Project no longer matches the accepted forward receipt", { expected_revision: request.expected_revision, current_revision: current.updated_at });
|
|
52000
|
+
}
|
|
52001
|
+
assertNoProjectCollision(id, accepted.before_project, d);
|
|
52002
|
+
const restored = accepted.before_project;
|
|
52003
|
+
const result = d.run(`
|
|
52004
|
+
UPDATE projects
|
|
52005
|
+
SET name = ?, path = ?, description = ?, memory_prefix = ?,
|
|
52006
|
+
created_at = ?, updated_at = ?
|
|
52007
|
+
WHERE id = ? AND updated_at = ?
|
|
52008
|
+
`, [
|
|
52009
|
+
restored.name,
|
|
52010
|
+
restored.path,
|
|
52011
|
+
restored.description,
|
|
52012
|
+
restored.memory_prefix,
|
|
52013
|
+
restored.created_at,
|
|
52014
|
+
restored.updated_at,
|
|
52015
|
+
id,
|
|
52016
|
+
request.expected_revision
|
|
52017
|
+
]);
|
|
52018
|
+
if (result.changes !== 1) {
|
|
52019
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_STALE_REVISION", "Project rollback compare-and-swap did not update exactly one row");
|
|
52020
|
+
}
|
|
52021
|
+
const readback = getProjectByExactId2(id, d);
|
|
52022
|
+
if (!readback || canonicalProjectUpdateJson(readback) !== canonicalProjectUpdateJson(restored)) {
|
|
52023
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_ACCEPTED_TARGET_DRIFTED", "Project rollback did not restore the exact prior row");
|
|
52024
|
+
}
|
|
52025
|
+
const receipt = makeProjectUpdateReceipt({
|
|
52026
|
+
request,
|
|
52027
|
+
direction: "rollback",
|
|
52028
|
+
request_digest: requestDigest,
|
|
52029
|
+
target_id: id,
|
|
52030
|
+
before_project: current,
|
|
52031
|
+
after_project: readback,
|
|
52032
|
+
accepted_receipt_id: accepted.receipt_id
|
|
52033
|
+
});
|
|
52034
|
+
insertProjectUpdateReceipt(d, receipt);
|
|
52035
|
+
return { dry_run: false, applied: true, project: readback, receipt };
|
|
52036
|
+
});
|
|
52037
|
+
}
|
|
52038
|
+
function getProjectUpdateReceipt(id, receiptId, identity = PROJECT_UPDATE_AUTHORITY, db) {
|
|
52039
|
+
assertProjectUpdateIdentity(identity);
|
|
52040
|
+
if (!db && isApiMode()) {
|
|
52041
|
+
const { data } = apiJson("POST", `/projects/${encodeURIComponent(id)}/update-receipts/lookup`, { ...identity, receipt_id: receiptId });
|
|
52042
|
+
return data;
|
|
52043
|
+
}
|
|
52044
|
+
const d = db || getDatabase();
|
|
52045
|
+
const receipt = findProjectUpdateReceiptById(d, identity, receiptId);
|
|
52046
|
+
if (!receipt || receipt.target_id !== id) {
|
|
52047
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_RECEIPT_NOT_FOUND", "immutable project update receipt was not found for this exact project");
|
|
52048
|
+
}
|
|
52049
|
+
return receipt;
|
|
52050
|
+
}
|
|
52051
|
+
// src/project-registration/authority.ts
|
|
52052
|
+
import { createHash as createHash3 } from "crypto";
|
|
52053
|
+
import { resolve as resolve2 } from "path";
|
|
52054
|
+
|
|
52055
|
+
// src/project-registration/project-references.ts
|
|
52056
|
+
var MEMENTOS_PROJECT_REFERENCE_SURFACES = [
|
|
52057
|
+
{ key: "memories", table: "memories", column: "project_id" },
|
|
52058
|
+
{ key: "sessions", table: "sessions", column: "project_id" },
|
|
52059
|
+
{ key: "entities", table: "entities", column: "project_id" },
|
|
52060
|
+
{ key: "agents", table: "agents", column: "active_project_id" },
|
|
52061
|
+
{ key: "tool_events", table: "tool_events", column: "project_id" },
|
|
52062
|
+
{ key: "tasks", table: "tasks", column: "project_id" },
|
|
52063
|
+
{
|
|
52064
|
+
key: "memory_consolidation_runs",
|
|
52065
|
+
table: "memory_consolidation_runs",
|
|
52066
|
+
column: "project_id"
|
|
52067
|
+
},
|
|
52068
|
+
{
|
|
52069
|
+
key: "memory_reflection_runs",
|
|
52070
|
+
table: "memory_reflection_runs",
|
|
52071
|
+
column: "project_id"
|
|
52072
|
+
},
|
|
52073
|
+
{ key: "memory_acl", table: "memory_acl", column: "project_id" },
|
|
52074
|
+
{ key: "search_history", table: "search_history", column: "project_id" },
|
|
52075
|
+
{
|
|
52076
|
+
key: "session_memory_jobs",
|
|
52077
|
+
table: "session_memory_jobs",
|
|
52078
|
+
column: "project_id"
|
|
52079
|
+
},
|
|
52080
|
+
{ key: "synthesis_events", table: "synthesis_events", column: "project_id" },
|
|
52081
|
+
{ key: "synthesis_runs", table: "synthesis_runs", column: "project_id" },
|
|
52082
|
+
{ key: "webhook_hooks", table: "webhook_hooks", column: "project_id" }
|
|
52083
|
+
];
|
|
52084
|
+
function quotedIdentifier(value) {
|
|
52085
|
+
if (!/^[a-z_][a-z0-9_]*$/.test(value)) {
|
|
52086
|
+
throw new Error(`unsafe project-reference identifier: ${value}`);
|
|
52087
|
+
}
|
|
52088
|
+
return `"${value}"`;
|
|
52089
|
+
}
|
|
52090
|
+
function mementosProjectReferenceCounts(db, projectId) {
|
|
52091
|
+
const projections = MEMENTOS_PROJECT_REFERENCE_SURFACES.map(({ key, table, column }) => `(SELECT COUNT(*) FROM ${quotedIdentifier(table)} ` + `WHERE ${quotedIdentifier(column)} = ?) AS ${quotedIdentifier(key)}`).join(`,
|
|
52092
|
+
`);
|
|
52093
|
+
const row = db.get(`SELECT
|
|
52094
|
+
${projections}`, ...MEMENTOS_PROJECT_REFERENCE_SURFACES.map(() => projectId));
|
|
52095
|
+
return Object.fromEntries(MEMENTOS_PROJECT_REFERENCE_SURFACES.map(({ key }) => [key, Number(row[key] ?? 0)]));
|
|
52096
|
+
}
|
|
52097
|
+
function hasMementosProjectReferences(counts) {
|
|
52098
|
+
return MEMENTOS_PROJECT_REFERENCE_SURFACES.some(({ key }) => counts[key] > 0);
|
|
52099
|
+
}
|
|
52100
|
+
function deleteMementosProjectIfUnreferenced(db, projectId) {
|
|
52101
|
+
const guards = MEMENTOS_PROJECT_REFERENCE_SURFACES.map(({ table, column }) => `NOT EXISTS (SELECT 1 FROM ${quotedIdentifier(table)} ` + `WHERE ${quotedIdentifier(column)} = ?)`).join(`
|
|
52102
|
+
AND `);
|
|
52103
|
+
const result = db.run(`DELETE FROM projects
|
|
52104
|
+
WHERE id = ?
|
|
52105
|
+
AND ${guards}`, projectId, ...MEMENTOS_PROJECT_REFERENCE_SURFACES.map(() => projectId));
|
|
52106
|
+
return result.changes;
|
|
52107
|
+
}
|
|
52108
|
+
|
|
52109
|
+
// src/project-registration/authority.ts
|
|
52110
|
+
var WORKSPACE_ID_PATTERN = /^wks_[A-Za-z0-9][A-Za-z0-9_-]{11,}$/;
|
|
52111
|
+
var OPERATION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
|
|
52112
|
+
var STEP_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$/;
|
|
52113
|
+
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
52114
|
+
var IDEMPOTENCY_PATTERN = /^prk_[0-9a-f]{48}$/;
|
|
52115
|
+
var PROJECT_ID_PATTERN = /^mm_project_[0-9a-f]{40}$/;
|
|
52116
|
+
|
|
52117
|
+
class WriteBoundaryError extends Error {
|
|
52118
|
+
point;
|
|
52119
|
+
cause;
|
|
52120
|
+
constructor(point, cause) {
|
|
52121
|
+
super(`Mementos project registration failed at ${point}`);
|
|
52122
|
+
this.point = point;
|
|
52123
|
+
this.cause = cause;
|
|
52124
|
+
}
|
|
52125
|
+
}
|
|
52126
|
+
function canonicalMementosProjectRegistrationJson(value) {
|
|
52127
|
+
return JSON.stringify(canonicalize2(value));
|
|
52128
|
+
}
|
|
52129
|
+
function canonicalize2(value) {
|
|
52130
|
+
if (Array.isArray(value))
|
|
52131
|
+
return value.map(canonicalize2);
|
|
52132
|
+
if (!value || typeof value !== "object")
|
|
52133
|
+
return value;
|
|
52134
|
+
const out = {};
|
|
52135
|
+
for (const key of Object.keys(value).sort()) {
|
|
52136
|
+
const item = value[key];
|
|
52137
|
+
if (item !== undefined)
|
|
52138
|
+
out[key] = canonicalize2(item);
|
|
52139
|
+
}
|
|
52140
|
+
return out;
|
|
52141
|
+
}
|
|
52142
|
+
function digestMementosProjectRegistrationValue(value) {
|
|
52143
|
+
return createHash3("sha256").update(canonicalMementosProjectRegistrationJson(value)).digest("hex");
|
|
52144
|
+
}
|
|
52145
|
+
function digestOwnedPath(path) {
|
|
52146
|
+
return createHash3("sha256").update(path).digest("hex");
|
|
52147
|
+
}
|
|
52148
|
+
function deriveMementosProjectRegistrationIdempotencyKey(input) {
|
|
52149
|
+
return `prk_${digestMementosProjectRegistrationValue({
|
|
52150
|
+
route: MEMENTOS_PROJECT_REGISTRATION_CALLER_ROUTE,
|
|
52151
|
+
operation_id: input.operation_id,
|
|
52152
|
+
step_id: input.step_id,
|
|
52153
|
+
direction: input.direction,
|
|
52154
|
+
target_selector: input.target_selector,
|
|
52155
|
+
request_digest: input.request_digest,
|
|
52156
|
+
precondition_digest: input.precondition_digest
|
|
52157
|
+
}).slice(0, 48)}`;
|
|
52158
|
+
}
|
|
52159
|
+
function responseBytes(value) {
|
|
52160
|
+
return Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
52161
|
+
}
|
|
52162
|
+
function assertBounds(bounds) {
|
|
52163
|
+
if (!Number.isSafeInteger(bounds.response_byte_limit) || bounds.response_byte_limit <= 0) {
|
|
52164
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "response_byte_limit must be a positive integer");
|
|
52165
|
+
}
|
|
52166
|
+
if (!Number.isSafeInteger(bounds.time_budget_ms) || bounds.time_budget_ms <= 0) {
|
|
52167
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "time_budget_ms must be a positive integer");
|
|
52168
|
+
}
|
|
52169
|
+
}
|
|
52170
|
+
function assertWithinBounds(value, bounds, startedAt) {
|
|
52171
|
+
const bytes = responseBytes(value);
|
|
52172
|
+
if (bytes > bounds.response_byte_limit) {
|
|
52173
|
+
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 });
|
|
52174
|
+
}
|
|
52175
|
+
const elapsed = Date.now() - startedAt;
|
|
52176
|
+
if (elapsed > bounds.time_budget_ms) {
|
|
52177
|
+
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 });
|
|
52178
|
+
}
|
|
52179
|
+
return { response_bytes: bytes, elapsed_ms: elapsed };
|
|
52180
|
+
}
|
|
52181
|
+
function withResponseControl(receipt, bounds, startedAt) {
|
|
52182
|
+
const result = {
|
|
52183
|
+
receipt,
|
|
52184
|
+
response_control: {
|
|
52185
|
+
response_byte_limit: bounds.response_byte_limit,
|
|
52186
|
+
time_budget_ms: bounds.time_budget_ms,
|
|
52187
|
+
response_bytes: 0,
|
|
52188
|
+
elapsed_ms: 0,
|
|
52189
|
+
complete: true,
|
|
52190
|
+
truncated: false
|
|
52191
|
+
}
|
|
52192
|
+
};
|
|
52193
|
+
for (let attempt = 0;attempt < 8; attempt += 1) {
|
|
52194
|
+
const measured2 = assertWithinBounds(result, bounds, startedAt);
|
|
52195
|
+
const stable = result.response_control.response_bytes === measured2.response_bytes && result.response_control.elapsed_ms === measured2.elapsed_ms;
|
|
52196
|
+
result.response_control.response_bytes = measured2.response_bytes;
|
|
52197
|
+
result.response_control.elapsed_ms = measured2.elapsed_ms;
|
|
52198
|
+
if (stable)
|
|
52199
|
+
break;
|
|
52200
|
+
}
|
|
52201
|
+
const measured = assertWithinBounds(result, bounds, startedAt);
|
|
52202
|
+
result.response_control.response_bytes = measured.response_bytes;
|
|
52203
|
+
result.response_control.elapsed_ms = measured.elapsed_ms;
|
|
52204
|
+
return result;
|
|
52205
|
+
}
|
|
52206
|
+
function requireString(value, field, options = {}) {
|
|
52207
|
+
const min = options.min ?? 1;
|
|
52208
|
+
const max = options.max ?? 512;
|
|
52209
|
+
if (typeof value !== "string" || value.length < min || value.length > max || /[\u0000-\u001f]/.test(value) || options.pattern && !options.pattern.test(value)) {
|
|
52210
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field} is not a valid bounded registration identifier`);
|
|
52211
|
+
}
|
|
52212
|
+
return value;
|
|
52213
|
+
}
|
|
52214
|
+
function exactKeys(value, expected, field) {
|
|
52215
|
+
const actual = Object.keys(value).sort();
|
|
52216
|
+
const wanted = [...expected].sort();
|
|
52217
|
+
if (actual.length !== wanted.length || actual.some((key, i) => key !== wanted[i])) {
|
|
52218
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field} must contain exactly: ${wanted.join(", ")}`);
|
|
52219
|
+
}
|
|
52220
|
+
}
|
|
52221
|
+
function ownedPath(target) {
|
|
52222
|
+
if (!target || typeof target.withOwnedPath !== "function") {
|
|
52223
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "target must be a package-owned path handle");
|
|
52224
|
+
}
|
|
52225
|
+
const path = target.withOwnedPath((value) => value);
|
|
52226
|
+
requireString(path, "target path", { max: 4096 });
|
|
52227
|
+
if (path !== resolve2(path)) {
|
|
52228
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "target path must already be canonical and absolute");
|
|
52229
|
+
}
|
|
52230
|
+
return path;
|
|
52231
|
+
}
|
|
52232
|
+
function normalizedCallDigest(request) {
|
|
52233
|
+
return digestMementosProjectRegistrationValue({
|
|
52234
|
+
authority_route: request.authority_route,
|
|
52235
|
+
package_version: request.package_version,
|
|
52236
|
+
authority_id: request.authority_id,
|
|
52237
|
+
tenant_id: request.tenant_id,
|
|
52238
|
+
corpus_id: request.corpus_id,
|
|
52239
|
+
operation_id: request.operation_id,
|
|
52240
|
+
step_id: request.step_id,
|
|
52241
|
+
resource_kind: request.resource_kind,
|
|
52242
|
+
direction: request.direction,
|
|
52243
|
+
target_selector: request.target_selector,
|
|
52244
|
+
idempotency_key: request.idempotency_key,
|
|
52245
|
+
request_digest: request.request_digest,
|
|
52246
|
+
precondition_digest: request.precondition_digest,
|
|
52247
|
+
project_id: request.project_id,
|
|
52248
|
+
project_slug: request.project_slug,
|
|
52249
|
+
project_name: request.project_name,
|
|
52250
|
+
desired: request.desired,
|
|
52251
|
+
accepted_receipt_id: request.accepted_receipt?.receipt_id ?? null
|
|
52252
|
+
});
|
|
52253
|
+
}
|
|
52254
|
+
function publicReceipt(row) {
|
|
52255
|
+
const { target_selector: _selector, normalized_call_digest: _digest, ...receipt } = row;
|
|
52256
|
+
return receipt;
|
|
52257
|
+
}
|
|
52258
|
+
function timestampString3(value) {
|
|
52259
|
+
return value instanceof Date ? value.toISOString() : String(value);
|
|
52260
|
+
}
|
|
52261
|
+
function receiptFromRow2(row) {
|
|
52262
|
+
return {
|
|
52263
|
+
...row,
|
|
52264
|
+
authority: "mementos",
|
|
52265
|
+
resource_kind: "project",
|
|
52266
|
+
created_by_operation: row["created_by_operation"] === true || Number(row["created_by_operation"]) === 1,
|
|
52267
|
+
created_at: timestampString3(row["created_at"])
|
|
52268
|
+
};
|
|
52269
|
+
}
|
|
52270
|
+
function bindingFromRow(row) {
|
|
52271
|
+
return {
|
|
52272
|
+
...row,
|
|
52273
|
+
created_at: timestampString3(row["created_at"]),
|
|
52274
|
+
updated_at: timestampString3(row["updated_at"])
|
|
52275
|
+
};
|
|
52276
|
+
}
|
|
52277
|
+
function getStoredReceipt(db, receiptId) {
|
|
52278
|
+
const row = db.get("SELECT * FROM mementos_project_registration_receipts WHERE receipt_id = ? LIMIT 1", receiptId);
|
|
52279
|
+
return row ? receiptFromRow2(row) : null;
|
|
52280
|
+
}
|
|
52281
|
+
function getAcceptedReceipt(db, request) {
|
|
52282
|
+
const row = db.get(`
|
|
52283
|
+
SELECT * FROM mementos_project_registration_receipts
|
|
52284
|
+
WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
52285
|
+
AND operation_id = ? AND step_id = ? AND resource_kind = 'project'
|
|
52286
|
+
AND direction = ? AND outcome = 'accepted'
|
|
52287
|
+
ORDER BY created_at ASC, receipt_id ASC
|
|
52288
|
+
LIMIT 1
|
|
52289
|
+
`, request.authority_id, request.tenant_id, request.corpus_id, request.operation_id, request.step_id, request.direction);
|
|
52290
|
+
return row ? receiptFromRow2(row) : null;
|
|
52291
|
+
}
|
|
52292
|
+
function getReceiptForLookup(db, request) {
|
|
52293
|
+
const row = db.get(`
|
|
52294
|
+
SELECT * FROM mementos_project_registration_receipts
|
|
52295
|
+
WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
52296
|
+
AND operation_id = ? AND step_id = ? AND resource_kind = 'project'
|
|
52297
|
+
AND direction = ? AND idempotency_key = ? AND target_selector = ?
|
|
52298
|
+
ORDER BY CASE outcome
|
|
52299
|
+
WHEN 'terminal_nonacceptance' THEN 0
|
|
52300
|
+
WHEN 'duplicate_of_accepted' THEN 1
|
|
52301
|
+
ELSE 2
|
|
52302
|
+
END, created_at DESC, receipt_id DESC
|
|
52303
|
+
LIMIT 1
|
|
52304
|
+
`, request.authority_id, request.tenant_id, request.corpus_id, request.operation_id, request.step_id, request.direction, request.idempotency_key, request.target_selector);
|
|
52305
|
+
return row ? receiptFromRow2(row) : null;
|
|
52306
|
+
}
|
|
52307
|
+
function getBinding(db, capability, targetSelector) {
|
|
52308
|
+
const row = db.get(`
|
|
52309
|
+
SELECT * FROM mementos_project_registration_bindings
|
|
52310
|
+
WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
52311
|
+
AND resource_kind = 'project' AND target_selector = ?
|
|
52312
|
+
LIMIT 1
|
|
52313
|
+
`, capability.authority_id, capability.tenant_id, capability.corpus_id, targetSelector);
|
|
52314
|
+
return row ? bindingFromRow(row) : null;
|
|
52315
|
+
}
|
|
52316
|
+
function getProjectByExactId3(db, id) {
|
|
52317
|
+
const row = db.get("SELECT * FROM projects WHERE id = ? LIMIT 1", id);
|
|
52318
|
+
if (!row)
|
|
52319
|
+
return null;
|
|
52320
|
+
return {
|
|
52321
|
+
id: String(row["id"]),
|
|
52322
|
+
name: String(row["name"]),
|
|
52323
|
+
path: String(row["path"]),
|
|
52324
|
+
description: row["description"] === null ? null : String(row["description"]),
|
|
52325
|
+
memory_prefix: row["memory_prefix"] === null ? null : String(row["memory_prefix"]),
|
|
52326
|
+
created_at: timestampString3(row["created_at"]),
|
|
52327
|
+
updated_at: timestampString3(row["updated_at"])
|
|
52328
|
+
};
|
|
52329
|
+
}
|
|
52330
|
+
function getProjectByPath(db, path) {
|
|
52331
|
+
const row = db.get("SELECT * FROM projects WHERE path = ? LIMIT 1", path);
|
|
52332
|
+
return row ? getProjectByExactId3(db, String(row["id"])) : null;
|
|
52333
|
+
}
|
|
52334
|
+
function projectRecord(db, project, references = mementosProjectReferenceCounts(db, project.id)) {
|
|
52335
|
+
return {
|
|
52336
|
+
target_id: project.id,
|
|
52337
|
+
revision: project.updated_at,
|
|
52338
|
+
digest: digestMementosProjectRegistrationValue({
|
|
52339
|
+
id: project.id,
|
|
52340
|
+
name: project.name,
|
|
52341
|
+
path: project.path,
|
|
52342
|
+
description: project.description,
|
|
52343
|
+
memory_prefix: project.memory_prefix,
|
|
52344
|
+
created_at: project.created_at,
|
|
52345
|
+
updated_at: project.updated_at,
|
|
52346
|
+
references
|
|
52347
|
+
})
|
|
52348
|
+
};
|
|
52349
|
+
}
|
|
52350
|
+
function targetId(capability, selector) {
|
|
52351
|
+
return `mm_project_${digestMementosProjectRegistrationValue({
|
|
52352
|
+
authority_id: capability.authority_id,
|
|
52353
|
+
tenant_id: capability.tenant_id,
|
|
52354
|
+
corpus_id: capability.corpus_id,
|
|
52355
|
+
selector
|
|
52356
|
+
}).slice(0, 40)}`;
|
|
52357
|
+
}
|
|
52358
|
+
function receiptId(input) {
|
|
52359
|
+
return `mmpr_${digestMementosProjectRegistrationValue(input).slice(0, 40)}`;
|
|
52360
|
+
}
|
|
52361
|
+
function makeReceipt2(request, capability, callDigest, now2, values) {
|
|
52362
|
+
const withoutId = {
|
|
52363
|
+
authority: "mementos",
|
|
52364
|
+
route: MEMENTOS_PROJECT_REGISTRATION_ROUTE,
|
|
52365
|
+
package_version: capability.package_version,
|
|
52366
|
+
authority_id: capability.authority_id,
|
|
52367
|
+
tenant_id: capability.tenant_id,
|
|
52368
|
+
corpus_id: capability.corpus_id,
|
|
52369
|
+
operation_id: request.operation_id,
|
|
52370
|
+
step_id: request.step_id,
|
|
52371
|
+
resource_kind: "project",
|
|
52372
|
+
direction: request.direction,
|
|
52373
|
+
target_selector: values.target_selector ?? request.target_selector,
|
|
52374
|
+
idempotency_key: request.idempotency_key,
|
|
52375
|
+
request_digest: request.request_digest,
|
|
52376
|
+
precondition_digest: request.precondition_digest,
|
|
52377
|
+
normalized_call_digest: callDigest,
|
|
52378
|
+
outcome: values.outcome,
|
|
52379
|
+
reason: values.reason ?? null,
|
|
52380
|
+
target_id: values.target_id ?? null,
|
|
52381
|
+
result_revision: values.result_revision ?? null,
|
|
52382
|
+
result_digest: values.result_digest ?? null,
|
|
52383
|
+
duplicate_of_receipt_id: values.duplicate_of_receipt_id ?? null,
|
|
52384
|
+
accepted_receipt_id: values.accepted_receipt_id ?? null,
|
|
52385
|
+
created_by_operation: values.created_by_operation ?? false
|
|
52386
|
+
};
|
|
52387
|
+
return {
|
|
52388
|
+
receipt_id: receiptId(withoutId),
|
|
52389
|
+
...withoutId,
|
|
52390
|
+
created_at: now2
|
|
52391
|
+
};
|
|
52392
|
+
}
|
|
52393
|
+
function insertReceipt2(db, receipt) {
|
|
52394
|
+
db.run(`
|
|
52395
|
+
INSERT INTO mementos_project_registration_receipts (
|
|
52396
|
+
receipt_id, authority, route, package_version, authority_id, tenant_id,
|
|
52397
|
+
corpus_id, operation_id, step_id, resource_kind, direction,
|
|
52398
|
+
target_selector, idempotency_key, request_digest, precondition_digest,
|
|
52399
|
+
normalized_call_digest, outcome, reason, target_id, result_revision,
|
|
52400
|
+
result_digest, duplicate_of_receipt_id, accepted_receipt_id,
|
|
52401
|
+
created_by_operation, created_at
|
|
52402
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
52403
|
+
ON CONFLICT DO NOTHING
|
|
52404
|
+
`, 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);
|
|
52405
|
+
const stored = getStoredReceipt(db, receipt.receipt_id);
|
|
52406
|
+
const withoutCreatedAt = (value) => {
|
|
52407
|
+
const { created_at: _createdAt, ...logical } = value;
|
|
52408
|
+
return logical;
|
|
52409
|
+
};
|
|
52410
|
+
if (!stored || canonicalMementosProjectRegistrationJson(withoutCreatedAt(stored)) !== canonicalMementosProjectRegistrationJson(withoutCreatedAt(receipt))) {
|
|
52411
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "immutable receipt id resolved to different content");
|
|
52412
|
+
}
|
|
52413
|
+
return stored;
|
|
52414
|
+
}
|
|
52415
|
+
function insertBinding(db, request, callDigest, now2) {
|
|
52416
|
+
const result = db.run(`
|
|
52417
|
+
INSERT INTO mementos_project_registration_bindings (
|
|
52418
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_selector,
|
|
52419
|
+
operation_id, step_id, direction, idempotency_key, request_digest,
|
|
52420
|
+
precondition_digest, normalized_call_digest, state, target_id,
|
|
52421
|
+
accepted_receipt_id, result_revision, result_digest, removed_receipt_id,
|
|
52422
|
+
created_at, updated_at
|
|
52423
|
+
) VALUES (?, ?, ?, 'project', ?, ?, ?, 'forward', ?, ?, ?, ?, 'pending',
|
|
52424
|
+
NULL, NULL, NULL, NULL, NULL, ?, ?)
|
|
52425
|
+
ON CONFLICT DO NOTHING
|
|
52426
|
+
`, 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, now2, now2);
|
|
52427
|
+
return result.changes === 1;
|
|
52428
|
+
}
|
|
52429
|
+
function lockBinding(db, capability, targetSelector) {
|
|
52430
|
+
const result = db.run(`
|
|
52431
|
+
UPDATE mementos_project_registration_bindings
|
|
52432
|
+
SET updated_at = updated_at
|
|
52433
|
+
WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
52434
|
+
AND resource_kind = 'project' AND target_selector = ?
|
|
52435
|
+
`, capability.authority_id, capability.tenant_id, capability.corpus_id, targetSelector);
|
|
52436
|
+
return result.changes === 1;
|
|
52437
|
+
}
|
|
52438
|
+
function setBindingAccepted(db, request, receipt, now2) {
|
|
52439
|
+
const result = db.run(`
|
|
52440
|
+
UPDATE mementos_project_registration_bindings
|
|
52441
|
+
SET state = 'accepted', target_id = ?, accepted_receipt_id = ?,
|
|
52442
|
+
result_revision = ?, result_digest = ?, updated_at = ?
|
|
52443
|
+
WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
52444
|
+
AND resource_kind = 'project' AND target_selector = ? AND state = 'pending'
|
|
52445
|
+
`, receipt.target_id, receipt.receipt_id, receipt.result_revision, receipt.result_digest, now2, request.authority_id, request.tenant_id, request.corpus_id, request.target_selector);
|
|
52446
|
+
if (result.changes !== 1) {
|
|
52447
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "registration binding was not pending at acceptance");
|
|
52448
|
+
}
|
|
52449
|
+
}
|
|
52450
|
+
function setBindingRemoved(db, accepted, inverseReceiptId, now2) {
|
|
52451
|
+
const result = db.run(`
|
|
52452
|
+
UPDATE mementos_project_registration_bindings
|
|
52453
|
+
SET state = 'removed', removed_receipt_id = ?, updated_at = ?
|
|
52454
|
+
WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
52455
|
+
AND resource_kind = 'project' AND target_selector = ?
|
|
52456
|
+
AND state = 'accepted' AND accepted_receipt_id = ?
|
|
52457
|
+
`, inverseReceiptId, now2, accepted.authority_id, accepted.tenant_id, accepted.corpus_id, accepted.target_selector, accepted.receipt_id);
|
|
52458
|
+
if (result.changes !== 1) {
|
|
52459
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "registration binding was not accepted at inverse");
|
|
52460
|
+
}
|
|
52461
|
+
}
|
|
52462
|
+
function capabilityMatches(request, capability) {
|
|
52463
|
+
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;
|
|
52464
|
+
}
|
|
52465
|
+
function assertCommonRequest(request, capability) {
|
|
52466
|
+
assertBounds(request);
|
|
52467
|
+
if (request.resource_kind !== "project") {
|
|
52468
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "resource_kind must be project");
|
|
52469
|
+
}
|
|
52470
|
+
if (request.direction !== "forward" && request.direction !== "inverse") {
|
|
52471
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "direction must be forward or inverse");
|
|
52472
|
+
}
|
|
52473
|
+
if (!capabilityMatches(request, capability)) {
|
|
52474
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH", "registration request does not match this authority capability identity");
|
|
52475
|
+
}
|
|
52476
|
+
requireString(request.operation_id, "operation_id", {
|
|
52477
|
+
min: 8,
|
|
52478
|
+
max: 128,
|
|
52479
|
+
pattern: OPERATION_PATTERN
|
|
52480
|
+
});
|
|
52481
|
+
requireString(request.step_id, "step_id", { min: 3, max: 128, pattern: STEP_PATTERN });
|
|
52482
|
+
requireString(request.target_selector, "target_selector", { max: 512 });
|
|
52483
|
+
requireString(request.project_id, "project_id", {
|
|
52484
|
+
min: 16,
|
|
52485
|
+
max: 128,
|
|
52486
|
+
pattern: WORKSPACE_ID_PATTERN
|
|
52487
|
+
});
|
|
52488
|
+
requireString(request.project_name, "project_name", { max: 256 });
|
|
52489
|
+
requireString(request.project_slug, "project_slug", { max: 128 });
|
|
52490
|
+
requireString(request.request_digest, "request_digest", {
|
|
52491
|
+
min: 64,
|
|
52492
|
+
max: 64,
|
|
52493
|
+
pattern: SHA256_PATTERN
|
|
52494
|
+
});
|
|
52495
|
+
requireString(request.precondition_digest, "precondition_digest", {
|
|
52496
|
+
min: 64,
|
|
52497
|
+
max: 64,
|
|
52498
|
+
pattern: SHA256_PATTERN
|
|
52499
|
+
});
|
|
52500
|
+
requireString(request.idempotency_key, "idempotency_key", {
|
|
52501
|
+
min: 52,
|
|
52502
|
+
max: 52,
|
|
52503
|
+
pattern: IDEMPOTENCY_PATTERN
|
|
52504
|
+
});
|
|
52505
|
+
if (!request.desired || typeof request.desired !== "object" || Array.isArray(request.desired)) {
|
|
52506
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "desired must be a JSON object");
|
|
52507
|
+
}
|
|
52508
|
+
if (digestMementosProjectRegistrationValue(request.desired) !== request.request_digest) {
|
|
52509
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "request_digest does not match the canonical desired payload");
|
|
52510
|
+
}
|
|
52511
|
+
const expectedKey = deriveMementosProjectRegistrationIdempotencyKey(request);
|
|
52512
|
+
if (request.idempotency_key !== expectedKey) {
|
|
52513
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_IDEMPOTENCY_MISMATCH", "idempotency_key does not match the deterministic request", { expected: expectedKey });
|
|
52514
|
+
}
|
|
52515
|
+
}
|
|
52516
|
+
function assertForwardRequest2(request, capability) {
|
|
52517
|
+
assertCommonRequest(request, capability);
|
|
52518
|
+
if (request.direction !== "forward" || request.accepted_receipt !== undefined) {
|
|
52519
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "conditional create requires a forward request without an accepted receipt");
|
|
52520
|
+
}
|
|
52521
|
+
exactKeys(request.desired, [
|
|
52522
|
+
"name",
|
|
52523
|
+
"source_project_id",
|
|
52524
|
+
"source_project_slug",
|
|
52525
|
+
"target_path_digest"
|
|
52526
|
+
], "forward desired");
|
|
52527
|
+
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) {
|
|
52528
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "forward project identity fields disagree");
|
|
52529
|
+
}
|
|
52530
|
+
const expectedPrecondition = digestMementosProjectRegistrationValue({
|
|
52531
|
+
target_selector: request.target_selector,
|
|
52532
|
+
expected: "absent"
|
|
52533
|
+
});
|
|
52534
|
+
if (request.precondition_digest !== expectedPrecondition) {
|
|
52535
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "forward precondition must bind expected absence");
|
|
52536
|
+
}
|
|
52537
|
+
const path = ownedPath(request.target);
|
|
52538
|
+
if (request.desired["target_path_digest"] !== digestOwnedPath(path)) {
|
|
52539
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "target path does not match the package-owned digest");
|
|
52540
|
+
}
|
|
52541
|
+
return path;
|
|
52542
|
+
}
|
|
52543
|
+
function sameReceipt(left, right) {
|
|
52544
|
+
return canonicalMementosProjectRegistrationJson(publicReceipt(left)) === canonicalMementosProjectRegistrationJson(right);
|
|
52545
|
+
}
|
|
52546
|
+
function assertInverseRequest(request, capability) {
|
|
52547
|
+
assertCommonRequest(request, capability);
|
|
52548
|
+
if (request.direction !== "inverse" || !request.accepted_receipt) {
|
|
52549
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "conditional inverse requires the accepted forward receipt");
|
|
52550
|
+
}
|
|
52551
|
+
const accepted = request.accepted_receipt;
|
|
52552
|
+
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) {
|
|
52553
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "accepted receipt is not an exact attempt-created forward receipt");
|
|
52554
|
+
}
|
|
52555
|
+
exactKeys(request.desired, ["accepted_receipt_id", "target_id"], "inverse desired");
|
|
52556
|
+
if (request.desired["accepted_receipt_id"] !== accepted.receipt_id || request.desired["target_id"] !== accepted.target_id) {
|
|
52557
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "inverse desired payload does not bind the accepted receipt");
|
|
52558
|
+
}
|
|
52559
|
+
const expectedPrecondition = digestMementosProjectRegistrationValue({
|
|
52560
|
+
expected_revision: accepted.result_revision,
|
|
52561
|
+
expected_digest: accepted.result_digest
|
|
52562
|
+
});
|
|
52563
|
+
if (request.precondition_digest !== expectedPrecondition) {
|
|
52564
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "inverse precondition does not bind the accepted object state");
|
|
52565
|
+
}
|
|
52566
|
+
return accepted;
|
|
52567
|
+
}
|
|
52568
|
+
function assertLookup(request, capability) {
|
|
52569
|
+
assertBounds(request);
|
|
52570
|
+
if (request.max_items !== 1) {
|
|
52571
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "terminal receipt lookup requires max_items exactly 1");
|
|
52572
|
+
}
|
|
52573
|
+
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) {
|
|
52574
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH", "receipt lookup does not match this authority capability identity");
|
|
52575
|
+
}
|
|
52576
|
+
requireString(request.operation_id, "operation_id", { pattern: OPERATION_PATTERN });
|
|
52577
|
+
requireString(request.step_id, "step_id", { pattern: STEP_PATTERN });
|
|
52578
|
+
requireString(request.target_selector, "target_selector");
|
|
52579
|
+
requireString(request.idempotency_key, "idempotency_key", {
|
|
52580
|
+
min: 52,
|
|
52581
|
+
max: 52,
|
|
52582
|
+
pattern: IDEMPOTENCY_PATTERN
|
|
52583
|
+
});
|
|
52584
|
+
if (request.target_id !== undefined) {
|
|
52585
|
+
requireString(request.target_id, "target_id", {
|
|
52586
|
+
min: 51,
|
|
52587
|
+
max: 51,
|
|
52588
|
+
pattern: PROJECT_ID_PATTERN
|
|
52589
|
+
});
|
|
52590
|
+
}
|
|
52591
|
+
}
|
|
52592
|
+
|
|
52593
|
+
class PackageOwnedMementosProjectRegistrationAuthority {
|
|
52594
|
+
db;
|
|
52595
|
+
authority = "mementos";
|
|
52596
|
+
capabilityValue;
|
|
52597
|
+
now;
|
|
52598
|
+
faultInjector;
|
|
52599
|
+
constructor(db, options = {}) {
|
|
52600
|
+
this.db = db;
|
|
52601
|
+
this.now = options.now ?? (() => new Date().toISOString());
|
|
52602
|
+
this.faultInjector = options.faultInjector;
|
|
52603
|
+
this.capabilityValue = {
|
|
52604
|
+
authority: "mementos",
|
|
52605
|
+
route: MEMENTOS_PROJECT_REGISTRATION_ROUTE,
|
|
52606
|
+
package_version: options.packageVersion ?? getMementosPackageVersion(),
|
|
52607
|
+
authority_id: options.authorityId ?? "mementos",
|
|
52608
|
+
tenant_id: options.tenantId ?? "default",
|
|
52609
|
+
corpus_id: options.corpusId ?? "default",
|
|
52610
|
+
supported_resources: ["project"],
|
|
52611
|
+
conditional_create: true,
|
|
52612
|
+
immutable_receipts: true,
|
|
52613
|
+
exact_terminal_lookup: true,
|
|
52614
|
+
exact_readback: true,
|
|
52615
|
+
conditional_inverse: true,
|
|
52616
|
+
ambiguous_outcome_reconciliation: true,
|
|
52617
|
+
guarded_update: true,
|
|
52618
|
+
no_write_dry_run: true,
|
|
52619
|
+
expected_revision_compare_and_swap: true,
|
|
52620
|
+
caller_idempotency: true,
|
|
52621
|
+
exact_inverse_rollback: true
|
|
52622
|
+
};
|
|
52623
|
+
}
|
|
52624
|
+
fault(point, request) {
|
|
52625
|
+
this.faultInjector?.(point, {
|
|
52626
|
+
operation_id: request.operation_id,
|
|
52627
|
+
step_id: request.step_id,
|
|
52628
|
+
resource_kind: "project",
|
|
52629
|
+
direction: request.direction
|
|
52630
|
+
});
|
|
52631
|
+
}
|
|
52632
|
+
writeBoundary(point, request) {
|
|
52633
|
+
try {
|
|
52634
|
+
this.fault(point, request);
|
|
52635
|
+
} catch (cause) {
|
|
52636
|
+
throw new WriteBoundaryError(point, cause);
|
|
52637
|
+
}
|
|
52638
|
+
}
|
|
52639
|
+
terminal(request, callDigest, reason, values = {}) {
|
|
52640
|
+
return insertReceipt2(this.db, makeReceipt2(request, this.capabilityValue, callDigest, this.now(), {
|
|
52641
|
+
outcome: "terminal_nonacceptance",
|
|
52642
|
+
reason,
|
|
52643
|
+
target_id: values.target_id,
|
|
52644
|
+
accepted_receipt_id: values.accepted_receipt_id,
|
|
52645
|
+
target_selector: values.target_selector
|
|
52646
|
+
}));
|
|
52647
|
+
}
|
|
52648
|
+
duplicateForwardOrTerminal(request, callDigest, path, accepted) {
|
|
52649
|
+
if (accepted.normalized_call_digest !== callDigest) {
|
|
52650
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_IDEMPOTENCY_MISMATCH", "accepted operation/step identity is bound to a different request");
|
|
52651
|
+
}
|
|
52652
|
+
const project = accepted.target_id ? getProjectByExactId3(this.db, accepted.target_id) : null;
|
|
52653
|
+
if (!project || project.path !== path) {
|
|
52654
|
+
return this.terminal(request, callDigest, "accepted_target_missing_or_replaced", {
|
|
52655
|
+
target_id: accepted.target_id
|
|
52656
|
+
});
|
|
52657
|
+
}
|
|
52658
|
+
const record = projectRecord(this.db, project);
|
|
52659
|
+
if (record.revision !== accepted.result_revision || record.digest !== accepted.result_digest) {
|
|
52660
|
+
return this.terminal(request, callDigest, "accepted_target_drifted", {
|
|
52661
|
+
target_id: accepted.target_id
|
|
52662
|
+
});
|
|
52663
|
+
}
|
|
52664
|
+
return insertReceipt2(this.db, makeReceipt2(request, this.capabilityValue, callDigest, this.now(), {
|
|
52665
|
+
outcome: "duplicate_of_accepted",
|
|
52666
|
+
target_id: accepted.target_id,
|
|
52667
|
+
result_revision: accepted.result_revision,
|
|
52668
|
+
result_digest: accepted.result_digest,
|
|
52669
|
+
duplicate_of_receipt_id: accepted.receipt_id,
|
|
52670
|
+
created_by_operation: false
|
|
52671
|
+
}));
|
|
52672
|
+
}
|
|
52673
|
+
async capability() {
|
|
52674
|
+
return {
|
|
52675
|
+
...this.capabilityValue,
|
|
52676
|
+
supported_resources: ["project"]
|
|
52677
|
+
};
|
|
52678
|
+
}
|
|
52679
|
+
async create(request) {
|
|
52680
|
+
const startedAt = Date.now();
|
|
52681
|
+
const path = assertForwardRequest2(request, this.capabilityValue);
|
|
52682
|
+
const callDigest = normalizedCallDigest(request);
|
|
52683
|
+
let stored;
|
|
52684
|
+
try {
|
|
52685
|
+
stored = this.db.transaction(() => {
|
|
52686
|
+
const exact = getReceiptForLookup(this.db, {
|
|
52687
|
+
...request,
|
|
52688
|
+
authority: "mementos",
|
|
52689
|
+
max_items: 1
|
|
52690
|
+
});
|
|
52691
|
+
if (exact?.outcome === "terminal_nonacceptance")
|
|
52692
|
+
return exact;
|
|
52693
|
+
const accepted = getAcceptedReceipt(this.db, request);
|
|
52694
|
+
if (accepted) {
|
|
52695
|
+
return this.duplicateForwardOrTerminal(request, callDigest, path, accepted);
|
|
52696
|
+
}
|
|
52697
|
+
const existing = getProjectByPath(this.db, path) ?? getProjectByExactId3(this.db, targetId(this.capabilityValue, request.target_selector));
|
|
52698
|
+
if (existing) {
|
|
52699
|
+
return this.terminal(request, callDigest, "target_preexists", {
|
|
52700
|
+
target_id: existing.id
|
|
52701
|
+
});
|
|
52702
|
+
}
|
|
52703
|
+
const now2 = this.now();
|
|
52704
|
+
const insertedBinding = insertBinding(this.db, request, callDigest, now2);
|
|
52705
|
+
if (!lockBinding(this.db, this.capabilityValue, request.target_selector)) {
|
|
52706
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "registration binding disappeared before it could be locked");
|
|
52707
|
+
}
|
|
52708
|
+
const ownedBinding = getBinding(this.db, this.capabilityValue, request.target_selector);
|
|
52709
|
+
if (!ownedBinding || ownedBinding.normalized_call_digest !== callDigest) {
|
|
52710
|
+
return this.terminal(request, callDigest, "target_selector_preexists", {
|
|
52711
|
+
target_id: ownedBinding?.target_id
|
|
52712
|
+
});
|
|
52713
|
+
}
|
|
52714
|
+
const acceptedAfterBinding = getAcceptedReceipt(this.db, request);
|
|
52715
|
+
if (acceptedAfterBinding) {
|
|
52716
|
+
return this.duplicateForwardOrTerminal(request, callDigest, path, acceptedAfterBinding);
|
|
52717
|
+
}
|
|
52718
|
+
if (!insertedBinding || ownedBinding.state !== "pending") {
|
|
52719
|
+
return this.terminal(request, callDigest, "target_selector_preexists", {
|
|
52720
|
+
target_id: ownedBinding.target_id
|
|
52721
|
+
});
|
|
52722
|
+
}
|
|
52723
|
+
const id = targetId(this.capabilityValue, request.target_selector);
|
|
52724
|
+
this.writeBoundary("before_object_write", request);
|
|
52725
|
+
try {
|
|
52726
|
+
this.db.run(`
|
|
52727
|
+
INSERT INTO projects (
|
|
52728
|
+
id, name, path, description, memory_prefix, created_at, updated_at
|
|
52729
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
52730
|
+
`, id, request.project_name, path, null, request.project_slug, now2, now2);
|
|
52731
|
+
} catch (cause) {
|
|
52732
|
+
throw new WriteBoundaryError("before_object_write", cause);
|
|
52733
|
+
}
|
|
52734
|
+
this.writeBoundary("after_object_write", request);
|
|
52735
|
+
const project = getProjectByExactId3(this.db, id);
|
|
52736
|
+
if (!project || project.path !== path) {
|
|
52737
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "project write did not read back by its full id");
|
|
52738
|
+
}
|
|
52739
|
+
const record = projectRecord(this.db, project);
|
|
52740
|
+
const acceptedReceipt = makeReceipt2(request, this.capabilityValue, callDigest, now2, {
|
|
52741
|
+
outcome: "accepted",
|
|
52742
|
+
target_id: record.target_id,
|
|
52743
|
+
result_revision: record.revision,
|
|
52744
|
+
result_digest: record.digest,
|
|
52745
|
+
created_by_operation: true
|
|
52746
|
+
});
|
|
52747
|
+
this.writeBoundary("before_receipt_write", request);
|
|
52748
|
+
let inserted;
|
|
52749
|
+
try {
|
|
52750
|
+
inserted = insertReceipt2(this.db, acceptedReceipt);
|
|
52751
|
+
} catch (cause) {
|
|
52752
|
+
throw new WriteBoundaryError("before_receipt_write", cause);
|
|
52753
|
+
}
|
|
52754
|
+
this.writeBoundary("after_receipt_write", request);
|
|
52755
|
+
setBindingAccepted(this.db, request, inserted, now2);
|
|
52756
|
+
return inserted;
|
|
52757
|
+
});
|
|
52758
|
+
} catch (error) {
|
|
52759
|
+
if (!(error instanceof WriteBoundaryError))
|
|
52760
|
+
throw error;
|
|
52761
|
+
stored = this.db.transaction(() => this.terminal(request, callDigest, `write_failed:${error.point}`));
|
|
52762
|
+
}
|
|
52763
|
+
this.fault("after_commit", request);
|
|
52764
|
+
const receipt = publicReceipt(stored);
|
|
52765
|
+
assertWithinBounds(receipt, request, startedAt);
|
|
52766
|
+
return receipt;
|
|
52767
|
+
}
|
|
52768
|
+
async readExact(request) {
|
|
52769
|
+
const startedAt = Date.now();
|
|
52770
|
+
assertBounds(request);
|
|
52771
|
+
if (request.resource_kind !== "project") {
|
|
52772
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "resource_kind must be project");
|
|
52773
|
+
}
|
|
52774
|
+
requireString(request.target_id, "target_id", {
|
|
52775
|
+
min: 51,
|
|
52776
|
+
max: 51,
|
|
52777
|
+
pattern: PROJECT_ID_PATTERN
|
|
52778
|
+
});
|
|
52779
|
+
const path = ownedPath(request.target);
|
|
52780
|
+
const project = getProjectByExactId3(this.db, request.target_id);
|
|
52781
|
+
if (!project || project.path !== path) {
|
|
52782
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_RECORD_NOT_FOUND", "exact full-id project readback did not resolve the owned path");
|
|
52783
|
+
}
|
|
52784
|
+
const record = projectRecord(this.db, project);
|
|
52785
|
+
assertWithinBounds(record, request, startedAt);
|
|
52786
|
+
return record;
|
|
52787
|
+
}
|
|
52788
|
+
async lookupReceipt(request) {
|
|
52789
|
+
const startedAt = Date.now();
|
|
52790
|
+
assertLookup(request, this.capabilityValue);
|
|
52791
|
+
const receipt = getReceiptForLookup(this.db, request);
|
|
52792
|
+
if (!receipt || request.target_id !== undefined && receipt.target_id !== request.target_id) {
|
|
52793
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND", "exact immutable terminal receipt was not found");
|
|
52794
|
+
}
|
|
52795
|
+
return withResponseControl(publicReceipt(receipt), request, startedAt);
|
|
52796
|
+
}
|
|
52797
|
+
async compensate(request) {
|
|
52798
|
+
const startedAt = Date.now();
|
|
52799
|
+
const acceptedPublic = assertInverseRequest(request, this.capabilityValue);
|
|
52800
|
+
const callDigest = normalizedCallDigest(request);
|
|
52801
|
+
const storedAccepted = getStoredReceipt(this.db, acceptedPublic.receipt_id);
|
|
52802
|
+
if (!storedAccepted || !sameReceipt(storedAccepted, acceptedPublic)) {
|
|
52803
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND", "accepted forward receipt is not stored immutably in this authority");
|
|
52804
|
+
}
|
|
52805
|
+
const path = ownedPath(request.target);
|
|
52806
|
+
let stored;
|
|
52807
|
+
try {
|
|
52808
|
+
stored = this.db.transaction(() => {
|
|
52809
|
+
const exact = getReceiptForLookup(this.db, {
|
|
52810
|
+
...request,
|
|
52811
|
+
authority: "mementos",
|
|
52812
|
+
max_items: 1
|
|
52813
|
+
});
|
|
52814
|
+
if (exact?.outcome === "terminal_nonacceptance")
|
|
52815
|
+
return exact;
|
|
52816
|
+
if (!lockBinding(this.db, this.capabilityValue, storedAccepted.target_selector)) {
|
|
52817
|
+
return this.terminal(request, callDigest, "accepted_binding_missing", {
|
|
52818
|
+
target_id: storedAccepted.target_id,
|
|
52819
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
52820
|
+
});
|
|
52821
|
+
}
|
|
52822
|
+
const binding = getBinding(this.db, this.capabilityValue, storedAccepted.target_selector);
|
|
52823
|
+
if (!binding || binding.accepted_receipt_id !== storedAccepted.receipt_id) {
|
|
52824
|
+
return this.terminal(request, callDigest, "accepted_binding_replaced", {
|
|
52825
|
+
target_id: storedAccepted.target_id,
|
|
52826
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
52827
|
+
});
|
|
52828
|
+
}
|
|
52829
|
+
const prior = getAcceptedReceipt(this.db, request);
|
|
52830
|
+
if (prior) {
|
|
52831
|
+
if (prior.normalized_call_digest !== callDigest || prior.accepted_receipt_id !== storedAccepted.receipt_id) {
|
|
52832
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_IDEMPOTENCY_MISMATCH", "inverse operation/step identity is bound to a different request");
|
|
52833
|
+
}
|
|
52834
|
+
return insertReceipt2(this.db, makeReceipt2(request, this.capabilityValue, callDigest, this.now(), {
|
|
52835
|
+
outcome: "duplicate_of_accepted",
|
|
52836
|
+
target_id: prior.target_id,
|
|
52837
|
+
result_revision: prior.result_revision,
|
|
52838
|
+
result_digest: prior.result_digest,
|
|
52839
|
+
duplicate_of_receipt_id: prior.receipt_id,
|
|
52840
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
52841
|
+
}));
|
|
52842
|
+
}
|
|
52843
|
+
if (binding.state !== "accepted") {
|
|
52844
|
+
return this.terminal(request, callDigest, "accepted_binding_not_active", {
|
|
52845
|
+
target_id: storedAccepted.target_id,
|
|
52846
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
52847
|
+
});
|
|
52848
|
+
}
|
|
52849
|
+
const project = getProjectByExactId3(this.db, storedAccepted.target_id);
|
|
52850
|
+
if (!project) {
|
|
52851
|
+
return this.terminal(request, callDigest, "target_missing", {
|
|
52852
|
+
target_id: storedAccepted.target_id,
|
|
52853
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
52854
|
+
});
|
|
52855
|
+
}
|
|
52856
|
+
if (project.path !== path) {
|
|
52857
|
+
return this.terminal(request, callDigest, "target_drifted", {
|
|
52858
|
+
target_id: storedAccepted.target_id,
|
|
52859
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
52860
|
+
});
|
|
52861
|
+
}
|
|
52862
|
+
const references = mementosProjectReferenceCounts(this.db, project.id);
|
|
52863
|
+
if (hasMementosProjectReferences(references)) {
|
|
52864
|
+
return this.terminal(request, callDigest, "target_has_dependents", {
|
|
52865
|
+
target_id: storedAccepted.target_id,
|
|
52866
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
52867
|
+
});
|
|
52868
|
+
}
|
|
52869
|
+
const current = projectRecord(this.db, project, references);
|
|
52870
|
+
if (current.revision !== storedAccepted.result_revision || current.digest !== storedAccepted.result_digest) {
|
|
52871
|
+
return this.terminal(request, callDigest, "target_drifted", {
|
|
52872
|
+
target_id: storedAccepted.target_id,
|
|
52873
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
52874
|
+
});
|
|
52875
|
+
}
|
|
52876
|
+
this.writeBoundary("before_object_write", request);
|
|
52877
|
+
let deleted;
|
|
52878
|
+
try {
|
|
52879
|
+
deleted = deleteMementosProjectIfUnreferenced(this.db, storedAccepted.target_id);
|
|
52880
|
+
} catch (cause) {
|
|
52881
|
+
throw new WriteBoundaryError("before_object_write", cause);
|
|
52882
|
+
}
|
|
52883
|
+
if (deleted !== 1) {
|
|
52884
|
+
const remaining = getProjectByExactId3(this.db, storedAccepted.target_id);
|
|
52885
|
+
if (remaining && hasMementosProjectReferences(mementosProjectReferenceCounts(this.db, storedAccepted.target_id))) {
|
|
52886
|
+
return this.terminal(request, callDigest, "target_has_dependents", {
|
|
52887
|
+
target_id: storedAccepted.target_id,
|
|
52888
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
52889
|
+
});
|
|
52890
|
+
}
|
|
52891
|
+
throw new WriteBoundaryError("before_object_write", new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "conditional inverse did not delete exactly one unreferenced project"));
|
|
52892
|
+
}
|
|
52893
|
+
this.writeBoundary("after_object_write", request);
|
|
52894
|
+
const inverseRecord = {
|
|
52895
|
+
target_id: storedAccepted.target_id,
|
|
52896
|
+
revision: "absent",
|
|
52897
|
+
digest: digestMementosProjectRegistrationValue({
|
|
52898
|
+
target_id: storedAccepted.target_id,
|
|
52899
|
+
accepted_receipt_id: storedAccepted.receipt_id,
|
|
52900
|
+
absent: true
|
|
52901
|
+
})
|
|
52902
|
+
};
|
|
52903
|
+
const inverseReceipt = makeReceipt2(request, this.capabilityValue, callDigest, this.now(), {
|
|
52904
|
+
outcome: "accepted",
|
|
52905
|
+
target_id: inverseRecord.target_id,
|
|
52906
|
+
result_revision: inverseRecord.revision,
|
|
52907
|
+
result_digest: inverseRecord.digest,
|
|
52908
|
+
accepted_receipt_id: storedAccepted.receipt_id,
|
|
52909
|
+
created_by_operation: false
|
|
52910
|
+
});
|
|
52911
|
+
this.writeBoundary("before_receipt_write", request);
|
|
52912
|
+
let inserted;
|
|
52913
|
+
try {
|
|
52914
|
+
inserted = insertReceipt2(this.db, inverseReceipt);
|
|
52915
|
+
} catch (cause) {
|
|
52916
|
+
throw new WriteBoundaryError("before_receipt_write", cause);
|
|
52917
|
+
}
|
|
52918
|
+
this.writeBoundary("after_receipt_write", request);
|
|
52919
|
+
setBindingRemoved(this.db, storedAccepted, inserted.receipt_id, this.now());
|
|
52920
|
+
return inserted;
|
|
52921
|
+
});
|
|
52922
|
+
} catch (error) {
|
|
52923
|
+
if (!(error instanceof WriteBoundaryError))
|
|
52924
|
+
throw error;
|
|
52925
|
+
stored = this.db.transaction(() => this.terminal(request, callDigest, `write_failed:${error.point}`, {
|
|
52926
|
+
target_id: storedAccepted.target_id,
|
|
52927
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
52928
|
+
}));
|
|
52929
|
+
}
|
|
52930
|
+
this.fault("after_commit", request);
|
|
52931
|
+
const receipt = publicReceipt(stored);
|
|
52932
|
+
assertWithinBounds(receipt, request, startedAt);
|
|
52933
|
+
return receipt;
|
|
52934
|
+
}
|
|
52935
|
+
async verifyInverse(request) {
|
|
52936
|
+
const startedAt = Date.now();
|
|
52937
|
+
const accepted = assertInverseRequest(request, this.capabilityValue);
|
|
52938
|
+
const storedAccepted = getStoredReceipt(this.db, accepted.receipt_id);
|
|
52939
|
+
if (!storedAccepted || !sameReceipt(storedAccepted, accepted)) {
|
|
52940
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND", "accepted forward receipt is not stored immutably in this authority");
|
|
52941
|
+
}
|
|
52942
|
+
const inverse = getAcceptedReceipt(this.db, request);
|
|
52943
|
+
if (!inverse || inverse.outcome !== "accepted" || inverse.accepted_receipt_id !== accepted.receipt_id || inverse.result_revision !== "absent" || !inverse.result_digest) {
|
|
52944
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND", "accepted conditional inverse receipt was not found");
|
|
52945
|
+
}
|
|
52946
|
+
if (getProjectByExactId3(this.db, accepted.target_id)) {
|
|
52947
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "inverse verification found the accepted target still present");
|
|
52948
|
+
}
|
|
52949
|
+
const verification = {
|
|
52950
|
+
target_id: accepted.target_id,
|
|
52951
|
+
accepted_receipt_id: accepted.receipt_id,
|
|
52952
|
+
absent: true,
|
|
52953
|
+
digest: digestMementosProjectRegistrationValue({
|
|
52954
|
+
target_id: accepted.target_id,
|
|
52955
|
+
accepted_receipt_id: accepted.receipt_id,
|
|
52956
|
+
absent: true
|
|
52957
|
+
})
|
|
52958
|
+
};
|
|
52959
|
+
assertWithinBounds(verification, request, startedAt);
|
|
52960
|
+
if (verification.digest !== inverse.result_digest) {
|
|
52961
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "inverse verification digest does not match its immutable receipt");
|
|
52962
|
+
}
|
|
52963
|
+
return verification;
|
|
52964
|
+
}
|
|
52965
|
+
}
|
|
52966
|
+
function createMementosProjectRegistrationAuthority(db, options = {}) {
|
|
52967
|
+
return new PackageOwnedMementosProjectRegistrationAuthority(db, options);
|
|
52968
|
+
}
|
|
52969
|
+
var createLocalMementosProjectRegistrationAuthority = createMementosProjectRegistrationAuthority;
|
|
52970
|
+
// src/project-registration/http.ts
|
|
52971
|
+
var JSON_HEADERS = { "Content-Type": "application/json" };
|
|
52972
|
+
|
|
52973
|
+
class WirePathHandle {
|
|
52974
|
+
absolutePath;
|
|
52975
|
+
constructor(absolutePath) {
|
|
52976
|
+
this.absolutePath = absolutePath;
|
|
52977
|
+
}
|
|
52978
|
+
withOwnedPath(consumer) {
|
|
52979
|
+
return consumer(this.absolutePath);
|
|
52980
|
+
}
|
|
52981
|
+
}
|
|
52982
|
+
function json(body, status = 200) {
|
|
52983
|
+
return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS });
|
|
52984
|
+
}
|
|
52985
|
+
function errorStatus(error) {
|
|
52986
|
+
switch (error.code) {
|
|
52987
|
+
case "MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT":
|
|
52988
|
+
case "MEMENTOS_PROJECT_REGISTRATION_INVALID_BOUNDS":
|
|
52989
|
+
case "MEMENTOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH":
|
|
52990
|
+
case "MEMENTOS_PROJECT_REGISTRATION_DIGEST_MISMATCH":
|
|
52991
|
+
case "MEMENTOS_PROJECT_REGISTRATION_IDEMPOTENCY_MISMATCH":
|
|
52992
|
+
case "MEMENTOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED":
|
|
52993
|
+
return 400;
|
|
52994
|
+
case "MEMENTOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND":
|
|
52995
|
+
case "MEMENTOS_PROJECT_REGISTRATION_RECORD_NOT_FOUND":
|
|
52996
|
+
case "MEMENTOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND":
|
|
52997
|
+
return 404;
|
|
52998
|
+
case "MEMENTOS_PROJECT_REGISTRATION_RESPONSE_TOO_LARGE":
|
|
52999
|
+
return 413;
|
|
53000
|
+
case "MEMENTOS_PROJECT_REGISTRATION_TIME_BUDGET_EXCEEDED":
|
|
53001
|
+
return 408;
|
|
53002
|
+
case "MEMENTOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE":
|
|
53003
|
+
return 503;
|
|
53004
|
+
default:
|
|
53005
|
+
return 409;
|
|
53006
|
+
}
|
|
53007
|
+
}
|
|
53008
|
+
async function readJson(request) {
|
|
53009
|
+
try {
|
|
53010
|
+
const value = await request.json();
|
|
53011
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
53012
|
+
} catch {
|
|
53013
|
+
return null;
|
|
53014
|
+
}
|
|
53015
|
+
}
|
|
53016
|
+
function fromWireRequest(body) {
|
|
53017
|
+
const { canonical_path: canonicalPath, ...request } = body;
|
|
53018
|
+
if (typeof canonicalPath !== "string") {
|
|
53019
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "canonical_path is required on the private registration transport");
|
|
53020
|
+
}
|
|
53021
|
+
return {
|
|
53022
|
+
...request,
|
|
53023
|
+
target: new WirePathHandle(canonicalPath)
|
|
53024
|
+
};
|
|
53025
|
+
}
|
|
53026
|
+
function fromWireReadRequest(body) {
|
|
53027
|
+
const { canonical_path: canonicalPath, ...request } = body;
|
|
53028
|
+
if (typeof canonicalPath !== "string") {
|
|
53029
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "canonical_path is required on the private exact-read transport");
|
|
53030
|
+
}
|
|
53031
|
+
return {
|
|
53032
|
+
...request,
|
|
53033
|
+
target: new WirePathHandle(canonicalPath)
|
|
53034
|
+
};
|
|
53035
|
+
}
|
|
53036
|
+
async function handleMementosProjectRegistrationHttpRequest(request, url, authority, basePath = "/v1/project-registration") {
|
|
53037
|
+
const path = url.pathname;
|
|
53038
|
+
if (path !== basePath && !path.startsWith(`${basePath}/`))
|
|
53039
|
+
return null;
|
|
53040
|
+
const action = path.slice(basePath.length).split("/").filter(Boolean).join("/");
|
|
53041
|
+
const method = request.method.toUpperCase();
|
|
53042
|
+
try {
|
|
53043
|
+
if ((action === "" || action === "capability") && method === "GET") {
|
|
53044
|
+
return json({ capability: await authority.capability() });
|
|
53045
|
+
}
|
|
53046
|
+
if (method !== "POST")
|
|
53047
|
+
return json({ error: "method not allowed" }, 405);
|
|
53048
|
+
const body = await readJson(request);
|
|
53049
|
+
if (!body) {
|
|
53050
|
+
return json({
|
|
53051
|
+
error: "invalid JSON body",
|
|
53052
|
+
code: "MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT"
|
|
53053
|
+
}, 400);
|
|
53054
|
+
}
|
|
53055
|
+
if (action === "create") {
|
|
53056
|
+
return json({ receipt: await authority.create(fromWireRequest(body)) }, 201);
|
|
53057
|
+
}
|
|
53058
|
+
if (action === "receipts/lookup") {
|
|
53059
|
+
return json(await authority.lookupReceipt(body));
|
|
53060
|
+
}
|
|
53061
|
+
if (action === "read-exact") {
|
|
53062
|
+
return json({ record: await authority.readExact(fromWireReadRequest(body)) });
|
|
53063
|
+
}
|
|
53064
|
+
if (action === "compensate") {
|
|
53065
|
+
return json({ receipt: await authority.compensate(fromWireRequest(body)) }, 201);
|
|
53066
|
+
}
|
|
53067
|
+
if (action === "verify-inverse") {
|
|
53068
|
+
return json({ verification: await authority.verifyInverse(fromWireRequest(body)) });
|
|
53069
|
+
}
|
|
53070
|
+
return json({
|
|
53071
|
+
error: "unknown Mementos project-registration route",
|
|
53072
|
+
code: "MEMENTOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND"
|
|
53073
|
+
}, 404);
|
|
53074
|
+
} catch (cause) {
|
|
53075
|
+
if (cause instanceof MementosProjectRegistrationError) {
|
|
53076
|
+
return json({
|
|
53077
|
+
error: cause.message,
|
|
53078
|
+
code: cause.code,
|
|
53079
|
+
details: cause.details,
|
|
53080
|
+
authoritative: true
|
|
53081
|
+
}, errorStatus(cause));
|
|
53082
|
+
}
|
|
53083
|
+
return json({
|
|
53084
|
+
error: cause instanceof Error ? cause.message : "internal registration error",
|
|
53085
|
+
code: "MEMENTOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE"
|
|
53086
|
+
}, 500);
|
|
53087
|
+
}
|
|
53088
|
+
}
|
|
53089
|
+
function extractPath(target) {
|
|
53090
|
+
if (!target || typeof target.withOwnedPath !== "function") {
|
|
53091
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "target must be a package-owned path handle");
|
|
53092
|
+
}
|
|
53093
|
+
return target.withOwnedPath((absolutePath) => absolutePath);
|
|
53094
|
+
}
|
|
53095
|
+
function toWireRequest(request) {
|
|
53096
|
+
const { target, ...serializable } = request;
|
|
53097
|
+
return {
|
|
53098
|
+
...serializable,
|
|
53099
|
+
canonical_path: extractPath(target)
|
|
53100
|
+
};
|
|
53101
|
+
}
|
|
53102
|
+
|
|
53103
|
+
class MementosProjectRegistrationHttpClient {
|
|
53104
|
+
authority = "mementos";
|
|
53105
|
+
baseUrl;
|
|
53106
|
+
fetchImpl;
|
|
53107
|
+
headers;
|
|
53108
|
+
constructor(options) {
|
|
53109
|
+
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
53110
|
+
this.fetchImpl = options.fetch ?? globalThis.fetch;
|
|
53111
|
+
this.headers = {
|
|
53112
|
+
...options.headers,
|
|
53113
|
+
...options.apiKey ? { Authorization: `Bearer ${options.apiKey}` } : {},
|
|
53114
|
+
"Content-Type": "application/json"
|
|
53115
|
+
};
|
|
53116
|
+
}
|
|
53117
|
+
async request(action, init = {}) {
|
|
53118
|
+
const response = await this.fetchImpl(`${this.baseUrl}/v1/project-registration${action}`, {
|
|
53119
|
+
...init,
|
|
53120
|
+
headers: { ...this.headers, ...init.headers ?? {} }
|
|
53121
|
+
});
|
|
53122
|
+
let body;
|
|
53123
|
+
try {
|
|
53124
|
+
body = await response.json();
|
|
53125
|
+
} catch {
|
|
53126
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE", `Mementos project registration HTTP ${response.status} returned non-JSON`);
|
|
53127
|
+
}
|
|
53128
|
+
if (!response.ok) {
|
|
53129
|
+
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"] : {});
|
|
53130
|
+
}
|
|
53131
|
+
return body;
|
|
53132
|
+
}
|
|
53133
|
+
async capability() {
|
|
53134
|
+
const body = await this.request("/capability");
|
|
53135
|
+
return body.capability;
|
|
53136
|
+
}
|
|
53137
|
+
async create(request) {
|
|
53138
|
+
const body = await this.request("/create", { method: "POST", body: JSON.stringify(toWireRequest(request)) });
|
|
53139
|
+
return body.receipt;
|
|
53140
|
+
}
|
|
53141
|
+
async readExact(request) {
|
|
53142
|
+
const { target, ...serializable } = request;
|
|
53143
|
+
const body = await this.request("/read-exact", {
|
|
53144
|
+
method: "POST",
|
|
53145
|
+
body: JSON.stringify({ ...serializable, canonical_path: extractPath(target) })
|
|
53146
|
+
});
|
|
53147
|
+
return body.record;
|
|
53148
|
+
}
|
|
53149
|
+
async lookupReceipt(request) {
|
|
53150
|
+
return this.request("/receipts/lookup", { method: "POST", body: JSON.stringify(request) });
|
|
53151
|
+
}
|
|
53152
|
+
async compensate(request) {
|
|
53153
|
+
const body = await this.request("/compensate", { method: "POST", body: JSON.stringify(toWireRequest(request)) });
|
|
53154
|
+
return body.receipt;
|
|
53155
|
+
}
|
|
53156
|
+
async verifyInverse(request) {
|
|
53157
|
+
const body = await this.request("/verify-inverse", {
|
|
53158
|
+
method: "POST",
|
|
53159
|
+
body: JSON.stringify(toWireRequest(request))
|
|
53160
|
+
});
|
|
53161
|
+
return body.verification;
|
|
53162
|
+
}
|
|
53163
|
+
}
|
|
53164
|
+
function createMementosProjectRegistrationHttpClient(options) {
|
|
53165
|
+
return new MementosProjectRegistrationHttpClient(options);
|
|
50537
53166
|
}
|
|
50538
53167
|
// src/db/machines.ts
|
|
50539
53168
|
init_database();
|
|
@@ -50828,17 +53457,17 @@ function deleteEntity(id, db) {
|
|
|
50828
53457
|
if (result.changes === 0)
|
|
50829
53458
|
throw new EntityNotFoundError(id);
|
|
50830
53459
|
}
|
|
50831
|
-
function mergeEntities(sourceId,
|
|
53460
|
+
function mergeEntities(sourceId, targetId2, db) {
|
|
50832
53461
|
if (!db && isApiMode()) {
|
|
50833
53462
|
const { data } = apiJson("POST", "/entities/merge", {
|
|
50834
53463
|
source_id: sourceId,
|
|
50835
|
-
target_id:
|
|
53464
|
+
target_id: targetId2
|
|
50836
53465
|
});
|
|
50837
53466
|
return data;
|
|
50838
53467
|
}
|
|
50839
53468
|
const d = db || getDatabase();
|
|
50840
53469
|
const src = getEntity(sourceId, d).id;
|
|
50841
|
-
const tgt = getEntity(
|
|
53470
|
+
const tgt = getEntity(targetId2, d).id;
|
|
50842
53471
|
d.run(`DELETE FROM relations
|
|
50843
53472
|
WHERE source_entity_id = ?
|
|
50844
53473
|
AND EXISTS (
|
|
@@ -51662,9 +54291,9 @@ function formatMementosProjectPanel(panel) {
|
|
|
51662
54291
|
`);
|
|
51663
54292
|
}
|
|
51664
54293
|
// src/lib/config.ts
|
|
51665
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as
|
|
54294
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, readdirSync, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, cpSync as cpSync2 } from "fs";
|
|
51666
54295
|
import { homedir as homedir2 } from "os";
|
|
51667
|
-
import { basename, dirname as
|
|
54296
|
+
import { basename, dirname as dirname3, join as join5, resolve as resolve3 } from "path";
|
|
51668
54297
|
function isInMemoryDb2(path) {
|
|
51669
54298
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
51670
54299
|
}
|
|
@@ -51727,11 +54356,11 @@ function isValidCategory(value) {
|
|
|
51727
54356
|
return VALID_CATEGORIES.includes(value);
|
|
51728
54357
|
}
|
|
51729
54358
|
function loadConfig() {
|
|
51730
|
-
const configPath =
|
|
54359
|
+
const configPath = join5(homeDir(), ".hasna", "mementos", "config.json");
|
|
51731
54360
|
let fileConfig = {};
|
|
51732
54361
|
if (existsSync3(configPath)) {
|
|
51733
54362
|
try {
|
|
51734
|
-
const raw =
|
|
54363
|
+
const raw = readFileSync3(configPath, "utf-8");
|
|
51735
54364
|
fileConfig = JSON.parse(raw);
|
|
51736
54365
|
} catch {}
|
|
51737
54366
|
}
|
|
@@ -51755,13 +54384,13 @@ function loadConfig() {
|
|
|
51755
54384
|
}
|
|
51756
54385
|
function findFileWalkingUp(filename) {
|
|
51757
54386
|
let dir = process.cwd();
|
|
51758
|
-
const legacyHomeMementosDb =
|
|
54387
|
+
const legacyHomeMementosDb = resolve3(homeDir(), ".mementos", "mementos.db");
|
|
51759
54388
|
while (true) {
|
|
51760
|
-
const candidate =
|
|
51761
|
-
if (existsSync3(candidate) &&
|
|
54389
|
+
const candidate = join5(dir, filename);
|
|
54390
|
+
if (existsSync3(candidate) && resolve3(candidate) !== legacyHomeMementosDb) {
|
|
51762
54391
|
return candidate;
|
|
51763
54392
|
}
|
|
51764
|
-
const parent =
|
|
54393
|
+
const parent = dirname3(dir);
|
|
51765
54394
|
if (parent === dir) {
|
|
51766
54395
|
return null;
|
|
51767
54396
|
}
|
|
@@ -51771,10 +54400,10 @@ function findFileWalkingUp(filename) {
|
|
|
51771
54400
|
function findGitRoot2() {
|
|
51772
54401
|
let dir = process.cwd();
|
|
51773
54402
|
while (true) {
|
|
51774
|
-
if (existsSync3(
|
|
54403
|
+
if (existsSync3(join5(dir, ".git"))) {
|
|
51775
54404
|
return dir;
|
|
51776
54405
|
}
|
|
51777
|
-
const parent =
|
|
54406
|
+
const parent = dirname3(dir);
|
|
51778
54407
|
if (parent === dir) {
|
|
51779
54408
|
return null;
|
|
51780
54409
|
}
|
|
@@ -51782,17 +54411,17 @@ function findGitRoot2() {
|
|
|
51782
54411
|
}
|
|
51783
54412
|
}
|
|
51784
54413
|
function profilesDir() {
|
|
51785
|
-
return
|
|
54414
|
+
return join5(homeDir(), ".hasna", "mementos", "profiles");
|
|
51786
54415
|
}
|
|
51787
54416
|
function globalConfigPath() {
|
|
51788
|
-
return
|
|
54417
|
+
return join5(homeDir(), ".hasna", "mementos", "config.json");
|
|
51789
54418
|
}
|
|
51790
54419
|
function readGlobalConfig() {
|
|
51791
54420
|
const p = globalConfigPath();
|
|
51792
54421
|
if (!existsSync3(p))
|
|
51793
54422
|
return {};
|
|
51794
54423
|
try {
|
|
51795
|
-
return JSON.parse(
|
|
54424
|
+
return JSON.parse(readFileSync3(p, "utf-8"));
|
|
51796
54425
|
} catch {
|
|
51797
54426
|
return {};
|
|
51798
54427
|
}
|
|
@@ -51802,7 +54431,7 @@ function readGlobalConfigForWrite() {
|
|
|
51802
54431
|
if (!existsSync3(p))
|
|
51803
54432
|
return {};
|
|
51804
54433
|
try {
|
|
51805
|
-
const data = JSON.parse(
|
|
54434
|
+
const data = JSON.parse(readFileSync3(p, "utf-8"));
|
|
51806
54435
|
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
51807
54436
|
throw new Error("expected a JSON object");
|
|
51808
54437
|
}
|
|
@@ -51814,7 +54443,7 @@ function readGlobalConfigForWrite() {
|
|
|
51814
54443
|
}
|
|
51815
54444
|
function writeGlobalConfig(data) {
|
|
51816
54445
|
const p = globalConfigPath();
|
|
51817
|
-
ensureDir2(
|
|
54446
|
+
ensureDir2(dirname3(p));
|
|
51818
54447
|
writeFileSync3(p, JSON.stringify(data, null, 2), "utf-8");
|
|
51819
54448
|
}
|
|
51820
54449
|
function getActiveProfile() {
|
|
@@ -51840,7 +54469,7 @@ function listProfiles() {
|
|
|
51840
54469
|
return readdirSync(dir).filter((f) => f.endsWith(".db")).map((f) => basename(f, ".db")).sort();
|
|
51841
54470
|
}
|
|
51842
54471
|
function deleteProfile(name) {
|
|
51843
|
-
const dbPath =
|
|
54472
|
+
const dbPath = join5(profilesDir(), `${name}.db`);
|
|
51844
54473
|
if (!existsSync3(dbPath))
|
|
51845
54474
|
return false;
|
|
51846
54475
|
unlinkSync2(dbPath);
|
|
@@ -51850,10 +54479,10 @@ function deleteProfile(name) {
|
|
|
51850
54479
|
}
|
|
51851
54480
|
function getDbPath2() {
|
|
51852
54481
|
const _home = homeDir();
|
|
51853
|
-
const _newDir =
|
|
51854
|
-
const _oldDir =
|
|
54482
|
+
const _newDir = join5(_home, ".hasna", "mementos");
|
|
54483
|
+
const _oldDir = join5(_home, ".mementos");
|
|
51855
54484
|
if (!existsSync3(_newDir) && existsSync3(_oldDir)) {
|
|
51856
|
-
mkdirSync3(
|
|
54485
|
+
mkdirSync3(join5(_home, ".hasna"), { recursive: true });
|
|
51857
54486
|
cpSync2(_oldDir, _newDir, { recursive: true });
|
|
51858
54487
|
}
|
|
51859
54488
|
const envDbPath = process.env["HASNA_MEMENTOS_DB_PATH"] ?? process.env["MEMENTOS_DB_PATH"];
|
|
@@ -51861,31 +54490,31 @@ function getDbPath2() {
|
|
|
51861
54490
|
if (isInMemoryDb2(envDbPath)) {
|
|
51862
54491
|
return envDbPath;
|
|
51863
54492
|
}
|
|
51864
|
-
const resolved =
|
|
51865
|
-
ensureDir2(
|
|
54493
|
+
const resolved = resolve3(envDbPath);
|
|
54494
|
+
ensureDir2(dirname3(resolved));
|
|
51866
54495
|
return resolved;
|
|
51867
54496
|
}
|
|
51868
54497
|
const profile = getActiveProfile();
|
|
51869
54498
|
if (profile) {
|
|
51870
|
-
const profilePath =
|
|
51871
|
-
ensureDir2(
|
|
54499
|
+
const profilePath = join5(profilesDir(), `${profile}.db`);
|
|
54500
|
+
ensureDir2(dirname3(profilePath));
|
|
51872
54501
|
return profilePath;
|
|
51873
54502
|
}
|
|
51874
54503
|
const dbScope = process.env["MEMENTOS_DB_SCOPE"];
|
|
51875
54504
|
if (dbScope === "project") {
|
|
51876
54505
|
const gitRoot = findGitRoot2();
|
|
51877
54506
|
if (gitRoot) {
|
|
51878
|
-
const dbPath =
|
|
51879
|
-
ensureDir2(
|
|
54507
|
+
const dbPath = join5(gitRoot, ".mementos", "mementos.db");
|
|
54508
|
+
ensureDir2(dirname3(dbPath));
|
|
51880
54509
|
return dbPath;
|
|
51881
54510
|
}
|
|
51882
54511
|
}
|
|
51883
|
-
const found = findFileWalkingUp(
|
|
54512
|
+
const found = findFileWalkingUp(join5(".mementos", "mementos.db"));
|
|
51884
54513
|
if (found) {
|
|
51885
54514
|
return found;
|
|
51886
54515
|
}
|
|
51887
|
-
const fallback =
|
|
51888
|
-
ensureDir2(
|
|
54516
|
+
const fallback = join5(homeDir(), ".hasna", "mementos", "mementos.db");
|
|
54517
|
+
ensureDir2(dirname3(fallback));
|
|
51889
54518
|
return fallback;
|
|
51890
54519
|
}
|
|
51891
54520
|
function ensureDir2(dir) {
|
|
@@ -52270,18 +54899,18 @@ function runCleanup(config, db) {
|
|
|
52270
54899
|
return { expired, evicted, archived, unused_archived, deprioritized };
|
|
52271
54900
|
}
|
|
52272
54901
|
// src/lib/sync.ts
|
|
52273
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as
|
|
54902
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
52274
54903
|
import { homedir as homedir3 } from "os";
|
|
52275
|
-
import { join as
|
|
54904
|
+
import { join as join6 } from "path";
|
|
52276
54905
|
function getAgentSyncDir(agentName) {
|
|
52277
|
-
const dir =
|
|
54906
|
+
const dir = join6(homedir3(), ".hasna", "mementos", "agents", agentName);
|
|
52278
54907
|
if (!existsSync4(dir)) {
|
|
52279
54908
|
mkdirSync4(dir, { recursive: true });
|
|
52280
54909
|
}
|
|
52281
54910
|
return dir;
|
|
52282
54911
|
}
|
|
52283
54912
|
function setHighWaterMark(agentDir, timestamp) {
|
|
52284
|
-
const markFile =
|
|
54913
|
+
const markFile = join6(agentDir, ".highwatermark");
|
|
52285
54914
|
writeFileSync4(markFile, timestamp, "utf-8");
|
|
52286
54915
|
}
|
|
52287
54916
|
function resolveConflict(local, remote, resolution) {
|
|
@@ -52302,7 +54931,7 @@ function pushMemories(agentName, agentId, projectId, db) {
|
|
|
52302
54931
|
status: "active",
|
|
52303
54932
|
limit: 1e4
|
|
52304
54933
|
}, db);
|
|
52305
|
-
const outFile =
|
|
54934
|
+
const outFile = join6(agentDir, "memories.json");
|
|
52306
54935
|
writeFileSync4(outFile, JSON.stringify(memories, null, 2), "utf-8");
|
|
52307
54936
|
if (memories.length > 0) {
|
|
52308
54937
|
const latest = memories.reduce((a, b) => new Date(a.updated_at).getTime() > new Date(b.updated_at).getTime() ? a : b);
|
|
@@ -52312,11 +54941,11 @@ function pushMemories(agentName, agentId, projectId, db) {
|
|
|
52312
54941
|
}
|
|
52313
54942
|
function pullMemories(agentName, conflictResolution = "prefer-newer", db) {
|
|
52314
54943
|
const agentDir = getAgentSyncDir(agentName);
|
|
52315
|
-
const inFile =
|
|
54944
|
+
const inFile = join6(agentDir, "memories.json");
|
|
52316
54945
|
if (!existsSync4(inFile)) {
|
|
52317
54946
|
return { pulled: 0, conflicts: 0 };
|
|
52318
54947
|
}
|
|
52319
|
-
const raw =
|
|
54948
|
+
const raw = readFileSync4(inFile, "utf-8");
|
|
52320
54949
|
let remoteMemories;
|
|
52321
54950
|
try {
|
|
52322
54951
|
remoteMemories = JSON.parse(raw);
|
|
@@ -54873,17 +57502,17 @@ var gatherTrainingData = async (options = {}) => {
|
|
|
54873
57502
|
};
|
|
54874
57503
|
};
|
|
54875
57504
|
// src/lib/model-config.ts
|
|
54876
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as
|
|
57505
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
54877
57506
|
import { homedir as homedir4 } from "os";
|
|
54878
|
-
import { join as
|
|
57507
|
+
import { join as join7 } from "path";
|
|
54879
57508
|
var DEFAULT_MODEL = "gpt-4o-mini";
|
|
54880
|
-
var CONFIG_DIR =
|
|
54881
|
-
var CONFIG_PATH =
|
|
57509
|
+
var CONFIG_DIR = join7(homedir4(), ".hasna", "mementos");
|
|
57510
|
+
var CONFIG_PATH = join7(CONFIG_DIR, "config.json");
|
|
54882
57511
|
function readConfig() {
|
|
54883
57512
|
if (!existsSync5(CONFIG_PATH))
|
|
54884
57513
|
return {};
|
|
54885
57514
|
try {
|
|
54886
|
-
const raw =
|
|
57515
|
+
const raw = readFileSync5(CONFIG_PATH, "utf-8");
|
|
54887
57516
|
return JSON.parse(raw);
|
|
54888
57517
|
} catch {
|
|
54889
57518
|
return {};
|
|
@@ -55337,6 +57966,9 @@ export {
|
|
|
55337
57966
|
touchAgent,
|
|
55338
57967
|
syncMemories,
|
|
55339
57968
|
startTaskRunner,
|
|
57969
|
+
sqliteMementosProjectRegistrationSchemaSql,
|
|
57970
|
+
sqliteMementosProjectGuardedUpdateSchemaSql,
|
|
57971
|
+
sqliteMementosMemoryProjectLinkSchemaSql,
|
|
55340
57972
|
shortUuid,
|
|
55341
57973
|
setPrimaryMachine,
|
|
55342
57974
|
setFocus,
|
|
@@ -55347,6 +57979,8 @@ export {
|
|
|
55347
57979
|
saveStorageConfig,
|
|
55348
57980
|
runConsolidation,
|
|
55349
57981
|
runCleanup,
|
|
57982
|
+
rollbackProjectUpdate,
|
|
57983
|
+
rollbackMemoryProjectLink,
|
|
55350
57984
|
resolveProjectId,
|
|
55351
57985
|
resolvePartialId,
|
|
55352
57986
|
resetDatabase,
|
|
@@ -55365,11 +57999,17 @@ export {
|
|
|
55365
57999
|
pullStorageChanges,
|
|
55366
58000
|
providerRegistry,
|
|
55367
58001
|
processConversationTurn,
|
|
58002
|
+
previewProjectUpdate,
|
|
58003
|
+
previewMemoryProjectLink,
|
|
58004
|
+
postgresMementosProjectRegistrationSchemaSql,
|
|
58005
|
+
postgresMementosProjectGuardedUpdateSchemaSql,
|
|
58006
|
+
postgresMementosMemoryProjectLinkSchemaSql,
|
|
55368
58007
|
parseRelationRow,
|
|
55369
58008
|
parseEntityRow2 as parseEntityRow,
|
|
55370
58009
|
now,
|
|
55371
58010
|
mergeEntities,
|
|
55372
58011
|
memoryLockId,
|
|
58012
|
+
mementosProjectReferenceCounts,
|
|
55373
58013
|
loadConfig,
|
|
55374
58014
|
listTasks,
|
|
55375
58015
|
listTaskComments,
|
|
@@ -55386,6 +58026,8 @@ export {
|
|
|
55386
58026
|
linkEntityToMemory,
|
|
55387
58027
|
incrementRecallCount,
|
|
55388
58028
|
heuristicReflectionCritic,
|
|
58029
|
+
hasMementosProjectReferences,
|
|
58030
|
+
handleMementosProjectRegistrationHttpRequest,
|
|
55389
58031
|
getTaskStats,
|
|
55390
58032
|
getTaskRunnerStats,
|
|
55391
58033
|
getTask,
|
|
@@ -55399,11 +58041,13 @@ export {
|
|
|
55399
58041
|
getStorageConfig,
|
|
55400
58042
|
getRelation,
|
|
55401
58043
|
getRelatedEntities,
|
|
58044
|
+
getProjectUpdateReceipt,
|
|
55402
58045
|
getProject,
|
|
55403
58046
|
getPrimaryMachineStartupWarning,
|
|
55404
58047
|
getPrimaryMachineCandidate,
|
|
55405
58048
|
getPrimaryMachine,
|
|
55406
58049
|
getMemoryVersions,
|
|
58050
|
+
getMemoryProjectLinkReceipt,
|
|
55407
58051
|
getMemoryLinks,
|
|
55408
58052
|
getMemoryByKey,
|
|
55409
58053
|
getMemoryBacklinks,
|
|
@@ -55432,6 +58076,8 @@ export {
|
|
|
55432
58076
|
focusFilterSQL,
|
|
55433
58077
|
findPath,
|
|
55434
58078
|
enforceQuotas,
|
|
58079
|
+
digestMementosProjectRegistrationValue,
|
|
58080
|
+
deriveMementosProjectRegistrationIdempotencyKey,
|
|
55435
58081
|
deprioritizeStale,
|
|
55436
58082
|
deleteTaskComment,
|
|
55437
58083
|
deleteTask,
|
|
@@ -55446,7 +58092,10 @@ export {
|
|
|
55446
58092
|
createRelation,
|
|
55447
58093
|
createMemoryLink,
|
|
55448
58094
|
createMemory,
|
|
58095
|
+
createMementosProjectRegistrationHttpClient,
|
|
58096
|
+
createMementosProjectRegistrationAuthority,
|
|
55449
58097
|
createMementosProjectPanel,
|
|
58098
|
+
createLocalMementosProjectRegistrationAuthority,
|
|
55450
58099
|
createEntity,
|
|
55451
58100
|
createAISDKReflectionCritic,
|
|
55452
58101
|
containsSecrets,
|
|
@@ -55457,11 +58106,14 @@ export {
|
|
|
55457
58106
|
cleanExpiredLocks,
|
|
55458
58107
|
checkMemoryWriteLock,
|
|
55459
58108
|
checkLock,
|
|
58109
|
+
canonicalMementosProjectRegistrationJson,
|
|
55460
58110
|
bulkLinkEntities,
|
|
55461
58111
|
bulkDeleteMemories,
|
|
55462
58112
|
buildFocusFilter,
|
|
55463
58113
|
archiveUnused,
|
|
55464
58114
|
archiveStale,
|
|
58115
|
+
applyProjectUpdate,
|
|
58116
|
+
applyMemoryProjectLink,
|
|
55465
58117
|
agentHoldsLock,
|
|
55466
58118
|
addTaskComment,
|
|
55467
58119
|
acquireMemoryWriteLock,
|
|
@@ -55469,15 +58121,28 @@ export {
|
|
|
55469
58121
|
VersionConflictError,
|
|
55470
58122
|
SqliteAdapter,
|
|
55471
58123
|
STORAGE_TABLES,
|
|
58124
|
+
ProjectGuardedUpdateError,
|
|
58125
|
+
ProjectCollisionError,
|
|
55472
58126
|
PgAdapterAsync,
|
|
55473
58127
|
PgAdapter,
|
|
58128
|
+
PackageOwnedMementosProjectRegistrationAuthority,
|
|
58129
|
+
MemoryProjectLinkError,
|
|
55474
58130
|
MemoryNotFoundError,
|
|
55475
58131
|
MemoryLockConflictError,
|
|
55476
58132
|
MemoryInjector,
|
|
55477
58133
|
MemoryExpiredError,
|
|
58134
|
+
MementosProjectRegistrationHttpClient,
|
|
58135
|
+
MementosProjectRegistrationError,
|
|
58136
|
+
MEMORY_PROJECT_LINK_RECEIPT_COLUMNS,
|
|
55478
58137
|
MEMENTOS_STORAGE_TABLES,
|
|
55479
58138
|
MEMENTOS_STORAGE_FALLBACK_ENV,
|
|
55480
58139
|
MEMENTOS_STORAGE_ENV,
|
|
58140
|
+
MEMENTOS_PROJECT_REGISTRATION_SCHEMA_VERSION,
|
|
58141
|
+
MEMENTOS_PROJECT_REGISTRATION_ROUTE,
|
|
58142
|
+
MEMENTOS_PROJECT_REGISTRATION_CALLER_ROUTE,
|
|
58143
|
+
MEMENTOS_PROJECT_REFERENCE_SURFACES,
|
|
58144
|
+
MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE,
|
|
58145
|
+
MEMENTOS_MEMORY_PROJECT_LINK_ROUTE,
|
|
55481
58146
|
InvalidScopeError,
|
|
55482
58147
|
EntityNotFoundError,
|
|
55483
58148
|
DuplicateMemoryError,
|