@hasna/mementos 0.14.74 → 0.14.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +93 -0
- package/dist/cli/commands/agent.d.ts.map +1 -1
- package/dist/cli/commands/info-context.d.ts.map +1 -1
- package/dist/cli/commands/info-stale.d.ts.map +1 -1
- package/dist/cli/commands/io-export.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-crud.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-list.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-recall.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-search.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-tail.d.ts.map +1 -1
- package/dist/cli/commands/project.d.ts.map +1 -1
- package/dist/cli/commands/system-watch.d.ts.map +1 -1
- package/dist/cli/helpers.d.ts +38 -0
- package/dist/cli/helpers.d.ts.map +1 -1
- package/dist/cli/index.js +2077 -299
- package/dist/db/__fixtures__/fail-closed-stub-server.d.ts.map +1 -1
- package/dist/db/agents.d.ts +6 -0
- package/dist/db/agents.d.ts.map +1 -1
- package/dist/db/audit.d.ts +14 -0
- package/dist/db/audit.d.ts.map +1 -1
- package/dist/db/memory-project-link.d.ts +13 -0
- package/dist/db/memory-project-link.d.ts.map +1 -0
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts +0 -6
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/projects.d.ts +22 -1
- package/dist/db/projects.d.ts.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2909 -173
- package/dist/lib/package-version.d.ts +3 -0
- package/dist/lib/package-version.d.ts.map +1 -0
- package/dist/mcp/index.js +524 -10
- package/dist/memory-project-link/index.d.ts +2 -0
- package/dist/memory-project-link/index.d.ts.map +1 -0
- package/dist/memory-project-link/schema.d.ts +5 -0
- package/dist/memory-project-link/schema.d.ts.map +1 -0
- package/dist/project-registration/authority.d.ts +39 -0
- package/dist/project-registration/authority.d.ts.map +1 -0
- package/dist/project-registration/http.d.ts +29 -0
- package/dist/project-registration/http.d.ts.map +1 -0
- package/dist/project-registration/index.d.ts +8 -0
- package/dist/project-registration/index.d.ts.map +1 -0
- package/dist/project-registration/project-references.d.ts +71 -0
- package/dist/project-registration/project-references.d.ts.map +1 -0
- package/dist/project-registration/schema.d.ts +5 -0
- package/dist/project-registration/schema.d.ts.map +1 -0
- package/dist/project-registration/types.d.ts +168 -0
- package/dist/project-registration/types.d.ts.map +1 -0
- package/dist/project-registration.d.ts +2 -0
- package/dist/project-registration.d.ts.map +1 -0
- package/dist/project-registration.js +1496 -0
- package/dist/sdk/index.d.ts +127 -0
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +74 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2667 -149
- package/dist/server/routes/memories.d.ts +1 -0
- package/dist/server/routes/memories.d.ts.map +1 -1
- package/dist/server/routes/memory-project-link.d.ts +2 -0
- package/dist/server/routes/memory-project-link.d.ts.map +1 -0
- package/dist/server/routes/project-registration.d.ts +2 -0
- package/dist/server/routes/project-registration.d.ts.map +1 -0
- package/dist/types/index.d.ts +119 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +9 -3
- package/dashboard/bun.lock +0 -519
package/dist/server/index.js
CHANGED
|
@@ -802,6 +802,201 @@ var init_api_mode = __esm(() => {
|
|
|
802
802
|
};
|
|
803
803
|
});
|
|
804
804
|
|
|
805
|
+
// src/project-registration/schema.ts
|
|
806
|
+
function sqliteMementosProjectRegistrationSchemaSql() {
|
|
807
|
+
return `
|
|
808
|
+
CREATE TABLE IF NOT EXISTS mementos_project_registration_receipts (
|
|
809
|
+
receipt_id TEXT PRIMARY KEY,
|
|
810
|
+
authority TEXT NOT NULL CHECK(authority = 'mementos'),
|
|
811
|
+
route TEXT NOT NULL,
|
|
812
|
+
package_version TEXT NOT NULL,
|
|
813
|
+
authority_id TEXT NOT NULL,
|
|
814
|
+
tenant_id TEXT NOT NULL,
|
|
815
|
+
corpus_id TEXT NOT NULL,
|
|
816
|
+
operation_id TEXT NOT NULL,
|
|
817
|
+
step_id TEXT NOT NULL,
|
|
818
|
+
resource_kind TEXT NOT NULL CHECK(resource_kind = 'project'),
|
|
819
|
+
direction TEXT NOT NULL CHECK(direction IN ('forward', 'inverse')),
|
|
820
|
+
target_selector TEXT NOT NULL,
|
|
821
|
+
idempotency_key TEXT NOT NULL,
|
|
822
|
+
request_digest TEXT NOT NULL,
|
|
823
|
+
precondition_digest TEXT NOT NULL,
|
|
824
|
+
normalized_call_digest TEXT NOT NULL,
|
|
825
|
+
outcome TEXT NOT NULL CHECK(outcome IN (
|
|
826
|
+
'accepted', 'duplicate_of_accepted', 'terminal_nonacceptance'
|
|
827
|
+
)),
|
|
828
|
+
reason TEXT,
|
|
829
|
+
target_id TEXT,
|
|
830
|
+
result_revision TEXT,
|
|
831
|
+
result_digest TEXT,
|
|
832
|
+
duplicate_of_receipt_id TEXT,
|
|
833
|
+
accepted_receipt_id TEXT,
|
|
834
|
+
created_by_operation INTEGER NOT NULL CHECK(created_by_operation IN (0, 1)),
|
|
835
|
+
created_at TEXT NOT NULL
|
|
836
|
+
);
|
|
837
|
+
|
|
838
|
+
CREATE INDEX IF NOT EXISTS idx_mementos_project_registration_receipts_lookup
|
|
839
|
+
ON mementos_project_registration_receipts (
|
|
840
|
+
authority_id, tenant_id, corpus_id, operation_id, step_id,
|
|
841
|
+
resource_kind, direction, idempotency_key, target_selector
|
|
842
|
+
);
|
|
843
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_mementos_project_registration_receipts_accepted_step
|
|
844
|
+
ON mementos_project_registration_receipts (
|
|
845
|
+
authority_id, tenant_id, corpus_id, operation_id, step_id,
|
|
846
|
+
resource_kind, direction
|
|
847
|
+
)
|
|
848
|
+
WHERE outcome = 'accepted';
|
|
849
|
+
|
|
850
|
+
CREATE TABLE IF NOT EXISTS mementos_project_registration_bindings (
|
|
851
|
+
authority_id TEXT NOT NULL,
|
|
852
|
+
tenant_id TEXT NOT NULL,
|
|
853
|
+
corpus_id TEXT NOT NULL,
|
|
854
|
+
resource_kind TEXT NOT NULL CHECK(resource_kind = 'project'),
|
|
855
|
+
target_selector TEXT NOT NULL,
|
|
856
|
+
operation_id TEXT NOT NULL,
|
|
857
|
+
step_id TEXT NOT NULL,
|
|
858
|
+
direction TEXT NOT NULL CHECK(direction = 'forward'),
|
|
859
|
+
idempotency_key TEXT NOT NULL,
|
|
860
|
+
request_digest TEXT NOT NULL,
|
|
861
|
+
precondition_digest TEXT NOT NULL,
|
|
862
|
+
normalized_call_digest TEXT NOT NULL,
|
|
863
|
+
state TEXT NOT NULL CHECK(state IN (
|
|
864
|
+
'pending', 'accepted', 'terminal_nonacceptance', 'removed'
|
|
865
|
+
)),
|
|
866
|
+
target_id TEXT,
|
|
867
|
+
accepted_receipt_id TEXT,
|
|
868
|
+
result_revision TEXT,
|
|
869
|
+
result_digest TEXT,
|
|
870
|
+
removed_receipt_id TEXT,
|
|
871
|
+
created_at TEXT NOT NULL,
|
|
872
|
+
updated_at TEXT NOT NULL,
|
|
873
|
+
PRIMARY KEY(authority_id, tenant_id, corpus_id, resource_kind, target_selector),
|
|
874
|
+
UNIQUE(accepted_receipt_id)
|
|
875
|
+
);
|
|
876
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_mementos_project_registration_binding_target
|
|
877
|
+
ON mementos_project_registration_bindings(
|
|
878
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_id
|
|
879
|
+
)
|
|
880
|
+
WHERE target_id IS NOT NULL;
|
|
881
|
+
|
|
882
|
+
CREATE TRIGGER IF NOT EXISTS mementos_project_registration_receipts_immutable_update
|
|
883
|
+
BEFORE UPDATE ON mementos_project_registration_receipts
|
|
884
|
+
BEGIN
|
|
885
|
+
SELECT RAISE(ABORT, 'mementos project registration receipts are immutable');
|
|
886
|
+
END;
|
|
887
|
+
|
|
888
|
+
CREATE TRIGGER IF NOT EXISTS mementos_project_registration_receipts_immutable_delete
|
|
889
|
+
BEFORE DELETE ON mementos_project_registration_receipts
|
|
890
|
+
BEGIN
|
|
891
|
+
SELECT RAISE(ABORT, 'mementos project registration receipts are immutable');
|
|
892
|
+
END;
|
|
893
|
+
`;
|
|
894
|
+
}
|
|
895
|
+
function sqliteMementosProjectGuardedUpdateSchemaSql() {
|
|
896
|
+
return `
|
|
897
|
+
CREATE TABLE IF NOT EXISTS mementos_project_update_receipts (
|
|
898
|
+
receipt_id TEXT PRIMARY KEY,
|
|
899
|
+
authority TEXT NOT NULL CHECK(authority = 'mementos'),
|
|
900
|
+
route TEXT NOT NULL CHECK(route = 'mementos.project-guarded-update.v1'),
|
|
901
|
+
package_version TEXT NOT NULL,
|
|
902
|
+
authority_id TEXT NOT NULL,
|
|
903
|
+
tenant_id TEXT NOT NULL,
|
|
904
|
+
corpus_id TEXT NOT NULL,
|
|
905
|
+
operation_id TEXT NOT NULL,
|
|
906
|
+
step_id TEXT NOT NULL,
|
|
907
|
+
direction TEXT NOT NULL CHECK(direction IN ('forward', 'rollback')),
|
|
908
|
+
idempotency_key TEXT NOT NULL,
|
|
909
|
+
request_digest TEXT NOT NULL,
|
|
910
|
+
outcome TEXT NOT NULL CHECK(outcome = 'accepted'),
|
|
911
|
+
target_id TEXT NOT NULL,
|
|
912
|
+
expected_revision TEXT NOT NULL,
|
|
913
|
+
result_revision TEXT NOT NULL,
|
|
914
|
+
result_digest TEXT NOT NULL,
|
|
915
|
+
accepted_receipt_id TEXT,
|
|
916
|
+
before_project_json TEXT NOT NULL,
|
|
917
|
+
after_project_json TEXT NOT NULL,
|
|
918
|
+
created_at TEXT NOT NULL,
|
|
919
|
+
UNIQUE(authority_id, tenant_id, corpus_id, direction, idempotency_key)
|
|
920
|
+
);
|
|
921
|
+
|
|
922
|
+
CREATE INDEX IF NOT EXISTS idx_mementos_project_update_receipts_target
|
|
923
|
+
ON mementos_project_update_receipts(
|
|
924
|
+
authority_id, tenant_id, corpus_id, target_id, created_at
|
|
925
|
+
);
|
|
926
|
+
|
|
927
|
+
CREATE TRIGGER IF NOT EXISTS mementos_project_update_receipts_immutable_update
|
|
928
|
+
BEFORE UPDATE ON mementos_project_update_receipts
|
|
929
|
+
BEGIN
|
|
930
|
+
SELECT RAISE(ABORT, 'mementos project update receipts are immutable');
|
|
931
|
+
END;
|
|
932
|
+
|
|
933
|
+
CREATE TRIGGER IF NOT EXISTS mementos_project_update_receipts_immutable_delete
|
|
934
|
+
BEFORE DELETE ON mementos_project_update_receipts
|
|
935
|
+
BEGIN
|
|
936
|
+
SELECT RAISE(ABORT, 'mementos project update receipts are immutable');
|
|
937
|
+
END;
|
|
938
|
+
`;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// src/memory-project-link/schema.ts
|
|
942
|
+
function sqliteMementosMemoryProjectLinkSchemaSql() {
|
|
943
|
+
return `
|
|
944
|
+
CREATE TABLE IF NOT EXISTS mementos_memory_project_link_receipts (
|
|
945
|
+
receipt_id TEXT PRIMARY KEY,
|
|
946
|
+
authority TEXT NOT NULL CHECK(authority = 'mementos'),
|
|
947
|
+
route TEXT NOT NULL CHECK(route = 'mementos.memory-project-link.v1'),
|
|
948
|
+
package_version TEXT NOT NULL,
|
|
949
|
+
authority_id TEXT NOT NULL,
|
|
950
|
+
tenant_id TEXT NOT NULL,
|
|
951
|
+
corpus_id TEXT NOT NULL,
|
|
952
|
+
operation_id TEXT NOT NULL,
|
|
953
|
+
step_id TEXT NOT NULL,
|
|
954
|
+
direction TEXT NOT NULL CHECK(direction IN ('forward', 'rollback')),
|
|
955
|
+
idempotency_key TEXT NOT NULL,
|
|
956
|
+
request_digest TEXT NOT NULL,
|
|
957
|
+
outcome TEXT NOT NULL CHECK(outcome IN ('accepted', 'no_change')),
|
|
958
|
+
target_memory_id TEXT NOT NULL,
|
|
959
|
+
requested_project_id TEXT NOT NULL,
|
|
960
|
+
expected_memory_version INTEGER NOT NULL,
|
|
961
|
+
expected_memory_revision TEXT NOT NULL,
|
|
962
|
+
expected_project_revision TEXT,
|
|
963
|
+
result_memory_version INTEGER NOT NULL,
|
|
964
|
+
result_memory_revision TEXT NOT NULL,
|
|
965
|
+
result_memory_digest TEXT NOT NULL,
|
|
966
|
+
result_project_revision TEXT,
|
|
967
|
+
result_project_digest TEXT,
|
|
968
|
+
accepted_receipt_id TEXT,
|
|
969
|
+
before_link_json TEXT NOT NULL,
|
|
970
|
+
after_link_json TEXT NOT NULL,
|
|
971
|
+
before_project_revision TEXT,
|
|
972
|
+
before_project_digest TEXT,
|
|
973
|
+
after_project_revision TEXT,
|
|
974
|
+
after_project_digest TEXT,
|
|
975
|
+
created_at TEXT NOT NULL,
|
|
976
|
+
UNIQUE(authority_id, tenant_id, corpus_id, direction, idempotency_key)
|
|
977
|
+
);
|
|
978
|
+
|
|
979
|
+
CREATE INDEX IF NOT EXISTS idx_mementos_memory_project_link_receipts_target
|
|
980
|
+
ON mementos_memory_project_link_receipts(
|
|
981
|
+
authority_id, tenant_id, corpus_id, target_memory_id, created_at
|
|
982
|
+
);
|
|
983
|
+
|
|
984
|
+
CREATE TRIGGER IF NOT EXISTS mementos_memory_project_link_receipts_immutable_update
|
|
985
|
+
BEFORE UPDATE ON mementos_memory_project_link_receipts
|
|
986
|
+
BEGIN
|
|
987
|
+
SELECT RAISE(ABORT, 'mementos memory project link receipts are immutable');
|
|
988
|
+
END;
|
|
989
|
+
|
|
990
|
+
CREATE TRIGGER IF NOT EXISTS mementos_memory_project_link_receipts_immutable_delete
|
|
991
|
+
BEFORE DELETE ON mementos_memory_project_link_receipts
|
|
992
|
+
BEGIN
|
|
993
|
+
SELECT RAISE(ABORT, 'mementos memory project link receipts are immutable');
|
|
994
|
+
END;
|
|
995
|
+
`;
|
|
996
|
+
}
|
|
997
|
+
var MEMENTOS_MEMORY_PROJECT_LINK_ROUTE = "mementos.memory-project-link.v1";
|
|
998
|
+
var init_schema = () => {};
|
|
999
|
+
|
|
805
1000
|
// src/db/migrations.ts
|
|
806
1001
|
var MEMORY_VERSION_SNAPSHOT_TRIGGER = `
|
|
807
1002
|
CREATE TRIGGER IF NOT EXISTS memories_version_snapshot
|
|
@@ -822,6 +1017,7 @@ BEGIN
|
|
|
822
1017
|
END;
|
|
823
1018
|
`, MIGRATIONS;
|
|
824
1019
|
var init_migrations = __esm(() => {
|
|
1020
|
+
init_schema();
|
|
825
1021
|
MIGRATIONS = [
|
|
826
1022
|
`
|
|
827
1023
|
CREATE TABLE IF NOT EXISTS projects (
|
|
@@ -1722,6 +1918,51 @@ INSERT OR IGNORE INTO _migrations (id) VALUES (35);
|
|
|
1722
1918
|
`
|
|
1723
1919
|
${MEMORY_VERSION_SNAPSHOT_TRIGGER}
|
|
1724
1920
|
INSERT OR IGNORE INTO _migrations (id) VALUES (36);
|
|
1921
|
+
`,
|
|
1922
|
+
`
|
|
1923
|
+
DROP TRIGGER IF EXISTS audit_memory_insert;
|
|
1924
|
+
CREATE TRIGGER audit_memory_insert AFTER INSERT ON memories BEGIN
|
|
1925
|
+
INSERT INTO memory_audit_log (id, memory_id, memory_key, operation, agent_id, created_at)
|
|
1926
|
+
VALUES (hex(randomblob(4)), new.id, new.key, 'create', new.agent_id, datetime('now'));
|
|
1927
|
+
END;
|
|
1928
|
+
|
|
1929
|
+
DROP TRIGGER IF EXISTS audit_memory_update;
|
|
1930
|
+
CREATE TRIGGER audit_memory_update AFTER UPDATE ON memories BEGIN
|
|
1931
|
+
INSERT INTO memory_audit_log (id, memory_id, memory_key, operation, agent_id, changes, created_at)
|
|
1932
|
+
VALUES (hex(randomblob(4)), new.id, new.key, 'update', new.agent_id,
|
|
1933
|
+
json_object('version_from', old.version, 'version_to', new.version, 'importance_from', old.importance, 'importance_to', new.importance),
|
|
1934
|
+
datetime('now'));
|
|
1935
|
+
END;
|
|
1936
|
+
|
|
1937
|
+
DROP TRIGGER IF EXISTS audit_memory_delete;
|
|
1938
|
+
CREATE TRIGGER audit_memory_delete AFTER DELETE ON memories BEGIN
|
|
1939
|
+
INSERT INTO memory_audit_log (id, memory_id, memory_key, operation, agent_id, created_at)
|
|
1940
|
+
VALUES (hex(randomblob(4)), old.id, old.key, 'delete', old.agent_id, datetime('now'));
|
|
1941
|
+
END;
|
|
1942
|
+
|
|
1943
|
+
UPDATE memory_audit_log SET old_value_hash = NULL
|
|
1944
|
+
WHERE old_value_hash IS NOT NULL
|
|
1945
|
+
AND length(old_value_hash) = 32
|
|
1946
|
+
AND old_value_hash NOT GLOB '*[^0-9A-F]*';
|
|
1947
|
+
|
|
1948
|
+
UPDATE memory_audit_log SET new_value_hash = NULL
|
|
1949
|
+
WHERE new_value_hash IS NOT NULL
|
|
1950
|
+
AND length(new_value_hash) = 32
|
|
1951
|
+
AND new_value_hash NOT GLOB '*[^0-9A-F]*';
|
|
1952
|
+
|
|
1953
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (37);
|
|
1954
|
+
`,
|
|
1955
|
+
`
|
|
1956
|
+
${sqliteMementosProjectRegistrationSchemaSql()}
|
|
1957
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (38);
|
|
1958
|
+
`,
|
|
1959
|
+
`
|
|
1960
|
+
${sqliteMementosProjectGuardedUpdateSchemaSql()}
|
|
1961
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (39);
|
|
1962
|
+
`,
|
|
1963
|
+
`
|
|
1964
|
+
${sqliteMementosMemoryProjectLinkSchemaSql()}
|
|
1965
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (40);
|
|
1725
1966
|
`
|
|
1726
1967
|
];
|
|
1727
1968
|
});
|
|
@@ -6159,8 +6400,8 @@ var init_util = __esm(() => {
|
|
|
6159
6400
|
|
|
6160
6401
|
// node_modules/zod/v3/ZodError.js
|
|
6161
6402
|
var ZodIssueCode, quotelessJson = (obj) => {
|
|
6162
|
-
const
|
|
6163
|
-
return
|
|
6403
|
+
const json3 = JSON.stringify(obj, null, 2);
|
|
6404
|
+
return json3.replace(/"([^"]+)":/g, "$1:");
|
|
6164
6405
|
}, ZodError;
|
|
6165
6406
|
var init_ZodError = __esm(() => {
|
|
6166
6407
|
init_util();
|
|
@@ -19352,24 +19593,24 @@ class JSONSchemaGenerator {
|
|
|
19352
19593
|
const _json = result.schema;
|
|
19353
19594
|
switch (def.type) {
|
|
19354
19595
|
case "string": {
|
|
19355
|
-
const
|
|
19356
|
-
|
|
19596
|
+
const json3 = _json;
|
|
19597
|
+
json3.type = "string";
|
|
19357
19598
|
const { minimum, maximum, format, patterns, contentEncoding } = schema._zod.bag;
|
|
19358
19599
|
if (typeof minimum === "number")
|
|
19359
|
-
|
|
19600
|
+
json3.minLength = minimum;
|
|
19360
19601
|
if (typeof maximum === "number")
|
|
19361
|
-
|
|
19602
|
+
json3.maxLength = maximum;
|
|
19362
19603
|
if (format) {
|
|
19363
|
-
|
|
19364
|
-
if (
|
|
19365
|
-
delete
|
|
19604
|
+
json3.format = formatMap[format] ?? format;
|
|
19605
|
+
if (json3.format === "")
|
|
19606
|
+
delete json3.format;
|
|
19366
19607
|
}
|
|
19367
19608
|
if (contentEncoding)
|
|
19368
|
-
|
|
19609
|
+
json3.contentEncoding = contentEncoding;
|
|
19369
19610
|
if (patterns && patterns.size > 0) {
|
|
19370
19611
|
const regexes = [...patterns];
|
|
19371
19612
|
if (regexes.length === 1)
|
|
19372
|
-
|
|
19613
|
+
json3.pattern = regexes[0].source;
|
|
19373
19614
|
else if (regexes.length > 1) {
|
|
19374
19615
|
result.schema.allOf = [
|
|
19375
19616
|
...regexes.map((regex) => ({
|
|
@@ -19382,41 +19623,41 @@ class JSONSchemaGenerator {
|
|
|
19382
19623
|
break;
|
|
19383
19624
|
}
|
|
19384
19625
|
case "number": {
|
|
19385
|
-
const
|
|
19626
|
+
const json3 = _json;
|
|
19386
19627
|
const { minimum, maximum, format, multipleOf, exclusiveMaximum, exclusiveMinimum } = schema._zod.bag;
|
|
19387
19628
|
if (typeof format === "string" && format.includes("int"))
|
|
19388
|
-
|
|
19629
|
+
json3.type = "integer";
|
|
19389
19630
|
else
|
|
19390
|
-
|
|
19631
|
+
json3.type = "number";
|
|
19391
19632
|
if (typeof exclusiveMinimum === "number")
|
|
19392
|
-
|
|
19633
|
+
json3.exclusiveMinimum = exclusiveMinimum;
|
|
19393
19634
|
if (typeof minimum === "number") {
|
|
19394
|
-
|
|
19635
|
+
json3.minimum = minimum;
|
|
19395
19636
|
if (typeof exclusiveMinimum === "number") {
|
|
19396
19637
|
if (exclusiveMinimum >= minimum)
|
|
19397
|
-
delete
|
|
19638
|
+
delete json3.minimum;
|
|
19398
19639
|
else
|
|
19399
|
-
delete
|
|
19640
|
+
delete json3.exclusiveMinimum;
|
|
19400
19641
|
}
|
|
19401
19642
|
}
|
|
19402
19643
|
if (typeof exclusiveMaximum === "number")
|
|
19403
|
-
|
|
19644
|
+
json3.exclusiveMaximum = exclusiveMaximum;
|
|
19404
19645
|
if (typeof maximum === "number") {
|
|
19405
|
-
|
|
19646
|
+
json3.maximum = maximum;
|
|
19406
19647
|
if (typeof exclusiveMaximum === "number") {
|
|
19407
19648
|
if (exclusiveMaximum <= maximum)
|
|
19408
|
-
delete
|
|
19649
|
+
delete json3.maximum;
|
|
19409
19650
|
else
|
|
19410
|
-
delete
|
|
19651
|
+
delete json3.exclusiveMaximum;
|
|
19411
19652
|
}
|
|
19412
19653
|
}
|
|
19413
19654
|
if (typeof multipleOf === "number")
|
|
19414
|
-
|
|
19655
|
+
json3.multipleOf = multipleOf;
|
|
19415
19656
|
break;
|
|
19416
19657
|
}
|
|
19417
19658
|
case "boolean": {
|
|
19418
|
-
const
|
|
19419
|
-
|
|
19659
|
+
const json3 = _json;
|
|
19660
|
+
json3.type = "boolean";
|
|
19420
19661
|
break;
|
|
19421
19662
|
}
|
|
19422
19663
|
case "bigint": {
|
|
@@ -19464,23 +19705,23 @@ class JSONSchemaGenerator {
|
|
|
19464
19705
|
break;
|
|
19465
19706
|
}
|
|
19466
19707
|
case "array": {
|
|
19467
|
-
const
|
|
19708
|
+
const json3 = _json;
|
|
19468
19709
|
const { minimum, maximum } = schema._zod.bag;
|
|
19469
19710
|
if (typeof minimum === "number")
|
|
19470
|
-
|
|
19711
|
+
json3.minItems = minimum;
|
|
19471
19712
|
if (typeof maximum === "number")
|
|
19472
|
-
|
|
19473
|
-
|
|
19474
|
-
|
|
19713
|
+
json3.maxItems = maximum;
|
|
19714
|
+
json3.type = "array";
|
|
19715
|
+
json3.items = this.process(def.element, { ...params, path: [...params.path, "items"] });
|
|
19475
19716
|
break;
|
|
19476
19717
|
}
|
|
19477
19718
|
case "object": {
|
|
19478
|
-
const
|
|
19479
|
-
|
|
19480
|
-
|
|
19719
|
+
const json3 = _json;
|
|
19720
|
+
json3.type = "object";
|
|
19721
|
+
json3.properties = {};
|
|
19481
19722
|
const shape = def.shape;
|
|
19482
19723
|
for (const key in shape) {
|
|
19483
|
-
|
|
19724
|
+
json3.properties[key] = this.process(shape[key], {
|
|
19484
19725
|
...params,
|
|
19485
19726
|
path: [...params.path, "properties", key]
|
|
19486
19727
|
});
|
|
@@ -19495,15 +19736,15 @@ class JSONSchemaGenerator {
|
|
|
19495
19736
|
}
|
|
19496
19737
|
}));
|
|
19497
19738
|
if (requiredKeys.size > 0) {
|
|
19498
|
-
|
|
19739
|
+
json3.required = Array.from(requiredKeys);
|
|
19499
19740
|
}
|
|
19500
19741
|
if (def.catchall?._zod.def.type === "never") {
|
|
19501
|
-
|
|
19742
|
+
json3.additionalProperties = false;
|
|
19502
19743
|
} else if (!def.catchall) {
|
|
19503
19744
|
if (this.io === "output")
|
|
19504
|
-
|
|
19745
|
+
json3.additionalProperties = false;
|
|
19505
19746
|
} else if (def.catchall) {
|
|
19506
|
-
|
|
19747
|
+
json3.additionalProperties = this.process(def.catchall, {
|
|
19507
19748
|
...params,
|
|
19508
19749
|
path: [...params.path, "additionalProperties"]
|
|
19509
19750
|
});
|
|
@@ -19511,15 +19752,15 @@ class JSONSchemaGenerator {
|
|
|
19511
19752
|
break;
|
|
19512
19753
|
}
|
|
19513
19754
|
case "union": {
|
|
19514
|
-
const
|
|
19515
|
-
|
|
19755
|
+
const json3 = _json;
|
|
19756
|
+
json3.anyOf = def.options.map((x, i) => this.process(x, {
|
|
19516
19757
|
...params,
|
|
19517
19758
|
path: [...params.path, "anyOf", i]
|
|
19518
19759
|
}));
|
|
19519
19760
|
break;
|
|
19520
19761
|
}
|
|
19521
19762
|
case "intersection": {
|
|
19522
|
-
const
|
|
19763
|
+
const json3 = _json;
|
|
19523
19764
|
const a = this.process(def.left, {
|
|
19524
19765
|
...params,
|
|
19525
19766
|
path: [...params.path, "allOf", 0]
|
|
@@ -19533,17 +19774,17 @@ class JSONSchemaGenerator {
|
|
|
19533
19774
|
...isSimpleIntersection(a) ? a.allOf : [a],
|
|
19534
19775
|
...isSimpleIntersection(b) ? b.allOf : [b]
|
|
19535
19776
|
];
|
|
19536
|
-
|
|
19777
|
+
json3.allOf = allOf;
|
|
19537
19778
|
break;
|
|
19538
19779
|
}
|
|
19539
19780
|
case "tuple": {
|
|
19540
|
-
const
|
|
19541
|
-
|
|
19781
|
+
const json3 = _json;
|
|
19782
|
+
json3.type = "array";
|
|
19542
19783
|
const prefixItems = def.items.map((x, i) => this.process(x, { ...params, path: [...params.path, "prefixItems", i] }));
|
|
19543
19784
|
if (this.target === "draft-2020-12") {
|
|
19544
|
-
|
|
19785
|
+
json3.prefixItems = prefixItems;
|
|
19545
19786
|
} else {
|
|
19546
|
-
|
|
19787
|
+
json3.items = prefixItems;
|
|
19547
19788
|
}
|
|
19548
19789
|
if (def.rest) {
|
|
19549
19790
|
const rest = this.process(def.rest, {
|
|
@@ -19551,29 +19792,29 @@ class JSONSchemaGenerator {
|
|
|
19551
19792
|
path: [...params.path, "items"]
|
|
19552
19793
|
});
|
|
19553
19794
|
if (this.target === "draft-2020-12") {
|
|
19554
|
-
|
|
19795
|
+
json3.items = rest;
|
|
19555
19796
|
} else {
|
|
19556
|
-
|
|
19797
|
+
json3.additionalItems = rest;
|
|
19557
19798
|
}
|
|
19558
19799
|
}
|
|
19559
19800
|
if (def.rest) {
|
|
19560
|
-
|
|
19801
|
+
json3.items = this.process(def.rest, {
|
|
19561
19802
|
...params,
|
|
19562
19803
|
path: [...params.path, "items"]
|
|
19563
19804
|
});
|
|
19564
19805
|
}
|
|
19565
19806
|
const { minimum, maximum } = schema._zod.bag;
|
|
19566
19807
|
if (typeof minimum === "number")
|
|
19567
|
-
|
|
19808
|
+
json3.minItems = minimum;
|
|
19568
19809
|
if (typeof maximum === "number")
|
|
19569
|
-
|
|
19810
|
+
json3.maxItems = maximum;
|
|
19570
19811
|
break;
|
|
19571
19812
|
}
|
|
19572
19813
|
case "record": {
|
|
19573
|
-
const
|
|
19574
|
-
|
|
19575
|
-
|
|
19576
|
-
|
|
19814
|
+
const json3 = _json;
|
|
19815
|
+
json3.type = "object";
|
|
19816
|
+
json3.propertyNames = this.process(def.keyType, { ...params, path: [...params.path, "propertyNames"] });
|
|
19817
|
+
json3.additionalProperties = this.process(def.valueType, {
|
|
19577
19818
|
...params,
|
|
19578
19819
|
path: [...params.path, "additionalProperties"]
|
|
19579
19820
|
});
|
|
@@ -19592,17 +19833,17 @@ class JSONSchemaGenerator {
|
|
|
19592
19833
|
break;
|
|
19593
19834
|
}
|
|
19594
19835
|
case "enum": {
|
|
19595
|
-
const
|
|
19836
|
+
const json3 = _json;
|
|
19596
19837
|
const values = getEnumValues(def.entries);
|
|
19597
19838
|
if (values.every((v) => typeof v === "number"))
|
|
19598
|
-
|
|
19839
|
+
json3.type = "number";
|
|
19599
19840
|
if (values.every((v) => typeof v === "string"))
|
|
19600
|
-
|
|
19601
|
-
|
|
19841
|
+
json3.type = "string";
|
|
19842
|
+
json3.enum = values;
|
|
19602
19843
|
break;
|
|
19603
19844
|
}
|
|
19604
19845
|
case "literal": {
|
|
19605
|
-
const
|
|
19846
|
+
const json3 = _json;
|
|
19606
19847
|
const vals = [];
|
|
19607
19848
|
for (const val of def.values) {
|
|
19608
19849
|
if (val === undefined) {
|
|
@@ -19621,23 +19862,23 @@ class JSONSchemaGenerator {
|
|
|
19621
19862
|
}
|
|
19622
19863
|
if (vals.length === 0) {} else if (vals.length === 1) {
|
|
19623
19864
|
const val = vals[0];
|
|
19624
|
-
|
|
19625
|
-
|
|
19865
|
+
json3.type = val === null ? "null" : typeof val;
|
|
19866
|
+
json3.const = val;
|
|
19626
19867
|
} else {
|
|
19627
19868
|
if (vals.every((v) => typeof v === "number"))
|
|
19628
|
-
|
|
19869
|
+
json3.type = "number";
|
|
19629
19870
|
if (vals.every((v) => typeof v === "string"))
|
|
19630
|
-
|
|
19871
|
+
json3.type = "string";
|
|
19631
19872
|
if (vals.every((v) => typeof v === "boolean"))
|
|
19632
|
-
|
|
19873
|
+
json3.type = "string";
|
|
19633
19874
|
if (vals.every((v) => v === null))
|
|
19634
|
-
|
|
19635
|
-
|
|
19875
|
+
json3.type = "null";
|
|
19876
|
+
json3.enum = vals;
|
|
19636
19877
|
}
|
|
19637
19878
|
break;
|
|
19638
19879
|
}
|
|
19639
19880
|
case "file": {
|
|
19640
|
-
const
|
|
19881
|
+
const json3 = _json;
|
|
19641
19882
|
const file = {
|
|
19642
19883
|
type: "string",
|
|
19643
19884
|
format: "binary",
|
|
@@ -19651,15 +19892,15 @@ class JSONSchemaGenerator {
|
|
|
19651
19892
|
if (mime) {
|
|
19652
19893
|
if (mime.length === 1) {
|
|
19653
19894
|
file.contentMediaType = mime[0];
|
|
19654
|
-
Object.assign(
|
|
19895
|
+
Object.assign(json3, file);
|
|
19655
19896
|
} else {
|
|
19656
|
-
|
|
19897
|
+
json3.anyOf = mime.map((m) => {
|
|
19657
19898
|
const mFile = { ...file, contentMediaType: m };
|
|
19658
19899
|
return mFile;
|
|
19659
19900
|
});
|
|
19660
19901
|
}
|
|
19661
19902
|
} else {
|
|
19662
|
-
Object.assign(
|
|
19903
|
+
Object.assign(json3, file);
|
|
19663
19904
|
}
|
|
19664
19905
|
break;
|
|
19665
19906
|
}
|
|
@@ -19680,8 +19921,8 @@ class JSONSchemaGenerator {
|
|
|
19680
19921
|
break;
|
|
19681
19922
|
}
|
|
19682
19923
|
case "success": {
|
|
19683
|
-
const
|
|
19684
|
-
|
|
19924
|
+
const json3 = _json;
|
|
19925
|
+
json3.type = "boolean";
|
|
19685
19926
|
break;
|
|
19686
19927
|
}
|
|
19687
19928
|
case "default": {
|
|
@@ -19716,12 +19957,12 @@ class JSONSchemaGenerator {
|
|
|
19716
19957
|
break;
|
|
19717
19958
|
}
|
|
19718
19959
|
case "template_literal": {
|
|
19719
|
-
const
|
|
19960
|
+
const json3 = _json;
|
|
19720
19961
|
const pattern = schema._zod.pattern;
|
|
19721
19962
|
if (!pattern)
|
|
19722
19963
|
throw new Error("Pattern not found in template literal");
|
|
19723
|
-
|
|
19724
|
-
|
|
19964
|
+
json3.type = "string";
|
|
19965
|
+
json3.pattern = pattern.source;
|
|
19725
19966
|
break;
|
|
19726
19967
|
}
|
|
19727
19968
|
case "pipe": {
|
|
@@ -20814,7 +21055,7 @@ function _instanceof(cls, params = {
|
|
|
20814
21055
|
inst._zod.bag.Class = cls;
|
|
20815
21056
|
return inst;
|
|
20816
21057
|
}
|
|
20817
|
-
function
|
|
21058
|
+
function json3(params) {
|
|
20818
21059
|
const jsonSchema = lazy(() => {
|
|
20819
21060
|
return union([string2(params), number2(), boolean2(), _null3(), array(jsonSchema), record(string2(), jsonSchema)]);
|
|
20820
21061
|
});
|
|
@@ -21501,7 +21742,7 @@ __export(exports_external2, {
|
|
|
21501
21742
|
ksuid: () => ksuid2,
|
|
21502
21743
|
keyof: () => keyof,
|
|
21503
21744
|
jwt: () => jwt,
|
|
21504
|
-
json: () =>
|
|
21745
|
+
json: () => json3,
|
|
21505
21746
|
iso: () => exports_iso,
|
|
21506
21747
|
ipv6: () => ipv62,
|
|
21507
21748
|
ipv4: () => ipv42,
|
|
@@ -23361,7 +23602,7 @@ function createProviderToolFactoryWithOutputSchema({
|
|
|
23361
23602
|
supportsDeferredResults
|
|
23362
23603
|
});
|
|
23363
23604
|
}
|
|
23364
|
-
async function
|
|
23605
|
+
async function resolve4(value) {
|
|
23365
23606
|
if (typeof value === "function") {
|
|
23366
23607
|
value = value();
|
|
23367
23608
|
}
|
|
@@ -26183,11 +26424,11 @@ var VERSION2 = "3.0.82", anthropicErrorDataSchema, anthropicFailedResponseHandle
|
|
|
26183
26424
|
betas,
|
|
26184
26425
|
headers
|
|
26185
26426
|
}) {
|
|
26186
|
-
return combineHeaders(await
|
|
26427
|
+
return combineHeaders(await resolve4(this.config.headers), headers, betas.size > 0 ? { "anthropic-beta": Array.from(betas).join(",") } : {});
|
|
26187
26428
|
}
|
|
26188
26429
|
async getBetasFromHeaders(requestHeaders) {
|
|
26189
26430
|
var _a16, _b16;
|
|
26190
|
-
const configHeaders = await
|
|
26431
|
+
const configHeaders = await resolve4(this.config.headers);
|
|
26191
26432
|
const configBetaHeader = (_a16 = configHeaders["anthropic-beta"]) != null ? _a16 : "";
|
|
26192
26433
|
const requestBetaHeader = (_b16 = requestHeaders == null ? undefined : requestHeaders["anthropic-beta"]) != null ? _b16 : "";
|
|
26193
26434
|
return new Set([
|
|
@@ -36857,24 +37098,24 @@ var require_oauth = __commonJS((exports, module) => {
|
|
|
36857
37098
|
});
|
|
36858
37099
|
}
|
|
36859
37100
|
async function processTokenResponse(response) {
|
|
36860
|
-
const
|
|
37101
|
+
const json4 = await response.json();
|
|
36861
37102
|
if (!response.ok) {
|
|
36862
|
-
const errorMsg = typeof
|
|
37103
|
+
const errorMsg = typeof json4 === "object" && json4 && "error" in json4 ? String(json4.error) : "Token refresh failed";
|
|
36863
37104
|
return [new Error(errorMsg)];
|
|
36864
37105
|
}
|
|
36865
|
-
if (typeof
|
|
37106
|
+
if (typeof json4 !== "object" || json4 === null) {
|
|
36866
37107
|
return [new Error("Invalid token response")];
|
|
36867
37108
|
}
|
|
36868
|
-
if (typeof
|
|
37109
|
+
if (typeof json4.access_token !== "string") {
|
|
36869
37110
|
return [new Error("Missing access_token in response")];
|
|
36870
37111
|
}
|
|
36871
|
-
if (
|
|
37112
|
+
if (json4.token_type !== "Bearer") {
|
|
36872
37113
|
return [new Error("Invalid token_type in response")];
|
|
36873
37114
|
}
|
|
36874
|
-
if (typeof
|
|
37115
|
+
if (typeof json4.expires_in !== "number") {
|
|
36875
37116
|
return [new Error("Missing expires_in in response")];
|
|
36876
37117
|
}
|
|
36877
|
-
return [null,
|
|
37118
|
+
return [null, json4];
|
|
36878
37119
|
}
|
|
36879
37120
|
});
|
|
36880
37121
|
|
|
@@ -37611,7 +37852,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37611
37852
|
try {
|
|
37612
37853
|
const { value } = await getFromApi({
|
|
37613
37854
|
url: `${this.config.baseURL}/config`,
|
|
37614
|
-
headers: await
|
|
37855
|
+
headers: await resolve4(this.config.headers()),
|
|
37615
37856
|
successfulResponseHandler: createJsonResponseHandler(gatewayAvailableModelsResponseSchema),
|
|
37616
37857
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
37617
37858
|
errorSchema: exports_external2.any(),
|
|
@@ -37629,7 +37870,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37629
37870
|
const baseUrl = new URL(this.config.baseURL);
|
|
37630
37871
|
const { value } = await getFromApi({
|
|
37631
37872
|
url: `${baseUrl.origin}/v1/credits`,
|
|
37632
|
-
headers: await
|
|
37873
|
+
headers: await resolve4(this.config.headers()),
|
|
37633
37874
|
successfulResponseHandler: createJsonResponseHandler(gatewayCreditsResponseSchema),
|
|
37634
37875
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
37635
37876
|
errorSchema: exports_external2.any(),
|
|
@@ -37675,7 +37916,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37675
37916
|
}
|
|
37676
37917
|
const { value } = await getFromApi({
|
|
37677
37918
|
url: `${baseUrl.origin}/v1/report?${searchParams.toString()}`,
|
|
37678
|
-
headers: await
|
|
37919
|
+
headers: await resolve4(this.config.headers()),
|
|
37679
37920
|
successfulResponseHandler: createJsonResponseHandler(gatewaySpendReportResponseSchema),
|
|
37680
37921
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
37681
37922
|
errorSchema: exports_external2.any(),
|
|
@@ -37697,7 +37938,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37697
37938
|
const baseUrl = new URL(this.config.baseURL);
|
|
37698
37939
|
const { value } = await getFromApi({
|
|
37699
37940
|
url: `${baseUrl.origin}/v1/generation?id=${encodeURIComponent(params.id)}`,
|
|
37700
|
-
headers: await
|
|
37941
|
+
headers: await resolve4(this.config.headers()),
|
|
37701
37942
|
successfulResponseHandler: createJsonResponseHandler(gatewayGenerationInfoResponseSchema),
|
|
37702
37943
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
37703
37944
|
errorSchema: exports_external2.any(),
|
|
@@ -37730,7 +37971,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37730
37971
|
async doGenerate(options) {
|
|
37731
37972
|
const { args, warnings } = await this.getArgs(options);
|
|
37732
37973
|
const { abortSignal } = options;
|
|
37733
|
-
const resolvedHeaders = await
|
|
37974
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
37734
37975
|
try {
|
|
37735
37976
|
const {
|
|
37736
37977
|
responseHeaders,
|
|
@@ -37738,7 +37979,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37738
37979
|
rawValue: rawResponse
|
|
37739
37980
|
} = await postJsonToApi({
|
|
37740
37981
|
url: this.getUrl(),
|
|
37741
|
-
headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, false), await
|
|
37982
|
+
headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, false), await resolve4(this.config.o11yHeaders)),
|
|
37742
37983
|
body: args,
|
|
37743
37984
|
successfulResponseHandler: createJsonResponseHandler(exports_external2.any()),
|
|
37744
37985
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
@@ -37761,11 +38002,11 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37761
38002
|
async doStream(options) {
|
|
37762
38003
|
const { args, warnings } = await this.getArgs(options);
|
|
37763
38004
|
const { abortSignal } = options;
|
|
37764
|
-
const resolvedHeaders = await
|
|
38005
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
37765
38006
|
try {
|
|
37766
38007
|
const { value: response, responseHeaders } = await postJsonToApi({
|
|
37767
38008
|
url: this.getUrl(),
|
|
37768
|
-
headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, true), await
|
|
38009
|
+
headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, true), await resolve4(this.config.o11yHeaders)),
|
|
37769
38010
|
body: args,
|
|
37770
38011
|
successfulResponseHandler: createEventSourceResponseHandler(exports_external2.any()),
|
|
37771
38012
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
@@ -37850,7 +38091,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37850
38091
|
providerOptions
|
|
37851
38092
|
}) {
|
|
37852
38093
|
var _a102;
|
|
37853
|
-
const resolvedHeaders = await
|
|
38094
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
37854
38095
|
try {
|
|
37855
38096
|
const {
|
|
37856
38097
|
responseHeaders,
|
|
@@ -37858,7 +38099,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37858
38099
|
rawValue
|
|
37859
38100
|
} = await postJsonToApi({
|
|
37860
38101
|
url: this.getUrl(),
|
|
37861
|
-
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await
|
|
38102
|
+
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders)),
|
|
37862
38103
|
body: {
|
|
37863
38104
|
values,
|
|
37864
38105
|
...providerOptions ? { providerOptions } : {}
|
|
@@ -37914,7 +38155,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37914
38155
|
abortSignal
|
|
37915
38156
|
}) {
|
|
37916
38157
|
var _a102, _b102, _c, _d;
|
|
37917
|
-
const resolvedHeaders = await
|
|
38158
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
37918
38159
|
try {
|
|
37919
38160
|
const {
|
|
37920
38161
|
responseHeaders,
|
|
@@ -37922,7 +38163,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37922
38163
|
rawValue
|
|
37923
38164
|
} = await postJsonToApi({
|
|
37924
38165
|
url: this.getUrl(),
|
|
37925
|
-
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await
|
|
38166
|
+
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders)),
|
|
37926
38167
|
body: {
|
|
37927
38168
|
prompt,
|
|
37928
38169
|
n,
|
|
@@ -37997,11 +38238,11 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37997
38238
|
abortSignal
|
|
37998
38239
|
}) {
|
|
37999
38240
|
var _a102;
|
|
38000
|
-
const resolvedHeaders = await
|
|
38241
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
38001
38242
|
try {
|
|
38002
38243
|
const { responseHeaders, value: responseBody } = await postJsonToApi({
|
|
38003
38244
|
url: this.getUrl(),
|
|
38004
|
-
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await
|
|
38245
|
+
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders), { accept: "text/event-stream" }),
|
|
38005
38246
|
body: {
|
|
38006
38247
|
prompt,
|
|
38007
38248
|
n,
|
|
@@ -38124,7 +38365,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
38124
38365
|
abortSignal,
|
|
38125
38366
|
providerOptions
|
|
38126
38367
|
}) {
|
|
38127
|
-
const resolvedHeaders = await
|
|
38368
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
38128
38369
|
try {
|
|
38129
38370
|
const {
|
|
38130
38371
|
responseHeaders,
|
|
@@ -38132,7 +38373,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
38132
38373
|
rawValue
|
|
38133
38374
|
} = await postJsonToApi({
|
|
38134
38375
|
url: this.getUrl(),
|
|
38135
|
-
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await
|
|
38376
|
+
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders)),
|
|
38136
38377
|
body: {
|
|
38137
38378
|
documents,
|
|
38138
38379
|
query,
|
|
@@ -47736,7 +47977,7 @@ var import_api2, import_api3, __defProp2, __export2 = (target, all) => {
|
|
|
47736
47977
|
const schema = asSchema(inputSchema);
|
|
47737
47978
|
return {
|
|
47738
47979
|
name: "object",
|
|
47739
|
-
responseFormat:
|
|
47980
|
+
responseFormat: resolve4(schema.jsonSchema).then((jsonSchema2) => ({
|
|
47740
47981
|
type: "json",
|
|
47741
47982
|
schema: jsonSchema2,
|
|
47742
47983
|
...name21 != null && { name: name21 },
|
|
@@ -47797,7 +48038,7 @@ var import_api2, import_api3, __defProp2, __export2 = (target, all) => {
|
|
|
47797
48038
|
const elementSchema = asSchema(inputElementSchema);
|
|
47798
48039
|
return {
|
|
47799
48040
|
name: "array",
|
|
47800
|
-
responseFormat:
|
|
48041
|
+
responseFormat: resolve4(elementSchema.jsonSchema).then((jsonSchema2) => {
|
|
47801
48042
|
const { $schema, ...itemSchema } = jsonSchema2;
|
|
47802
48043
|
return {
|
|
47803
48044
|
type: "json",
|
|
@@ -47974,7 +48215,7 @@ var import_api2, import_api3, __defProp2, __export2 = (target, all) => {
|
|
|
47974
48215
|
return;
|
|
47975
48216
|
}
|
|
47976
48217
|
};
|
|
47977
|
-
},
|
|
48218
|
+
}, json4 = ({
|
|
47978
48219
|
name: name21,
|
|
47979
48220
|
description
|
|
47980
48221
|
} = {}) => {
|
|
@@ -50710,9 +50951,9 @@ var import_api2, import_api3, __defProp2, __export2 = (target, all) => {
|
|
|
50710
50951
|
...options
|
|
50711
50952
|
}) {
|
|
50712
50953
|
var _a21, _b16, _c, _d, _e;
|
|
50713
|
-
const resolvedBody = await
|
|
50714
|
-
const resolvedHeaders = await
|
|
50715
|
-
const resolvedCredentials = await
|
|
50954
|
+
const resolvedBody = await resolve4(this.body);
|
|
50955
|
+
const resolvedHeaders = await resolve4(this.headers);
|
|
50956
|
+
const resolvedCredentials = await resolve4(this.credentials);
|
|
50716
50957
|
const baseHeaders = {
|
|
50717
50958
|
...normalizeHeaders(resolvedHeaders),
|
|
50718
50959
|
...normalizeHeaders(options.headers)
|
|
@@ -50760,9 +51001,9 @@ var import_api2, import_api3, __defProp2, __export2 = (target, all) => {
|
|
|
50760
51001
|
}
|
|
50761
51002
|
async reconnectToStream(options) {
|
|
50762
51003
|
var _a21, _b16, _c, _d, _e;
|
|
50763
|
-
const resolvedBody = await
|
|
50764
|
-
const resolvedHeaders = await
|
|
50765
|
-
const resolvedCredentials = await
|
|
51004
|
+
const resolvedBody = await resolve4(this.body);
|
|
51005
|
+
const resolvedHeaders = await resolve4(this.headers);
|
|
51006
|
+
const resolvedCredentials = await resolve4(this.credentials);
|
|
50766
51007
|
const baseHeaders = {
|
|
50767
51008
|
...normalizeHeaders(resolvedHeaders),
|
|
50768
51009
|
...normalizeHeaders(options.headers)
|
|
@@ -52042,7 +52283,7 @@ var init_dist8 = __esm(() => {
|
|
|
52042
52283
|
__export2(output_exports, {
|
|
52043
52284
|
array: () => array2,
|
|
52044
52285
|
choice: () => choice,
|
|
52045
|
-
json: () =>
|
|
52286
|
+
json: () => json4,
|
|
52046
52287
|
object: () => object2,
|
|
52047
52288
|
text: () => text
|
|
52048
52289
|
});
|
|
@@ -52705,7 +52946,7 @@ var init_openapi = __esm(() => {
|
|
|
52705
52946
|
// src/server/index.ts
|
|
52706
52947
|
import { existsSync as existsSync5 } from "fs";
|
|
52707
52948
|
import { createRequire } from "module";
|
|
52708
|
-
import { join as
|
|
52949
|
+
import { join as join7, resolve as resolve5, sep } from "path";
|
|
52709
52950
|
|
|
52710
52951
|
// src/lib/config.ts
|
|
52711
52952
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, unlinkSync, cpSync } from "fs";
|
|
@@ -54795,6 +55036,9 @@ addRoute("PATCH", "/api/memories/:id", async (req, _url, params) => {
|
|
|
54795
55036
|
if (!body) {
|
|
54796
55037
|
return errorResponse("Invalid JSON body", 400);
|
|
54797
55038
|
}
|
|
55039
|
+
if (Object.prototype.hasOwnProperty.call(body, "project_id")) {
|
|
55040
|
+
return errorResponse("project_id changes require the guarded existing-memory project-link route", 428, { code: "MEMORY_PROJECT_LINK_GUARD_REQUIRED" });
|
|
55041
|
+
}
|
|
54798
55042
|
const patchViolation = validateMemoryEnums(body);
|
|
54799
55043
|
if (patchViolation) {
|
|
54800
55044
|
return errorResponse(formatEnumViolation(patchViolation), 400, {
|
|
@@ -55677,6 +55921,10 @@ addRoute("POST", "/api/memories/search", async (req) => {
|
|
|
55677
55921
|
filter.category = body["category"];
|
|
55678
55922
|
if (body["tags"])
|
|
55679
55923
|
filter.tags = body["tags"];
|
|
55924
|
+
if (body["agent_id"])
|
|
55925
|
+
filter.agent_id = body["agent_id"];
|
|
55926
|
+
if (body["project_id"])
|
|
55927
|
+
filter.project_id = body["project_id"];
|
|
55680
55928
|
if (body["session_id"])
|
|
55681
55929
|
filter.session_id = body["session_id"];
|
|
55682
55930
|
if (body["namespace"])
|
|
@@ -56142,11 +56390,707 @@ ${lines.join(`
|
|
|
56142
56390
|
return json({ context, memories_count: lines.length });
|
|
56143
56391
|
});
|
|
56144
56392
|
|
|
56393
|
+
// src/db/memory-project-link.ts
|
|
56394
|
+
init_storage();
|
|
56395
|
+
init_api_mode();
|
|
56396
|
+
init_database();
|
|
56397
|
+
init_memories();
|
|
56398
|
+
import { createHash } from "crypto";
|
|
56399
|
+
|
|
56400
|
+
// src/lib/package-version.ts
|
|
56401
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
56402
|
+
import { dirname as dirname4, join as join6 } from "path";
|
|
56403
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
56404
|
+
function getMementosPackageVersion() {
|
|
56405
|
+
const here = dirname4(fileURLToPath3(import.meta.url));
|
|
56406
|
+
for (const candidate of [
|
|
56407
|
+
join6(here, "..", "..", "package.json"),
|
|
56408
|
+
join6(here, "..", "package.json")
|
|
56409
|
+
]) {
|
|
56410
|
+
try {
|
|
56411
|
+
const parsed = JSON.parse(readFileSync3(candidate, "utf8"));
|
|
56412
|
+
if (typeof parsed.version === "string" && parsed.version.trim())
|
|
56413
|
+
return parsed.version;
|
|
56414
|
+
} catch {}
|
|
56415
|
+
}
|
|
56416
|
+
return "0.0.0";
|
|
56417
|
+
}
|
|
56418
|
+
|
|
56419
|
+
// src/db/memory-project-link.ts
|
|
56420
|
+
init_schema();
|
|
56421
|
+
|
|
56422
|
+
class MemoryProjectLinkError extends Error {
|
|
56423
|
+
code;
|
|
56424
|
+
details;
|
|
56425
|
+
constructor(code, message, details = {}) {
|
|
56426
|
+
super(message);
|
|
56427
|
+
this.code = code;
|
|
56428
|
+
this.details = details;
|
|
56429
|
+
this.name = "MemoryProjectLinkError";
|
|
56430
|
+
}
|
|
56431
|
+
}
|
|
56432
|
+
var LINK_AUTHORITY = {
|
|
56433
|
+
authority_id: "mementos",
|
|
56434
|
+
tenant_id: "default",
|
|
56435
|
+
corpus_id: "default"
|
|
56436
|
+
};
|
|
56437
|
+
var BOUNDED_IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
|
|
56438
|
+
function canonicalize(value) {
|
|
56439
|
+
if (Array.isArray(value))
|
|
56440
|
+
return value.map(canonicalize);
|
|
56441
|
+
if (!value || typeof value !== "object")
|
|
56442
|
+
return value;
|
|
56443
|
+
const output = {};
|
|
56444
|
+
for (const key of Object.keys(value).sort()) {
|
|
56445
|
+
const item = value[key];
|
|
56446
|
+
if (item !== undefined)
|
|
56447
|
+
output[key] = canonicalize(item);
|
|
56448
|
+
}
|
|
56449
|
+
return output;
|
|
56450
|
+
}
|
|
56451
|
+
function canonicalJson(value) {
|
|
56452
|
+
return JSON.stringify(canonicalize(value));
|
|
56453
|
+
}
|
|
56454
|
+
function digest(value) {
|
|
56455
|
+
return createHash("sha256").update(canonicalJson(value)).digest("hex");
|
|
56456
|
+
}
|
|
56457
|
+
function timestampString(value) {
|
|
56458
|
+
return value instanceof Date ? value.toISOString() : String(value);
|
|
56459
|
+
}
|
|
56460
|
+
function nullableTimestamp(value) {
|
|
56461
|
+
return value === null || value === undefined ? null : timestampString(value);
|
|
56462
|
+
}
|
|
56463
|
+
function normalizeMemoryTimestamps(memory) {
|
|
56464
|
+
return {
|
|
56465
|
+
...memory,
|
|
56466
|
+
created_at: timestampString(memory.created_at),
|
|
56467
|
+
updated_at: timestampString(memory.updated_at),
|
|
56468
|
+
accessed_at: nullableTimestamp(memory.accessed_at),
|
|
56469
|
+
expires_at: nullableTimestamp(memory.expires_at),
|
|
56470
|
+
valid_from: nullableTimestamp(memory.valid_from),
|
|
56471
|
+
valid_until: nullableTimestamp(memory.valid_until),
|
|
56472
|
+
ingested_at: nullableTimestamp(memory.ingested_at)
|
|
56473
|
+
};
|
|
56474
|
+
}
|
|
56475
|
+
function parseProjectRow(row) {
|
|
56476
|
+
return {
|
|
56477
|
+
id: String(row["id"]),
|
|
56478
|
+
name: String(row["name"]),
|
|
56479
|
+
path: String(row["path"]),
|
|
56480
|
+
description: row["description"] === null ? null : String(row["description"] ?? "") || null,
|
|
56481
|
+
memory_prefix: row["memory_prefix"] === null ? null : String(row["memory_prefix"] ?? "") || null,
|
|
56482
|
+
created_at: timestampString(row["created_at"]),
|
|
56483
|
+
updated_at: timestampString(row["updated_at"])
|
|
56484
|
+
};
|
|
56485
|
+
}
|
|
56486
|
+
function memoryMutationState(memory) {
|
|
56487
|
+
const { access_count: _accessCount, accessed_at: _accessedAt, ...state } = memory;
|
|
56488
|
+
return state;
|
|
56489
|
+
}
|
|
56490
|
+
function memoryDigest(memory) {
|
|
56491
|
+
return digest(memoryMutationState(memory));
|
|
56492
|
+
}
|
|
56493
|
+
function projectDigest(project) {
|
|
56494
|
+
return digest(project);
|
|
56495
|
+
}
|
|
56496
|
+
function snapshot(memory) {
|
|
56497
|
+
return {
|
|
56498
|
+
memory_id: memory.id,
|
|
56499
|
+
project_id: memory.project_id,
|
|
56500
|
+
memory_version: memory.version,
|
|
56501
|
+
memory_revision: timestampString(memory.updated_at),
|
|
56502
|
+
memory_digest: memoryDigest(memory)
|
|
56503
|
+
};
|
|
56504
|
+
}
|
|
56505
|
+
function parseSnapshot(value) {
|
|
56506
|
+
const parsed = typeof value === "string" ? JSON.parse(value) : value;
|
|
56507
|
+
return parsed;
|
|
56508
|
+
}
|
|
56509
|
+
function receiptFromRow(row) {
|
|
56510
|
+
return {
|
|
56511
|
+
receipt_id: String(row["receipt_id"]),
|
|
56512
|
+
authority: "mementos",
|
|
56513
|
+
route: MEMENTOS_MEMORY_PROJECT_LINK_ROUTE,
|
|
56514
|
+
package_version: String(row["package_version"]),
|
|
56515
|
+
authority_id: String(row["authority_id"]),
|
|
56516
|
+
tenant_id: String(row["tenant_id"]),
|
|
56517
|
+
corpus_id: String(row["corpus_id"]),
|
|
56518
|
+
operation_id: String(row["operation_id"]),
|
|
56519
|
+
step_id: String(row["step_id"]),
|
|
56520
|
+
direction: row["direction"],
|
|
56521
|
+
idempotency_key: String(row["idempotency_key"]),
|
|
56522
|
+
request_digest: String(row["request_digest"]),
|
|
56523
|
+
outcome: row["outcome"],
|
|
56524
|
+
target_memory_id: String(row["target_memory_id"]),
|
|
56525
|
+
requested_project_id: String(row["requested_project_id"]),
|
|
56526
|
+
expected_memory_version: Number(row["expected_memory_version"]),
|
|
56527
|
+
expected_memory_revision: timestampString(row["expected_memory_revision"]),
|
|
56528
|
+
expected_project_revision: nullableTimestamp(row["expected_project_revision"]),
|
|
56529
|
+
result_memory_version: Number(row["result_memory_version"]),
|
|
56530
|
+
result_memory_revision: timestampString(row["result_memory_revision"]),
|
|
56531
|
+
result_memory_digest: String(row["result_memory_digest"]),
|
|
56532
|
+
result_project_revision: nullableTimestamp(row["result_project_revision"]),
|
|
56533
|
+
result_project_digest: row["result_project_digest"] === null ? null : String(row["result_project_digest"]),
|
|
56534
|
+
accepted_receipt_id: row["accepted_receipt_id"] === null ? null : String(row["accepted_receipt_id"]),
|
|
56535
|
+
before_link: parseSnapshot(row["before_link_json"]),
|
|
56536
|
+
after_link: parseSnapshot(row["after_link_json"]),
|
|
56537
|
+
before_project_revision: nullableTimestamp(row["before_project_revision"]),
|
|
56538
|
+
before_project_digest: row["before_project_digest"] === null ? null : String(row["before_project_digest"]),
|
|
56539
|
+
after_project_revision: nullableTimestamp(row["after_project_revision"]),
|
|
56540
|
+
after_project_digest: row["after_project_digest"] === null ? null : String(row["after_project_digest"]),
|
|
56541
|
+
created_at: timestampString(row["created_at"])
|
|
56542
|
+
};
|
|
56543
|
+
}
|
|
56544
|
+
function assertIdentity(identity) {
|
|
56545
|
+
if (identity.authority_id !== LINK_AUTHORITY.authority_id || identity.tenant_id !== LINK_AUTHORITY.tenant_id || identity.corpus_id !== LINK_AUTHORITY.corpus_id) {
|
|
56546
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_AUTHORITY_MISMATCH", "memory project link does not match this authority, tenant, and corpus");
|
|
56547
|
+
}
|
|
56548
|
+
}
|
|
56549
|
+
function assertBoundedIdentifier(value, field) {
|
|
56550
|
+
if (!BOUNDED_IDENTIFIER.test(value)) {
|
|
56551
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_INVALID_INPUT", `${field} must be an 8-128 character bounded identifier`);
|
|
56552
|
+
}
|
|
56553
|
+
}
|
|
56554
|
+
function assertMemoryGuard(version, revision) {
|
|
56555
|
+
if (!Number.isSafeInteger(version) || version < 1) {
|
|
56556
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_INVALID_INPUT", "expected_memory_version must be a positive safe integer");
|
|
56557
|
+
}
|
|
56558
|
+
if (!revision || revision.length > 128) {
|
|
56559
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_INVALID_INPUT", "expected_memory_revision is required and must be bounded");
|
|
56560
|
+
}
|
|
56561
|
+
}
|
|
56562
|
+
function assertForwardRequest(request) {
|
|
56563
|
+
assertIdentity(request);
|
|
56564
|
+
assertBoundedIdentifier(request.operation_id, "operation_id");
|
|
56565
|
+
assertBoundedIdentifier(request.step_id, "step_id");
|
|
56566
|
+
assertBoundedIdentifier(request.idempotency_key, "idempotency_key");
|
|
56567
|
+
assertBoundedIdentifier(request.target_project_id, "target_project_id");
|
|
56568
|
+
assertMemoryGuard(request.expected_memory_version, request.expected_memory_revision);
|
|
56569
|
+
if (!request.expected_project_revision || request.expected_project_revision.length > 128) {
|
|
56570
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_INVALID_INPUT", "expected_project_revision is required and must be bounded");
|
|
56571
|
+
}
|
|
56572
|
+
}
|
|
56573
|
+
function assertRollbackRequest(request) {
|
|
56574
|
+
assertIdentity(request);
|
|
56575
|
+
assertBoundedIdentifier(request.operation_id, "operation_id");
|
|
56576
|
+
assertBoundedIdentifier(request.step_id, "step_id");
|
|
56577
|
+
assertBoundedIdentifier(request.idempotency_key, "idempotency_key");
|
|
56578
|
+
assertBoundedIdentifier(request.accepted_receipt_id, "accepted_receipt_id");
|
|
56579
|
+
assertMemoryGuard(request.expected_memory_version, request.expected_memory_revision);
|
|
56580
|
+
}
|
|
56581
|
+
function forwardRequestDigest(memoryId, request) {
|
|
56582
|
+
return digest({
|
|
56583
|
+
authority_id: request.authority_id,
|
|
56584
|
+
tenant_id: request.tenant_id,
|
|
56585
|
+
corpus_id: request.corpus_id,
|
|
56586
|
+
operation_id: request.operation_id,
|
|
56587
|
+
step_id: request.step_id,
|
|
56588
|
+
idempotency_key: request.idempotency_key,
|
|
56589
|
+
expected_memory_version: request.expected_memory_version,
|
|
56590
|
+
expected_memory_revision: request.expected_memory_revision,
|
|
56591
|
+
target_project_id: request.target_project_id,
|
|
56592
|
+
expected_project_revision: request.expected_project_revision,
|
|
56593
|
+
direction: "forward",
|
|
56594
|
+
target_memory_id: memoryId
|
|
56595
|
+
});
|
|
56596
|
+
}
|
|
56597
|
+
function rollbackRequestDigest(memoryId, request) {
|
|
56598
|
+
return digest({
|
|
56599
|
+
authority_id: request.authority_id,
|
|
56600
|
+
tenant_id: request.tenant_id,
|
|
56601
|
+
corpus_id: request.corpus_id,
|
|
56602
|
+
operation_id: request.operation_id,
|
|
56603
|
+
step_id: request.step_id,
|
|
56604
|
+
idempotency_key: request.idempotency_key,
|
|
56605
|
+
expected_memory_version: request.expected_memory_version,
|
|
56606
|
+
expected_memory_revision: request.expected_memory_revision,
|
|
56607
|
+
accepted_receipt_id: request.accepted_receipt_id,
|
|
56608
|
+
direction: "rollback",
|
|
56609
|
+
target_memory_id: memoryId
|
|
56610
|
+
});
|
|
56611
|
+
}
|
|
56612
|
+
function forUpdateSuffix(db, lock) {
|
|
56613
|
+
return lock && db instanceof PgAdapter ? " FOR UPDATE" : "";
|
|
56614
|
+
}
|
|
56615
|
+
function getMemoryByExactId(id, db, lock = false) {
|
|
56616
|
+
const row = db.query(`SELECT * FROM memories WHERE id = ? LIMIT 1${forUpdateSuffix(db, lock)}`).get(id);
|
|
56617
|
+
return row ? normalizeMemoryTimestamps(parseMemoryRow(row)) : null;
|
|
56618
|
+
}
|
|
56619
|
+
function getProjectByExactId(id, db, lock = false) {
|
|
56620
|
+
const row = db.query(`SELECT * FROM projects WHERE id = ? LIMIT 1${forUpdateSuffix(db, lock)}`).get(id);
|
|
56621
|
+
return row ? parseProjectRow(row) : null;
|
|
56622
|
+
}
|
|
56623
|
+
function lockProjects(ids, db) {
|
|
56624
|
+
const projects = new Map;
|
|
56625
|
+
const uniqueIds = [...new Set(ids.filter((id) => Boolean(id)))].sort();
|
|
56626
|
+
for (const id of uniqueIds) {
|
|
56627
|
+
const project = getProjectByExactId(id, db, true);
|
|
56628
|
+
if (!project) {
|
|
56629
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_PROJECT_NOT_FOUND", `Project not found by exact stable ID: ${id}`);
|
|
56630
|
+
}
|
|
56631
|
+
projects.set(id, project);
|
|
56632
|
+
}
|
|
56633
|
+
return projects;
|
|
56634
|
+
}
|
|
56635
|
+
function assertMemoryPrecondition(memory, expectedVersion, expectedRevision) {
|
|
56636
|
+
if (memory.version !== expectedVersion || memory.updated_at !== expectedRevision) {
|
|
56637
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_STALE_MEMORY", "Memory changed before the guarded project link", {
|
|
56638
|
+
expected_memory_version: expectedVersion,
|
|
56639
|
+
current_memory_version: memory.version,
|
|
56640
|
+
expected_memory_revision: expectedRevision,
|
|
56641
|
+
current_memory_revision: memory.updated_at
|
|
56642
|
+
});
|
|
56643
|
+
}
|
|
56644
|
+
}
|
|
56645
|
+
function assertProjectPrecondition(project, expectedRevision) {
|
|
56646
|
+
if (project.updated_at !== expectedRevision) {
|
|
56647
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_STALE_PROJECT", "Project changed before the guarded memory link", {
|
|
56648
|
+
expected_project_revision: expectedRevision,
|
|
56649
|
+
current_project_revision: project.updated_at
|
|
56650
|
+
});
|
|
56651
|
+
}
|
|
56652
|
+
}
|
|
56653
|
+
function assertNoTargetBucketCollision(memory, projectId, db) {
|
|
56654
|
+
const collision = db.query(`
|
|
56655
|
+
SELECT id FROM memories
|
|
56656
|
+
WHERE id != ? AND key = ? AND scope = ?
|
|
56657
|
+
AND COALESCE(agent_id, '') = COALESCE(?, '')
|
|
56658
|
+
AND COALESCE(project_id, '') = COALESCE(?, '')
|
|
56659
|
+
AND COALESCE(session_id, '') = COALESCE(?, '')
|
|
56660
|
+
LIMIT 1
|
|
56661
|
+
`).get(memory.id, memory.key, memory.scope, memory.agent_id, projectId, memory.session_id);
|
|
56662
|
+
if (collision) {
|
|
56663
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_COLLISION", "Linking this memory would collide with another memory in the target project bucket", { collision_id: collision.id });
|
|
56664
|
+
}
|
|
56665
|
+
}
|
|
56666
|
+
function nextRevision(previous) {
|
|
56667
|
+
const candidate = now();
|
|
56668
|
+
const previousMs = Date.parse(previous);
|
|
56669
|
+
const candidateMs = Date.parse(candidate);
|
|
56670
|
+
if (Number.isFinite(previousMs) && Number.isFinite(candidateMs) && candidateMs <= previousMs) {
|
|
56671
|
+
return new Date(previousMs + 1).toISOString();
|
|
56672
|
+
}
|
|
56673
|
+
return candidate;
|
|
56674
|
+
}
|
|
56675
|
+
function findReceiptByKey(db, identity, direction, idempotencyKey) {
|
|
56676
|
+
const row = db.query(`
|
|
56677
|
+
SELECT * FROM mementos_memory_project_link_receipts
|
|
56678
|
+
WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
56679
|
+
AND direction = ? AND idempotency_key = ?
|
|
56680
|
+
LIMIT 1
|
|
56681
|
+
`).get(identity.authority_id, identity.tenant_id, identity.corpus_id, direction, idempotencyKey);
|
|
56682
|
+
return row ? receiptFromRow(row) : null;
|
|
56683
|
+
}
|
|
56684
|
+
function findReceiptById(db, identity, receiptId) {
|
|
56685
|
+
const row = db.query(`
|
|
56686
|
+
SELECT * FROM mementos_memory_project_link_receipts
|
|
56687
|
+
WHERE receipt_id = ? AND authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
56688
|
+
LIMIT 1
|
|
56689
|
+
`).get(receiptId, identity.authority_id, identity.tenant_id, identity.corpus_id);
|
|
56690
|
+
return row ? receiptFromRow(row) : null;
|
|
56691
|
+
}
|
|
56692
|
+
function insertReceipt(db, receipt) {
|
|
56693
|
+
db.run(`
|
|
56694
|
+
INSERT INTO mementos_memory_project_link_receipts (
|
|
56695
|
+
receipt_id, authority, route, package_version, authority_id, tenant_id,
|
|
56696
|
+
corpus_id, operation_id, step_id, direction, idempotency_key,
|
|
56697
|
+
request_digest, outcome, target_memory_id, requested_project_id,
|
|
56698
|
+
expected_memory_version, expected_memory_revision,
|
|
56699
|
+
expected_project_revision, result_memory_version,
|
|
56700
|
+
result_memory_revision, result_memory_digest, result_project_revision,
|
|
56701
|
+
result_project_digest, accepted_receipt_id, before_link_json,
|
|
56702
|
+
after_link_json, before_project_revision, before_project_digest,
|
|
56703
|
+
after_project_revision, after_project_digest, created_at
|
|
56704
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
56705
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
56706
|
+
`, [
|
|
56707
|
+
receipt.receipt_id,
|
|
56708
|
+
receipt.authority,
|
|
56709
|
+
receipt.route,
|
|
56710
|
+
receipt.package_version,
|
|
56711
|
+
receipt.authority_id,
|
|
56712
|
+
receipt.tenant_id,
|
|
56713
|
+
receipt.corpus_id,
|
|
56714
|
+
receipt.operation_id,
|
|
56715
|
+
receipt.step_id,
|
|
56716
|
+
receipt.direction,
|
|
56717
|
+
receipt.idempotency_key,
|
|
56718
|
+
receipt.request_digest,
|
|
56719
|
+
receipt.outcome,
|
|
56720
|
+
receipt.target_memory_id,
|
|
56721
|
+
receipt.requested_project_id,
|
|
56722
|
+
receipt.expected_memory_version,
|
|
56723
|
+
receipt.expected_memory_revision,
|
|
56724
|
+
receipt.expected_project_revision,
|
|
56725
|
+
receipt.result_memory_version,
|
|
56726
|
+
receipt.result_memory_revision,
|
|
56727
|
+
receipt.result_memory_digest,
|
|
56728
|
+
receipt.result_project_revision,
|
|
56729
|
+
receipt.result_project_digest,
|
|
56730
|
+
receipt.accepted_receipt_id,
|
|
56731
|
+
canonicalJson(receipt.before_link),
|
|
56732
|
+
canonicalJson(receipt.after_link),
|
|
56733
|
+
receipt.before_project_revision,
|
|
56734
|
+
receipt.before_project_digest,
|
|
56735
|
+
receipt.after_project_revision,
|
|
56736
|
+
receipt.after_project_digest,
|
|
56737
|
+
receipt.created_at
|
|
56738
|
+
]);
|
|
56739
|
+
}
|
|
56740
|
+
function makeReceipt(input) {
|
|
56741
|
+
const beforeLink = snapshot(input.beforeMemory);
|
|
56742
|
+
const afterLink = snapshot(input.afterMemory);
|
|
56743
|
+
const logical = {
|
|
56744
|
+
authority: "mementos",
|
|
56745
|
+
route: MEMENTOS_MEMORY_PROJECT_LINK_ROUTE,
|
|
56746
|
+
package_version: getMementosPackageVersion(),
|
|
56747
|
+
authority_id: input.request.authority_id,
|
|
56748
|
+
tenant_id: input.request.tenant_id,
|
|
56749
|
+
corpus_id: input.request.corpus_id,
|
|
56750
|
+
operation_id: input.request.operation_id,
|
|
56751
|
+
step_id: input.request.step_id,
|
|
56752
|
+
direction: input.direction,
|
|
56753
|
+
idempotency_key: input.request.idempotency_key,
|
|
56754
|
+
request_digest: input.requestDigest,
|
|
56755
|
+
outcome: input.outcome,
|
|
56756
|
+
target_memory_id: input.targetMemoryId,
|
|
56757
|
+
requested_project_id: input.requestedProjectId,
|
|
56758
|
+
expected_memory_version: input.request.expected_memory_version,
|
|
56759
|
+
expected_memory_revision: input.request.expected_memory_revision,
|
|
56760
|
+
expected_project_revision: input.expectedProjectRevision,
|
|
56761
|
+
result_memory_version: afterLink.memory_version,
|
|
56762
|
+
result_memory_revision: afterLink.memory_revision,
|
|
56763
|
+
result_memory_digest: afterLink.memory_digest,
|
|
56764
|
+
result_project_revision: input.afterProject?.updated_at ?? null,
|
|
56765
|
+
result_project_digest: input.afterProject ? projectDigest(input.afterProject) : null,
|
|
56766
|
+
accepted_receipt_id: input.acceptedReceiptId ?? null,
|
|
56767
|
+
before_link: beforeLink,
|
|
56768
|
+
after_link: afterLink,
|
|
56769
|
+
before_project_revision: input.beforeProject?.updated_at ?? null,
|
|
56770
|
+
before_project_digest: input.beforeProject ? projectDigest(input.beforeProject) : null,
|
|
56771
|
+
after_project_revision: input.afterProject?.updated_at ?? null,
|
|
56772
|
+
after_project_digest: input.afterProject ? projectDigest(input.afterProject) : null
|
|
56773
|
+
};
|
|
56774
|
+
return {
|
|
56775
|
+
receipt_id: `mmpl_${digest(logical).slice(0, 40)}`,
|
|
56776
|
+
...logical,
|
|
56777
|
+
created_at: now()
|
|
56778
|
+
};
|
|
56779
|
+
}
|
|
56780
|
+
function sameSnapshot(memory, expected) {
|
|
56781
|
+
return canonicalJson(snapshot(memory)) === canonicalJson(expected);
|
|
56782
|
+
}
|
|
56783
|
+
function assertProjectSnapshot(project, revision, expectedDigest) {
|
|
56784
|
+
if (!revision && !expectedDigest) {
|
|
56785
|
+
if (project) {
|
|
56786
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_ACCEPTED_TARGET_DRIFTED", "receipt expected no linked project but a project row was resolved");
|
|
56787
|
+
}
|
|
56788
|
+
return;
|
|
56789
|
+
}
|
|
56790
|
+
if (!project || project.updated_at !== revision || projectDigest(project) !== expectedDigest) {
|
|
56791
|
+
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 });
|
|
56792
|
+
}
|
|
56793
|
+
}
|
|
56794
|
+
function replayResult(db, receipt, requestDigest, memoryId) {
|
|
56795
|
+
if (receipt.request_digest !== requestDigest || receipt.target_memory_id !== memoryId) {
|
|
56796
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_IDEMPOTENCY_MISMATCH", "caller idempotency key is already bound to a different memory project-link request");
|
|
56797
|
+
}
|
|
56798
|
+
const memory = getMemoryByExactId(memoryId, db);
|
|
56799
|
+
if (!memory || !sameSnapshot(memory, receipt.after_link)) {
|
|
56800
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_ACCEPTED_TARGET_DRIFTED", "accepted memory project-link target drifted after its immutable receipt");
|
|
56801
|
+
}
|
|
56802
|
+
const project = receipt.after_link.project_id ? getProjectByExactId(receipt.after_link.project_id, db) : null;
|
|
56803
|
+
assertProjectSnapshot(project, receipt.after_project_revision, receipt.after_project_digest);
|
|
56804
|
+
return {
|
|
56805
|
+
dry_run: false,
|
|
56806
|
+
applied: receipt.outcome === "accepted",
|
|
56807
|
+
no_change: receipt.outcome === "no_change",
|
|
56808
|
+
memory,
|
|
56809
|
+
project,
|
|
56810
|
+
receipt
|
|
56811
|
+
};
|
|
56812
|
+
}
|
|
56813
|
+
function previewMemoryProjectLink(memoryId, request, db) {
|
|
56814
|
+
assertForwardRequest(request);
|
|
56815
|
+
assertBoundedIdentifier(memoryId, "memory_id");
|
|
56816
|
+
if (!db && isApiMode()) {
|
|
56817
|
+
const { data } = apiJson("POST", `/memories/${encodeURIComponent(memoryId)}/guarded-project-link`, { ...request, dry_run: true });
|
|
56818
|
+
return data;
|
|
56819
|
+
}
|
|
56820
|
+
const d = db || getDatabase();
|
|
56821
|
+
const memory = getMemoryByExactId(memoryId, d);
|
|
56822
|
+
if (!memory) {
|
|
56823
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_MEMORY_NOT_FOUND", `Memory not found by exact stable ID: ${memoryId}`);
|
|
56824
|
+
}
|
|
56825
|
+
const project = getProjectByExactId(request.target_project_id, d);
|
|
56826
|
+
if (!project) {
|
|
56827
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_PROJECT_NOT_FOUND", `Project not found by exact stable ID: ${request.target_project_id}`);
|
|
56828
|
+
}
|
|
56829
|
+
assertMemoryPrecondition(memory, request.expected_memory_version, request.expected_memory_revision);
|
|
56830
|
+
assertProjectPrecondition(project, request.expected_project_revision);
|
|
56831
|
+
const noChange = memory.project_id === project.id;
|
|
56832
|
+
if (!noChange)
|
|
56833
|
+
assertNoTargetBucketCollision(memory, project.id, d);
|
|
56834
|
+
return {
|
|
56835
|
+
dry_run: true,
|
|
56836
|
+
applied: false,
|
|
56837
|
+
no_change: noChange,
|
|
56838
|
+
memory: noChange ? memory : { ...memory, project_id: project.id },
|
|
56839
|
+
project,
|
|
56840
|
+
receipt: null
|
|
56841
|
+
};
|
|
56842
|
+
}
|
|
56843
|
+
function applyMemoryProjectLink(memoryId, request, db) {
|
|
56844
|
+
assertForwardRequest(request);
|
|
56845
|
+
assertBoundedIdentifier(memoryId, "memory_id");
|
|
56846
|
+
if (!db && isApiMode()) {
|
|
56847
|
+
const { data } = apiJson("POST", `/memories/${encodeURIComponent(memoryId)}/guarded-project-link`, { ...request, dry_run: false });
|
|
56848
|
+
return data;
|
|
56849
|
+
}
|
|
56850
|
+
const d = db || getDatabase();
|
|
56851
|
+
const requestDigest = forwardRequestDigest(memoryId, request);
|
|
56852
|
+
return d.transaction(() => {
|
|
56853
|
+
const prior = findReceiptByKey(d, request, "forward", request.idempotency_key);
|
|
56854
|
+
if (prior)
|
|
56855
|
+
return replayResult(d, prior, requestDigest, memoryId);
|
|
56856
|
+
const before = getMemoryByExactId(memoryId, d, true);
|
|
56857
|
+
if (!before) {
|
|
56858
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_MEMORY_NOT_FOUND", `Memory not found by exact stable ID: ${memoryId}`);
|
|
56859
|
+
}
|
|
56860
|
+
const committedPrior = findReceiptByKey(d, request, "forward", request.idempotency_key);
|
|
56861
|
+
if (committedPrior)
|
|
56862
|
+
return replayResult(d, committedPrior, requestDigest, memoryId);
|
|
56863
|
+
assertMemoryPrecondition(before, request.expected_memory_version, request.expected_memory_revision);
|
|
56864
|
+
const projects = lockProjects([before.project_id, request.target_project_id], d);
|
|
56865
|
+
const targetProject = projects.get(request.target_project_id);
|
|
56866
|
+
const beforeProject = before.project_id ? projects.get(before.project_id) : null;
|
|
56867
|
+
assertProjectPrecondition(targetProject, request.expected_project_revision);
|
|
56868
|
+
const noChange = before.project_id === request.target_project_id;
|
|
56869
|
+
if (noChange) {
|
|
56870
|
+
const receipt2 = makeReceipt({
|
|
56871
|
+
request,
|
|
56872
|
+
direction: "forward",
|
|
56873
|
+
requestDigest,
|
|
56874
|
+
outcome: "no_change",
|
|
56875
|
+
targetMemoryId: memoryId,
|
|
56876
|
+
requestedProjectId: request.target_project_id,
|
|
56877
|
+
expectedProjectRevision: request.expected_project_revision,
|
|
56878
|
+
beforeMemory: before,
|
|
56879
|
+
afterMemory: before,
|
|
56880
|
+
beforeProject,
|
|
56881
|
+
afterProject: targetProject
|
|
56882
|
+
});
|
|
56883
|
+
insertReceipt(d, receipt2);
|
|
56884
|
+
return {
|
|
56885
|
+
dry_run: false,
|
|
56886
|
+
applied: false,
|
|
56887
|
+
no_change: true,
|
|
56888
|
+
memory: before,
|
|
56889
|
+
project: targetProject,
|
|
56890
|
+
receipt: receipt2
|
|
56891
|
+
};
|
|
56892
|
+
}
|
|
56893
|
+
assertNoTargetBucketCollision(before, targetProject.id, d);
|
|
56894
|
+
const revision = nextRevision(before.updated_at);
|
|
56895
|
+
const result = d.run(`
|
|
56896
|
+
UPDATE memories
|
|
56897
|
+
SET project_id = ?, updated_at = ?
|
|
56898
|
+
WHERE id = ? AND version = ? AND updated_at = ?
|
|
56899
|
+
AND COALESCE(project_id, '') = COALESCE(?, '')
|
|
56900
|
+
`, [
|
|
56901
|
+
targetProject.id,
|
|
56902
|
+
revision,
|
|
56903
|
+
memoryId,
|
|
56904
|
+
before.version,
|
|
56905
|
+
before.updated_at,
|
|
56906
|
+
before.project_id
|
|
56907
|
+
]);
|
|
56908
|
+
if (result.changes === 0) {
|
|
56909
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_STALE_MEMORY", "Memory project-link compare-and-swap did not update exactly one row");
|
|
56910
|
+
}
|
|
56911
|
+
const after = getMemoryByExactId(memoryId, d);
|
|
56912
|
+
const expectedAfter = { ...before, project_id: targetProject.id, updated_at: revision };
|
|
56913
|
+
if (!after || canonicalJson(memoryMutationState(after)) !== canonicalJson(memoryMutationState(expectedAfter))) {
|
|
56914
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_ACCEPTED_TARGET_DRIFTED", "Memory project link did not read back exactly under the stable ID");
|
|
56915
|
+
}
|
|
56916
|
+
const receipt = makeReceipt({
|
|
56917
|
+
request,
|
|
56918
|
+
direction: "forward",
|
|
56919
|
+
requestDigest,
|
|
56920
|
+
outcome: "accepted",
|
|
56921
|
+
targetMemoryId: memoryId,
|
|
56922
|
+
requestedProjectId: request.target_project_id,
|
|
56923
|
+
expectedProjectRevision: request.expected_project_revision,
|
|
56924
|
+
beforeMemory: before,
|
|
56925
|
+
afterMemory: after,
|
|
56926
|
+
beforeProject,
|
|
56927
|
+
afterProject: targetProject
|
|
56928
|
+
});
|
|
56929
|
+
insertReceipt(d, receipt);
|
|
56930
|
+
return {
|
|
56931
|
+
dry_run: false,
|
|
56932
|
+
applied: true,
|
|
56933
|
+
no_change: false,
|
|
56934
|
+
memory: after,
|
|
56935
|
+
project: targetProject,
|
|
56936
|
+
receipt
|
|
56937
|
+
};
|
|
56938
|
+
});
|
|
56939
|
+
}
|
|
56940
|
+
function rollbackMemoryProjectLink(memoryId, request, db) {
|
|
56941
|
+
assertRollbackRequest(request);
|
|
56942
|
+
assertBoundedIdentifier(memoryId, "memory_id");
|
|
56943
|
+
if (!db && isApiMode()) {
|
|
56944
|
+
const { data } = apiJson("POST", `/memories/${encodeURIComponent(memoryId)}/guarded-project-link-rollback`, request);
|
|
56945
|
+
return data;
|
|
56946
|
+
}
|
|
56947
|
+
const d = db || getDatabase();
|
|
56948
|
+
const requestDigest = rollbackRequestDigest(memoryId, request);
|
|
56949
|
+
return d.transaction(() => {
|
|
56950
|
+
const prior = findReceiptByKey(d, request, "rollback", request.idempotency_key);
|
|
56951
|
+
if (prior)
|
|
56952
|
+
return replayResult(d, prior, requestDigest, memoryId);
|
|
56953
|
+
const accepted = findReceiptById(d, request, request.accepted_receipt_id);
|
|
56954
|
+
if (!accepted || accepted.direction !== "forward" || accepted.target_memory_id !== memoryId) {
|
|
56955
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_RECEIPT_NOT_FOUND", "accepted forward link receipt was not found for this exact memory");
|
|
56956
|
+
}
|
|
56957
|
+
if (accepted.outcome !== "accepted") {
|
|
56958
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_RECEIPT_NOT_ROLLBACKABLE", "a no-change memory project-link receipt has no mutation to roll back");
|
|
56959
|
+
}
|
|
56960
|
+
const current = getMemoryByExactId(memoryId, d, true);
|
|
56961
|
+
if (!current) {
|
|
56962
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_MEMORY_NOT_FOUND", `Memory not found by exact stable ID: ${memoryId}`);
|
|
56963
|
+
}
|
|
56964
|
+
const committedPrior = findReceiptByKey(d, request, "rollback", request.idempotency_key);
|
|
56965
|
+
if (committedPrior)
|
|
56966
|
+
return replayResult(d, committedPrior, requestDigest, memoryId);
|
|
56967
|
+
assertMemoryPrecondition(current, request.expected_memory_version, request.expected_memory_revision);
|
|
56968
|
+
if (!sameSnapshot(current, accepted.after_link)) {
|
|
56969
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_STALE_MEMORY", "Memory no longer matches the accepted forward link receipt");
|
|
56970
|
+
}
|
|
56971
|
+
const projects = lockProjects([accepted.after_link.project_id, accepted.before_link.project_id], d);
|
|
56972
|
+
const beforeProject = accepted.after_link.project_id ? projects.get(accepted.after_link.project_id) : null;
|
|
56973
|
+
const afterProject = accepted.before_link.project_id ? projects.get(accepted.before_link.project_id) : null;
|
|
56974
|
+
assertProjectSnapshot(beforeProject, accepted.after_project_revision, accepted.after_project_digest);
|
|
56975
|
+
assertProjectSnapshot(afterProject, accepted.before_project_revision, accepted.before_project_digest);
|
|
56976
|
+
assertNoTargetBucketCollision(current, accepted.before_link.project_id, d);
|
|
56977
|
+
const result = d.run(`
|
|
56978
|
+
UPDATE memories
|
|
56979
|
+
SET project_id = ?, updated_at = ?
|
|
56980
|
+
WHERE id = ? AND version = ? AND updated_at = ?
|
|
56981
|
+
AND COALESCE(project_id, '') = COALESCE(?, '')
|
|
56982
|
+
`, [
|
|
56983
|
+
accepted.before_link.project_id,
|
|
56984
|
+
accepted.before_link.memory_revision,
|
|
56985
|
+
memoryId,
|
|
56986
|
+
current.version,
|
|
56987
|
+
current.updated_at,
|
|
56988
|
+
current.project_id
|
|
56989
|
+
]);
|
|
56990
|
+
if (result.changes === 0) {
|
|
56991
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_STALE_MEMORY", "Memory project-link rollback compare-and-swap did not update exactly one row");
|
|
56992
|
+
}
|
|
56993
|
+
const restored = getMemoryByExactId(memoryId, d);
|
|
56994
|
+
if (!restored || !sameSnapshot(restored, accepted.before_link)) {
|
|
56995
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_ACCEPTED_TARGET_DRIFTED", "Memory project-link rollback did not restore the exact prior linkage");
|
|
56996
|
+
}
|
|
56997
|
+
const receipt = makeReceipt({
|
|
56998
|
+
request,
|
|
56999
|
+
direction: "rollback",
|
|
57000
|
+
requestDigest,
|
|
57001
|
+
outcome: "accepted",
|
|
57002
|
+
targetMemoryId: memoryId,
|
|
57003
|
+
requestedProjectId: accepted.requested_project_id,
|
|
57004
|
+
expectedProjectRevision: null,
|
|
57005
|
+
beforeMemory: current,
|
|
57006
|
+
afterMemory: restored,
|
|
57007
|
+
beforeProject,
|
|
57008
|
+
afterProject,
|
|
57009
|
+
acceptedReceiptId: accepted.receipt_id
|
|
57010
|
+
});
|
|
57011
|
+
insertReceipt(d, receipt);
|
|
57012
|
+
return {
|
|
57013
|
+
dry_run: false,
|
|
57014
|
+
applied: true,
|
|
57015
|
+
no_change: false,
|
|
57016
|
+
memory: restored,
|
|
57017
|
+
project: afterProject,
|
|
57018
|
+
receipt
|
|
57019
|
+
};
|
|
57020
|
+
});
|
|
57021
|
+
}
|
|
57022
|
+
function getMemoryProjectLinkReceipt(memoryId, receiptId, identity = LINK_AUTHORITY, db) {
|
|
57023
|
+
assertIdentity(identity);
|
|
57024
|
+
assertBoundedIdentifier(memoryId, "memory_id");
|
|
57025
|
+
assertBoundedIdentifier(receiptId, "receipt_id");
|
|
57026
|
+
if (!db && isApiMode()) {
|
|
57027
|
+
const { data } = apiJson("POST", `/memories/${encodeURIComponent(memoryId)}/project-link-receipts/lookup`, { ...identity, receipt_id: receiptId });
|
|
57028
|
+
return data;
|
|
57029
|
+
}
|
|
57030
|
+
const d = db || getDatabase();
|
|
57031
|
+
const receipt = findReceiptById(d, identity, receiptId);
|
|
57032
|
+
if (!receipt || receipt.target_memory_id !== memoryId) {
|
|
57033
|
+
throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_RECEIPT_NOT_FOUND", "immutable memory project-link receipt was not found for this exact memory");
|
|
57034
|
+
}
|
|
57035
|
+
return receipt;
|
|
57036
|
+
}
|
|
57037
|
+
|
|
57038
|
+
// src/server/routes/memory-project-link.ts
|
|
57039
|
+
init_router();
|
|
57040
|
+
function linkError(error) {
|
|
57041
|
+
const status = error.code === "MEMORY_PROJECT_LINK_AUTHORITY_MISMATCH" ? 403 : error.code === "MEMORY_PROJECT_LINK_MEMORY_NOT_FOUND" || error.code === "MEMORY_PROJECT_LINK_PROJECT_NOT_FOUND" || error.code === "MEMORY_PROJECT_LINK_RECEIPT_NOT_FOUND" ? 404 : error.code === "MEMORY_PROJECT_LINK_INVALID_INPUT" ? 400 : 409;
|
|
57042
|
+
return errorResponse(error.message, status, { code: error.code, ...error.details });
|
|
57043
|
+
}
|
|
57044
|
+
addRoute("POST", "/api/memories/:id/guarded-project-link", async (req, _url, params) => {
|
|
57045
|
+
const body = await readJson(req);
|
|
57046
|
+
if (!body)
|
|
57047
|
+
return errorResponse("Invalid JSON body", 400);
|
|
57048
|
+
try {
|
|
57049
|
+
const request = body;
|
|
57050
|
+
return json(request.dry_run ? previewMemoryProjectLink(params["id"], request) : applyMemoryProjectLink(params["id"], request));
|
|
57051
|
+
} catch (error) {
|
|
57052
|
+
if (error instanceof MemoryProjectLinkError)
|
|
57053
|
+
return linkError(error);
|
|
57054
|
+
throw error;
|
|
57055
|
+
}
|
|
57056
|
+
});
|
|
57057
|
+
addRoute("POST", "/api/memories/:id/guarded-project-link-rollback", async (req, _url, params) => {
|
|
57058
|
+
const body = await readJson(req);
|
|
57059
|
+
if (!body)
|
|
57060
|
+
return errorResponse("Invalid JSON body", 400);
|
|
57061
|
+
try {
|
|
57062
|
+
return json(rollbackMemoryProjectLink(params["id"], body));
|
|
57063
|
+
} catch (error) {
|
|
57064
|
+
if (error instanceof MemoryProjectLinkError)
|
|
57065
|
+
return linkError(error);
|
|
57066
|
+
throw error;
|
|
57067
|
+
}
|
|
57068
|
+
});
|
|
57069
|
+
addRoute("POST", "/api/memories/:id/project-link-receipts/lookup", async (req, _url, params) => {
|
|
57070
|
+
const body = await readJson(req);
|
|
57071
|
+
if (!body || typeof body["receipt_id"] !== "string") {
|
|
57072
|
+
return errorResponse("receipt_id is required", 400);
|
|
57073
|
+
}
|
|
57074
|
+
try {
|
|
57075
|
+
const identity = {
|
|
57076
|
+
authority_id: String(body["authority_id"] ?? ""),
|
|
57077
|
+
tenant_id: String(body["tenant_id"] ?? ""),
|
|
57078
|
+
corpus_id: String(body["corpus_id"] ?? "")
|
|
57079
|
+
};
|
|
57080
|
+
return json(getMemoryProjectLinkReceipt(params["id"], body["receipt_id"], identity));
|
|
57081
|
+
} catch (error) {
|
|
57082
|
+
if (error instanceof MemoryProjectLinkError)
|
|
57083
|
+
return linkError(error);
|
|
57084
|
+
throw error;
|
|
57085
|
+
}
|
|
57086
|
+
});
|
|
57087
|
+
|
|
56145
57088
|
// src/db/agents.ts
|
|
56146
57089
|
init_types();
|
|
56147
57090
|
init_database();
|
|
56148
57091
|
init_api_mode();
|
|
56149
57092
|
var CONFLICT_WINDOW_MS = 30 * 60 * 1000;
|
|
57093
|
+
var UNBOUNDED_AGENT_LIST_LIMIT = Number.MAX_SAFE_INTEGER;
|
|
56150
57094
|
function parseAgentRow(row) {
|
|
56151
57095
|
return {
|
|
56152
57096
|
id: row["id"],
|
|
@@ -56238,24 +57182,61 @@ function getAgent(idOrName, db) {
|
|
|
56238
57182
|
return parseAgentRow(rows[0]);
|
|
56239
57183
|
return null;
|
|
56240
57184
|
}
|
|
56241
|
-
function
|
|
56242
|
-
|
|
56243
|
-
|
|
57185
|
+
function normalizedAgentListFilter(filter) {
|
|
57186
|
+
const limit = Number.isFinite(filter.limit) ? Math.max(0, Math.floor(filter.limit)) : undefined;
|
|
57187
|
+
const offset = Number.isFinite(filter.offset) ? Math.max(0, Math.floor(filter.offset)) : undefined;
|
|
57188
|
+
return { limit, offset };
|
|
57189
|
+
}
|
|
57190
|
+
function isDatabaseAdapter(value) {
|
|
57191
|
+
return typeof value === "object" && value !== null && "query" in value && typeof value.query === "function";
|
|
57192
|
+
}
|
|
57193
|
+
function paginatedAgentQuery(baseSql, baseParams, filter) {
|
|
57194
|
+
const { limit, offset } = normalizedAgentListFilter(filter);
|
|
57195
|
+
let sql = `${baseSql} ORDER BY created_at ASC, id ASC`;
|
|
57196
|
+
const params = [...baseParams];
|
|
57197
|
+
if (limit !== undefined) {
|
|
57198
|
+
sql += " LIMIT ?";
|
|
57199
|
+
params.push(limit);
|
|
57200
|
+
} else if ((offset ?? 0) > 0) {
|
|
57201
|
+
sql += " LIMIT ?";
|
|
57202
|
+
params.push(UNBOUNDED_AGENT_LIST_LIMIT);
|
|
57203
|
+
}
|
|
57204
|
+
if ((offset ?? 0) > 0) {
|
|
57205
|
+
sql += " OFFSET ?";
|
|
57206
|
+
params.push(offset);
|
|
57207
|
+
}
|
|
57208
|
+
return { sql, params };
|
|
57209
|
+
}
|
|
57210
|
+
function listAgents(filterOrDb = {}, db) {
|
|
57211
|
+
const explicitDb = isDatabaseAdapter(filterOrDb) ? filterOrDb : db;
|
|
57212
|
+
const filter = isDatabaseAdapter(filterOrDb) ? {} : filterOrDb;
|
|
57213
|
+
const normalized = normalizedAgentListFilter(filter);
|
|
57214
|
+
if (!explicitDb && isApiMode()) {
|
|
57215
|
+
const q = toQuery({
|
|
57216
|
+
limit: normalized.limit,
|
|
57217
|
+
offset: normalized.offset
|
|
57218
|
+
});
|
|
57219
|
+
const { data } = apiJson("GET", `/agents${q}`);
|
|
56244
57220
|
return data?.agents ?? [];
|
|
56245
57221
|
}
|
|
56246
|
-
const d =
|
|
56247
|
-
const
|
|
57222
|
+
const d = explicitDb || getDatabase();
|
|
57223
|
+
const { sql, params } = paginatedAgentQuery("SELECT * FROM agents", [], normalized);
|
|
57224
|
+
const rows = d.query(sql).all(...params);
|
|
56248
57225
|
return rows.map(parseAgentRow);
|
|
56249
57226
|
}
|
|
56250
|
-
function listAgentsByProject(projectId, db) {
|
|
56251
|
-
|
|
56252
|
-
|
|
57227
|
+
function listAgentsByProject(projectId, filterOrDb = {}, db) {
|
|
57228
|
+
const explicitDb = isDatabaseAdapter(filterOrDb) ? filterOrDb : db;
|
|
57229
|
+
const filter = isDatabaseAdapter(filterOrDb) ? {} : filterOrDb;
|
|
57230
|
+
const normalized = normalizedAgentListFilter(filter);
|
|
57231
|
+
if (!explicitDb && isApiMode()) {
|
|
57232
|
+
const q = toQuery({ project_id: projectId, ...normalized });
|
|
56253
57233
|
const { data } = apiJson("GET", `/agents${q}`);
|
|
56254
57234
|
return data?.agents ?? [];
|
|
56255
57235
|
}
|
|
56256
|
-
const d =
|
|
57236
|
+
const d = explicitDb || getDatabase();
|
|
56257
57237
|
const resolvedId = resolvePartialId(d, "projects", projectId) || projectId;
|
|
56258
|
-
const
|
|
57238
|
+
const { sql, params } = paginatedAgentQuery("SELECT * FROM agents WHERE active_project_id = ?", [resolvedId], normalized);
|
|
57239
|
+
const rows = d.query(sql).all(...params);
|
|
56259
57240
|
return rows.map(parseAgentRow);
|
|
56260
57241
|
}
|
|
56261
57242
|
function updateAgent(id, updates, db) {
|
|
@@ -56418,9 +57399,21 @@ function cleanExpiredLocks(db) {
|
|
|
56418
57399
|
|
|
56419
57400
|
// src/server/routes/agents.ts
|
|
56420
57401
|
init_router();
|
|
57402
|
+
function paginationInt(value) {
|
|
57403
|
+
if (value === undefined)
|
|
57404
|
+
return;
|
|
57405
|
+
const parsed = Number(value);
|
|
57406
|
+
if (!Number.isFinite(parsed) || parsed < 0)
|
|
57407
|
+
return;
|
|
57408
|
+
return Math.floor(parsed);
|
|
57409
|
+
}
|
|
56421
57410
|
addRoute("GET", "/api/agents", (_req, url) => {
|
|
56422
57411
|
const q = getSearchParams(url);
|
|
56423
|
-
const
|
|
57412
|
+
const pagination = {
|
|
57413
|
+
limit: paginationInt(q["limit"]),
|
|
57414
|
+
offset: paginationInt(q["cursor"] ?? q["offset"])
|
|
57415
|
+
};
|
|
57416
|
+
const agents = q["project_id"] ? listAgentsByProject(q["project_id"], pagination) : listAgents(pagination);
|
|
56424
57417
|
if (q["fields"]) {
|
|
56425
57418
|
const fields = q["fields"].split(",").map((f) => f.trim());
|
|
56426
57419
|
const filtered = agents.map((a) => Object.fromEntries(fields.map((f) => [f, a[f]]).filter(([, v]) => v !== undefined)));
|
|
@@ -56513,7 +57506,26 @@ addRoute("POST", "/api/locks/clean", () => {
|
|
|
56513
57506
|
// src/db/projects.ts
|
|
56514
57507
|
init_database();
|
|
56515
57508
|
init_api_mode();
|
|
56516
|
-
|
|
57509
|
+
import { createHash as createHash2 } from "crypto";
|
|
57510
|
+
|
|
57511
|
+
// src/project-registration/types.ts
|
|
57512
|
+
var MEMENTOS_PROJECT_REGISTRATION_ROUTE = "mementos.project-registration.v1";
|
|
57513
|
+
var MEMENTOS_PROJECT_REGISTRATION_CALLER_ROUTE = "projects.full-registration.v1";
|
|
57514
|
+
var MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE = "mementos.project-guarded-update.v1";
|
|
57515
|
+
|
|
57516
|
+
class MementosProjectRegistrationError extends Error {
|
|
57517
|
+
code;
|
|
57518
|
+
details;
|
|
57519
|
+
constructor(code, message, details = {}) {
|
|
57520
|
+
super(message);
|
|
57521
|
+
this.code = code;
|
|
57522
|
+
this.details = details;
|
|
57523
|
+
this.name = "MementosProjectRegistrationError";
|
|
57524
|
+
}
|
|
57525
|
+
}
|
|
57526
|
+
|
|
57527
|
+
// src/db/projects.ts
|
|
57528
|
+
function parseProjectRow2(row) {
|
|
56517
57529
|
return {
|
|
56518
57530
|
id: row["id"],
|
|
56519
57531
|
name: row["name"],
|
|
@@ -56524,6 +57536,229 @@ function parseProjectRow(row) {
|
|
|
56524
57536
|
updated_at: row["updated_at"]
|
|
56525
57537
|
};
|
|
56526
57538
|
}
|
|
57539
|
+
class ProjectGuardedUpdateError extends Error {
|
|
57540
|
+
code;
|
|
57541
|
+
details;
|
|
57542
|
+
constructor(code, message, details = {}) {
|
|
57543
|
+
super(message);
|
|
57544
|
+
this.code = code;
|
|
57545
|
+
this.details = details;
|
|
57546
|
+
this.name = "ProjectGuardedUpdateError";
|
|
57547
|
+
}
|
|
57548
|
+
}
|
|
57549
|
+
var PROJECT_UPDATE_AUTHORITY = {
|
|
57550
|
+
authority_id: "mementos",
|
|
57551
|
+
tenant_id: "default",
|
|
57552
|
+
corpus_id: "default"
|
|
57553
|
+
};
|
|
57554
|
+
var BOUNDED_IDENTIFIER2 = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
|
|
57555
|
+
function canonicalizeProjectUpdateValue(value) {
|
|
57556
|
+
if (Array.isArray(value))
|
|
57557
|
+
return value.map(canonicalizeProjectUpdateValue);
|
|
57558
|
+
if (!value || typeof value !== "object")
|
|
57559
|
+
return value;
|
|
57560
|
+
const output = {};
|
|
57561
|
+
for (const key of Object.keys(value).sort()) {
|
|
57562
|
+
const item = value[key];
|
|
57563
|
+
if (item !== undefined)
|
|
57564
|
+
output[key] = canonicalizeProjectUpdateValue(item);
|
|
57565
|
+
}
|
|
57566
|
+
return output;
|
|
57567
|
+
}
|
|
57568
|
+
function canonicalProjectUpdateJson(value) {
|
|
57569
|
+
return JSON.stringify(canonicalizeProjectUpdateValue(value));
|
|
57570
|
+
}
|
|
57571
|
+
function digestProjectUpdateValue(value) {
|
|
57572
|
+
return createHash2("sha256").update(canonicalProjectUpdateJson(value)).digest("hex");
|
|
57573
|
+
}
|
|
57574
|
+
function timestampString2(value) {
|
|
57575
|
+
return value instanceof Date ? value.toISOString() : String(value);
|
|
57576
|
+
}
|
|
57577
|
+
function parseProjectJson(value) {
|
|
57578
|
+
const parsed = typeof value === "string" ? JSON.parse(value) : value;
|
|
57579
|
+
return parsed;
|
|
57580
|
+
}
|
|
57581
|
+
function projectUpdateReceiptFromRow(row) {
|
|
57582
|
+
return {
|
|
57583
|
+
receipt_id: String(row["receipt_id"]),
|
|
57584
|
+
authority: "mementos",
|
|
57585
|
+
route: MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE,
|
|
57586
|
+
package_version: String(row["package_version"]),
|
|
57587
|
+
authority_id: String(row["authority_id"]),
|
|
57588
|
+
tenant_id: String(row["tenant_id"]),
|
|
57589
|
+
corpus_id: String(row["corpus_id"]),
|
|
57590
|
+
operation_id: String(row["operation_id"]),
|
|
57591
|
+
step_id: String(row["step_id"]),
|
|
57592
|
+
direction: row["direction"],
|
|
57593
|
+
idempotency_key: String(row["idempotency_key"]),
|
|
57594
|
+
request_digest: String(row["request_digest"]),
|
|
57595
|
+
outcome: "accepted",
|
|
57596
|
+
target_id: String(row["target_id"]),
|
|
57597
|
+
expected_revision: timestampString2(row["expected_revision"]),
|
|
57598
|
+
result_revision: timestampString2(row["result_revision"]),
|
|
57599
|
+
result_digest: String(row["result_digest"]),
|
|
57600
|
+
accepted_receipt_id: row["accepted_receipt_id"] === null ? null : String(row["accepted_receipt_id"]),
|
|
57601
|
+
before_project: parseProjectJson(row["before_project_json"]),
|
|
57602
|
+
after_project: parseProjectJson(row["after_project_json"]),
|
|
57603
|
+
created_at: timestampString2(row["created_at"])
|
|
57604
|
+
};
|
|
57605
|
+
}
|
|
57606
|
+
function normalizeProjectUpdateInput(input) {
|
|
57607
|
+
const normalized = {};
|
|
57608
|
+
if (input.name !== undefined)
|
|
57609
|
+
normalized.name = input.name.trim();
|
|
57610
|
+
if (input.path !== undefined)
|
|
57611
|
+
normalized.path = input.path.trim();
|
|
57612
|
+
if (input.description !== undefined)
|
|
57613
|
+
normalized.description = input.description;
|
|
57614
|
+
if (input.memory_prefix !== undefined)
|
|
57615
|
+
normalized.memory_prefix = input.memory_prefix;
|
|
57616
|
+
if (Object.keys(normalized).length === 0) {
|
|
57617
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_INVALID_INPUT", "At least one project field must be provided");
|
|
57618
|
+
}
|
|
57619
|
+
if (normalized.name !== undefined && normalized.name.length === 0) {
|
|
57620
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_INVALID_INPUT", "Project name cannot be empty");
|
|
57621
|
+
}
|
|
57622
|
+
if (normalized.path !== undefined && normalized.path.length === 0) {
|
|
57623
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_INVALID_INPUT", "Project path cannot be empty");
|
|
57624
|
+
}
|
|
57625
|
+
return normalized;
|
|
57626
|
+
}
|
|
57627
|
+
function assertProjectUpdateIdentity(identity) {
|
|
57628
|
+
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) {
|
|
57629
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_AUTHORITY_MISMATCH", "guarded project update does not match this authority, tenant, and corpus");
|
|
57630
|
+
}
|
|
57631
|
+
}
|
|
57632
|
+
function assertBoundedIdentifier2(value, field) {
|
|
57633
|
+
if (!BOUNDED_IDENTIFIER2.test(value)) {
|
|
57634
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_INVALID_INPUT", `${field} must be an 8-128 character bounded identifier`);
|
|
57635
|
+
}
|
|
57636
|
+
}
|
|
57637
|
+
function assertProjectUpdateRequest(request) {
|
|
57638
|
+
assertProjectUpdateIdentity(request);
|
|
57639
|
+
assertBoundedIdentifier2(request.operation_id, "operation_id");
|
|
57640
|
+
assertBoundedIdentifier2(request.step_id, "step_id");
|
|
57641
|
+
assertBoundedIdentifier2(request.idempotency_key, "idempotency_key");
|
|
57642
|
+
if (!request.expected_revision || request.expected_revision.length > 128) {
|
|
57643
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_INVALID_INPUT", "expected_revision is required and must be bounded");
|
|
57644
|
+
}
|
|
57645
|
+
}
|
|
57646
|
+
function getProjectByExactId2(id, db) {
|
|
57647
|
+
const row = db.query("SELECT * FROM projects WHERE id = ? LIMIT 1").get(id);
|
|
57648
|
+
return row ? parseProjectRow2(row) : null;
|
|
57649
|
+
}
|
|
57650
|
+
function assertNoProjectCollision(id, input, db) {
|
|
57651
|
+
if (input.name !== undefined) {
|
|
57652
|
+
const collision = db.query("SELECT id FROM projects WHERE LOWER(name) = LOWER(?) AND id != ? LIMIT 1").get(input.name, id);
|
|
57653
|
+
if (collision) {
|
|
57654
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_COLLISION", `Project name already exists: ${input.name}`, { field: "name" });
|
|
57655
|
+
}
|
|
57656
|
+
}
|
|
57657
|
+
if (input.path !== undefined) {
|
|
57658
|
+
const collision = db.query("SELECT id FROM projects WHERE path = ? AND id != ? LIMIT 1").get(input.path, id);
|
|
57659
|
+
if (collision) {
|
|
57660
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_COLLISION", `Project path already exists: ${input.path}`, { field: "path" });
|
|
57661
|
+
}
|
|
57662
|
+
}
|
|
57663
|
+
}
|
|
57664
|
+
function nextProjectRevision(previous) {
|
|
57665
|
+
const candidate = now();
|
|
57666
|
+
const previousMs = Date.parse(previous);
|
|
57667
|
+
const candidateMs = Date.parse(candidate);
|
|
57668
|
+
if (Number.isFinite(previousMs) && Number.isFinite(candidateMs) && candidateMs <= previousMs) {
|
|
57669
|
+
return new Date(previousMs + 1).toISOString();
|
|
57670
|
+
}
|
|
57671
|
+
return candidate;
|
|
57672
|
+
}
|
|
57673
|
+
function projectWithUpdates(project, updates, revision) {
|
|
57674
|
+
return {
|
|
57675
|
+
...project,
|
|
57676
|
+
...updates.name !== undefined ? { name: updates.name } : {},
|
|
57677
|
+
...updates.path !== undefined ? { path: updates.path } : {},
|
|
57678
|
+
...updates.description !== undefined ? { description: updates.description } : {},
|
|
57679
|
+
...updates.memory_prefix !== undefined ? { memory_prefix: updates.memory_prefix } : {},
|
|
57680
|
+
updated_at: revision
|
|
57681
|
+
};
|
|
57682
|
+
}
|
|
57683
|
+
function findProjectUpdateReceiptByKey(db, identity, direction, idempotencyKey) {
|
|
57684
|
+
const row = db.query(`
|
|
57685
|
+
SELECT * FROM mementos_project_update_receipts
|
|
57686
|
+
WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
57687
|
+
AND direction = ? AND idempotency_key = ?
|
|
57688
|
+
LIMIT 1
|
|
57689
|
+
`).get(identity.authority_id, identity.tenant_id, identity.corpus_id, direction, idempotencyKey);
|
|
57690
|
+
return row ? projectUpdateReceiptFromRow(row) : null;
|
|
57691
|
+
}
|
|
57692
|
+
function findProjectUpdateReceiptById(db, identity, receiptId) {
|
|
57693
|
+
const row = db.query(`
|
|
57694
|
+
SELECT * FROM mementos_project_update_receipts
|
|
57695
|
+
WHERE receipt_id = ? AND authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
57696
|
+
LIMIT 1
|
|
57697
|
+
`).get(receiptId, identity.authority_id, identity.tenant_id, identity.corpus_id);
|
|
57698
|
+
return row ? projectUpdateReceiptFromRow(row) : null;
|
|
57699
|
+
}
|
|
57700
|
+
function insertProjectUpdateReceipt(db, receipt) {
|
|
57701
|
+
db.run(`
|
|
57702
|
+
INSERT INTO mementos_project_update_receipts (
|
|
57703
|
+
receipt_id, authority, route, package_version, authority_id, tenant_id,
|
|
57704
|
+
corpus_id, operation_id, step_id, direction, idempotency_key,
|
|
57705
|
+
request_digest, outcome, target_id, expected_revision, result_revision,
|
|
57706
|
+
result_digest, accepted_receipt_id, before_project_json,
|
|
57707
|
+
after_project_json, created_at
|
|
57708
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
57709
|
+
`, [
|
|
57710
|
+
receipt.receipt_id,
|
|
57711
|
+
receipt.authority,
|
|
57712
|
+
receipt.route,
|
|
57713
|
+
receipt.package_version,
|
|
57714
|
+
receipt.authority_id,
|
|
57715
|
+
receipt.tenant_id,
|
|
57716
|
+
receipt.corpus_id,
|
|
57717
|
+
receipt.operation_id,
|
|
57718
|
+
receipt.step_id,
|
|
57719
|
+
receipt.direction,
|
|
57720
|
+
receipt.idempotency_key,
|
|
57721
|
+
receipt.request_digest,
|
|
57722
|
+
receipt.outcome,
|
|
57723
|
+
receipt.target_id,
|
|
57724
|
+
receipt.expected_revision,
|
|
57725
|
+
receipt.result_revision,
|
|
57726
|
+
receipt.result_digest,
|
|
57727
|
+
receipt.accepted_receipt_id,
|
|
57728
|
+
canonicalProjectUpdateJson(receipt.before_project),
|
|
57729
|
+
canonicalProjectUpdateJson(receipt.after_project),
|
|
57730
|
+
receipt.created_at
|
|
57731
|
+
]);
|
|
57732
|
+
}
|
|
57733
|
+
function makeProjectUpdateReceipt(input) {
|
|
57734
|
+
const createdAt = now();
|
|
57735
|
+
const logical = {
|
|
57736
|
+
authority: "mementos",
|
|
57737
|
+
route: MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE,
|
|
57738
|
+
package_version: getMementosPackageVersion(),
|
|
57739
|
+
authority_id: input.request.authority_id,
|
|
57740
|
+
tenant_id: input.request.tenant_id,
|
|
57741
|
+
corpus_id: input.request.corpus_id,
|
|
57742
|
+
operation_id: input.request.operation_id,
|
|
57743
|
+
step_id: input.request.step_id,
|
|
57744
|
+
direction: input.direction,
|
|
57745
|
+
idempotency_key: input.request.idempotency_key,
|
|
57746
|
+
request_digest: input.request_digest,
|
|
57747
|
+
outcome: "accepted",
|
|
57748
|
+
target_id: input.target_id,
|
|
57749
|
+
expected_revision: input.request.expected_revision,
|
|
57750
|
+
result_revision: input.after_project.updated_at,
|
|
57751
|
+
result_digest: digestProjectUpdateValue(input.after_project),
|
|
57752
|
+
accepted_receipt_id: input.accepted_receipt_id ?? null,
|
|
57753
|
+
before_project: input.before_project,
|
|
57754
|
+
after_project: input.after_project,
|
|
57755
|
+
created_at: createdAt
|
|
57756
|
+
};
|
|
57757
|
+
return {
|
|
57758
|
+
receipt_id: `mpur_${digestProjectUpdateValue(logical).slice(0, 40)}`,
|
|
57759
|
+
...logical
|
|
57760
|
+
};
|
|
57761
|
+
}
|
|
56527
57762
|
function registerProject(name, path, description, memoryPrefix, db) {
|
|
56528
57763
|
if (!db && isApiMode()) {
|
|
56529
57764
|
const { data } = apiJson("POST", "/projects", {
|
|
@@ -56543,7 +57778,7 @@ function registerProject(name, path, description, memoryPrefix, db) {
|
|
|
56543
57778
|
timestamp,
|
|
56544
57779
|
existingId
|
|
56545
57780
|
]);
|
|
56546
|
-
return
|
|
57781
|
+
return parseProjectRow2(existing);
|
|
56547
57782
|
}
|
|
56548
57783
|
const id = uuid();
|
|
56549
57784
|
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]);
|
|
@@ -56559,13 +57794,13 @@ function getProject(idOrPath, db) {
|
|
|
56559
57794
|
const d = db || getDatabase();
|
|
56560
57795
|
let row = d.query("SELECT * FROM projects WHERE id = ?").get(idOrPath);
|
|
56561
57796
|
if (row)
|
|
56562
|
-
return
|
|
57797
|
+
return parseProjectRow2(row);
|
|
56563
57798
|
row = d.query("SELECT * FROM projects WHERE path = ?").get(idOrPath);
|
|
56564
57799
|
if (row)
|
|
56565
|
-
return
|
|
57800
|
+
return parseProjectRow2(row);
|
|
56566
57801
|
row = d.query("SELECT * FROM projects WHERE LOWER(name) = ?").get(idOrPath.toLowerCase());
|
|
56567
57802
|
if (row)
|
|
56568
|
-
return
|
|
57803
|
+
return parseProjectRow2(row);
|
|
56569
57804
|
return null;
|
|
56570
57805
|
}
|
|
56571
57806
|
function listProjects(db) {
|
|
@@ -56575,7 +57810,184 @@ function listProjects(db) {
|
|
|
56575
57810
|
}
|
|
56576
57811
|
const d = db || getDatabase();
|
|
56577
57812
|
const rows = d.query("SELECT * FROM projects ORDER BY updated_at DESC").all();
|
|
56578
|
-
return rows.map(
|
|
57813
|
+
return rows.map(parseProjectRow2);
|
|
57814
|
+
}
|
|
57815
|
+
function previewProjectUpdate(id, request, db) {
|
|
57816
|
+
assertProjectUpdateRequest(request);
|
|
57817
|
+
const normalized = normalizeProjectUpdateInput(request.updates);
|
|
57818
|
+
if (!db && isApiMode()) {
|
|
57819
|
+
const { data } = apiJson("POST", `/projects/${encodeURIComponent(id)}/guarded-update`, { ...request, updates: normalized, dry_run: true });
|
|
57820
|
+
return data;
|
|
57821
|
+
}
|
|
57822
|
+
const d = db || getDatabase();
|
|
57823
|
+
const project = getProjectByExactId2(id, d);
|
|
57824
|
+
if (!project) {
|
|
57825
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_NOT_FOUND", `Project not found by exact stable ID: ${id}`);
|
|
57826
|
+
}
|
|
57827
|
+
if (project.updated_at !== request.expected_revision) {
|
|
57828
|
+
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 });
|
|
57829
|
+
}
|
|
57830
|
+
assertNoProjectCollision(id, normalized, d);
|
|
57831
|
+
return {
|
|
57832
|
+
dry_run: true,
|
|
57833
|
+
applied: false,
|
|
57834
|
+
project: projectWithUpdates(project, normalized, project.updated_at),
|
|
57835
|
+
receipt: null
|
|
57836
|
+
};
|
|
57837
|
+
}
|
|
57838
|
+
function applyProjectUpdate(id, request, db) {
|
|
57839
|
+
assertProjectUpdateRequest(request);
|
|
57840
|
+
const normalized = normalizeProjectUpdateInput(request.updates);
|
|
57841
|
+
if (!db && isApiMode()) {
|
|
57842
|
+
const { data } = apiJson("POST", `/projects/${encodeURIComponent(id)}/guarded-update`, { ...request, updates: normalized, dry_run: false });
|
|
57843
|
+
return data;
|
|
57844
|
+
}
|
|
57845
|
+
const d = db || getDatabase();
|
|
57846
|
+
const requestDigest = digestProjectUpdateValue({
|
|
57847
|
+
...request,
|
|
57848
|
+
target_id: id,
|
|
57849
|
+
direction: "forward",
|
|
57850
|
+
updates: normalized
|
|
57851
|
+
});
|
|
57852
|
+
return d.transaction(() => {
|
|
57853
|
+
const prior = findProjectUpdateReceiptByKey(d, request, "forward", request.idempotency_key);
|
|
57854
|
+
if (prior) {
|
|
57855
|
+
if (prior.request_digest !== requestDigest || prior.target_id !== id) {
|
|
57856
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_IDEMPOTENCY_MISMATCH", "caller idempotency key is already bound to a different request");
|
|
57857
|
+
}
|
|
57858
|
+
const current = getProjectByExactId2(id, d);
|
|
57859
|
+
if (!current || canonicalProjectUpdateJson(current) !== canonicalProjectUpdateJson(prior.after_project)) {
|
|
57860
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_ACCEPTED_TARGET_DRIFTED", "accepted guarded update target drifted after its immutable receipt");
|
|
57861
|
+
}
|
|
57862
|
+
return { dry_run: false, applied: true, project: prior.after_project, receipt: prior };
|
|
57863
|
+
}
|
|
57864
|
+
const before = getProjectByExactId2(id, d);
|
|
57865
|
+
if (!before) {
|
|
57866
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_NOT_FOUND", `Project not found by exact stable ID: ${id}`);
|
|
57867
|
+
}
|
|
57868
|
+
if (before.updated_at !== request.expected_revision) {
|
|
57869
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_STALE_REVISION", "Project revision changed before the guarded update", { expected_revision: request.expected_revision, current_revision: before.updated_at });
|
|
57870
|
+
}
|
|
57871
|
+
assertNoProjectCollision(id, normalized, d);
|
|
57872
|
+
const revision = nextProjectRevision(before.updated_at);
|
|
57873
|
+
const after = projectWithUpdates(before, normalized, revision);
|
|
57874
|
+
const result = d.run(`
|
|
57875
|
+
UPDATE projects
|
|
57876
|
+
SET name = ?, path = ?, description = ?, memory_prefix = ?, updated_at = ?
|
|
57877
|
+
WHERE id = ? AND updated_at = ?
|
|
57878
|
+
`, [
|
|
57879
|
+
after.name,
|
|
57880
|
+
after.path,
|
|
57881
|
+
after.description,
|
|
57882
|
+
after.memory_prefix,
|
|
57883
|
+
after.updated_at,
|
|
57884
|
+
id,
|
|
57885
|
+
request.expected_revision
|
|
57886
|
+
]);
|
|
57887
|
+
if (result.changes !== 1) {
|
|
57888
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_STALE_REVISION", "Project compare-and-swap did not update exactly one row");
|
|
57889
|
+
}
|
|
57890
|
+
const readback = getProjectByExactId2(id, d);
|
|
57891
|
+
if (!readback || canonicalProjectUpdateJson(readback) !== canonicalProjectUpdateJson(after)) {
|
|
57892
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_ACCEPTED_TARGET_DRIFTED", "Project guarded update did not read back exactly under the stable ID");
|
|
57893
|
+
}
|
|
57894
|
+
const receipt = makeProjectUpdateReceipt({
|
|
57895
|
+
request: { ...request, updates: normalized },
|
|
57896
|
+
direction: "forward",
|
|
57897
|
+
request_digest: requestDigest,
|
|
57898
|
+
target_id: id,
|
|
57899
|
+
before_project: before,
|
|
57900
|
+
after_project: readback
|
|
57901
|
+
});
|
|
57902
|
+
insertProjectUpdateReceipt(d, receipt);
|
|
57903
|
+
return { dry_run: false, applied: true, project: readback, receipt };
|
|
57904
|
+
});
|
|
57905
|
+
}
|
|
57906
|
+
function rollbackProjectUpdate(id, request, db) {
|
|
57907
|
+
assertProjectUpdateRequest(request);
|
|
57908
|
+
assertBoundedIdentifier2(request.accepted_receipt_id, "accepted_receipt_id");
|
|
57909
|
+
if (!db && isApiMode()) {
|
|
57910
|
+
const { data } = apiJson("POST", `/projects/${encodeURIComponent(id)}/guarded-rollback`, request);
|
|
57911
|
+
return data;
|
|
57912
|
+
}
|
|
57913
|
+
const d = db || getDatabase();
|
|
57914
|
+
const requestDigest = digestProjectUpdateValue({
|
|
57915
|
+
...request,
|
|
57916
|
+
target_id: id,
|
|
57917
|
+
direction: "rollback"
|
|
57918
|
+
});
|
|
57919
|
+
return d.transaction(() => {
|
|
57920
|
+
const prior = findProjectUpdateReceiptByKey(d, request, "rollback", request.idempotency_key);
|
|
57921
|
+
if (prior) {
|
|
57922
|
+
if (prior.request_digest !== requestDigest || prior.target_id !== id) {
|
|
57923
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_IDEMPOTENCY_MISMATCH", "caller idempotency key is already bound to a different rollback request");
|
|
57924
|
+
}
|
|
57925
|
+
const current2 = getProjectByExactId2(id, d);
|
|
57926
|
+
if (!current2 || canonicalProjectUpdateJson(current2) !== canonicalProjectUpdateJson(prior.after_project)) {
|
|
57927
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_ACCEPTED_TARGET_DRIFTED", "accepted rollback target drifted after its immutable receipt");
|
|
57928
|
+
}
|
|
57929
|
+
return { dry_run: false, applied: true, project: prior.after_project, receipt: prior };
|
|
57930
|
+
}
|
|
57931
|
+
const accepted = findProjectUpdateReceiptById(d, request, request.accepted_receipt_id);
|
|
57932
|
+
if (!accepted || accepted.direction !== "forward" || accepted.target_id !== id) {
|
|
57933
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_RECEIPT_NOT_FOUND", "accepted forward update receipt was not found for this exact project");
|
|
57934
|
+
}
|
|
57935
|
+
const current = getProjectByExactId2(id, d);
|
|
57936
|
+
if (!current) {
|
|
57937
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_NOT_FOUND", `Project not found by exact stable ID: ${id}`);
|
|
57938
|
+
}
|
|
57939
|
+
if (current.updated_at !== request.expected_revision || canonicalProjectUpdateJson(current) !== canonicalProjectUpdateJson(accepted.after_project)) {
|
|
57940
|
+
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 });
|
|
57941
|
+
}
|
|
57942
|
+
assertNoProjectCollision(id, accepted.before_project, d);
|
|
57943
|
+
const restored = accepted.before_project;
|
|
57944
|
+
const result = d.run(`
|
|
57945
|
+
UPDATE projects
|
|
57946
|
+
SET name = ?, path = ?, description = ?, memory_prefix = ?,
|
|
57947
|
+
created_at = ?, updated_at = ?
|
|
57948
|
+
WHERE id = ? AND updated_at = ?
|
|
57949
|
+
`, [
|
|
57950
|
+
restored.name,
|
|
57951
|
+
restored.path,
|
|
57952
|
+
restored.description,
|
|
57953
|
+
restored.memory_prefix,
|
|
57954
|
+
restored.created_at,
|
|
57955
|
+
restored.updated_at,
|
|
57956
|
+
id,
|
|
57957
|
+
request.expected_revision
|
|
57958
|
+
]);
|
|
57959
|
+
if (result.changes !== 1) {
|
|
57960
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_STALE_REVISION", "Project rollback compare-and-swap did not update exactly one row");
|
|
57961
|
+
}
|
|
57962
|
+
const readback = getProjectByExactId2(id, d);
|
|
57963
|
+
if (!readback || canonicalProjectUpdateJson(readback) !== canonicalProjectUpdateJson(restored)) {
|
|
57964
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_ACCEPTED_TARGET_DRIFTED", "Project rollback did not restore the exact prior row");
|
|
57965
|
+
}
|
|
57966
|
+
const receipt = makeProjectUpdateReceipt({
|
|
57967
|
+
request,
|
|
57968
|
+
direction: "rollback",
|
|
57969
|
+
request_digest: requestDigest,
|
|
57970
|
+
target_id: id,
|
|
57971
|
+
before_project: current,
|
|
57972
|
+
after_project: readback,
|
|
57973
|
+
accepted_receipt_id: accepted.receipt_id
|
|
57974
|
+
});
|
|
57975
|
+
insertProjectUpdateReceipt(d, receipt);
|
|
57976
|
+
return { dry_run: false, applied: true, project: readback, receipt };
|
|
57977
|
+
});
|
|
57978
|
+
}
|
|
57979
|
+
function getProjectUpdateReceipt(id, receiptId, identity = PROJECT_UPDATE_AUTHORITY, db) {
|
|
57980
|
+
assertProjectUpdateIdentity(identity);
|
|
57981
|
+
if (!db && isApiMode()) {
|
|
57982
|
+
const { data } = apiJson("POST", `/projects/${encodeURIComponent(id)}/update-receipts/lookup`, { ...identity, receipt_id: receiptId });
|
|
57983
|
+
return data;
|
|
57984
|
+
}
|
|
57985
|
+
const d = db || getDatabase();
|
|
57986
|
+
const receipt = findProjectUpdateReceiptById(d, identity, receiptId);
|
|
57987
|
+
if (!receipt || receipt.target_id !== id) {
|
|
57988
|
+
throw new ProjectGuardedUpdateError("PROJECT_UPDATE_RECEIPT_NOT_FOUND", "immutable project update receipt was not found for this exact project");
|
|
57989
|
+
}
|
|
57990
|
+
return receipt;
|
|
56579
57991
|
}
|
|
56580
57992
|
|
|
56581
57993
|
// src/server/routes/projects.ts
|
|
@@ -56604,6 +58016,54 @@ addRoute("GET", "/api/projects/:id", (_req, _url, params) => {
|
|
|
56604
58016
|
return errorResponse("Project not found", 404);
|
|
56605
58017
|
return json(project);
|
|
56606
58018
|
});
|
|
58019
|
+
function guardedUpdateError(error) {
|
|
58020
|
+
const status = error.code === "PROJECT_UPDATE_AUTHORITY_MISMATCH" ? 403 : error.code === "PROJECT_UPDATE_NOT_FOUND" || error.code === "PROJECT_UPDATE_RECEIPT_NOT_FOUND" ? 404 : error.code === "PROJECT_UPDATE_INVALID_INPUT" ? 400 : 409;
|
|
58021
|
+
return errorResponse(error.message, status, { code: error.code, ...error.details });
|
|
58022
|
+
}
|
|
58023
|
+
addRoute("PATCH", "/api/projects/:id", () => errorResponse("Unguarded project updates are disabled; use POST /projects/:id/guarded-update", 428));
|
|
58024
|
+
addRoute("POST", "/api/projects/:id/guarded-update", async (req, _url, params) => {
|
|
58025
|
+
const body = await readJson(req);
|
|
58026
|
+
if (!body)
|
|
58027
|
+
return errorResponse("Invalid JSON body", 400);
|
|
58028
|
+
try {
|
|
58029
|
+
const request = body;
|
|
58030
|
+
return json(request.dry_run ? previewProjectUpdate(params["id"], request) : applyProjectUpdate(params["id"], request));
|
|
58031
|
+
} catch (error) {
|
|
58032
|
+
if (error instanceof ProjectGuardedUpdateError)
|
|
58033
|
+
return guardedUpdateError(error);
|
|
58034
|
+
throw error;
|
|
58035
|
+
}
|
|
58036
|
+
});
|
|
58037
|
+
addRoute("POST", "/api/projects/:id/guarded-rollback", async (req, _url, params) => {
|
|
58038
|
+
const body = await readJson(req);
|
|
58039
|
+
if (!body)
|
|
58040
|
+
return errorResponse("Invalid JSON body", 400);
|
|
58041
|
+
try {
|
|
58042
|
+
return json(rollbackProjectUpdate(params["id"], body));
|
|
58043
|
+
} catch (error) {
|
|
58044
|
+
if (error instanceof ProjectGuardedUpdateError)
|
|
58045
|
+
return guardedUpdateError(error);
|
|
58046
|
+
throw error;
|
|
58047
|
+
}
|
|
58048
|
+
});
|
|
58049
|
+
addRoute("POST", "/api/projects/:id/update-receipts/lookup", async (req, _url, params) => {
|
|
58050
|
+
const body = await readJson(req);
|
|
58051
|
+
if (!body || typeof body["receipt_id"] !== "string") {
|
|
58052
|
+
return errorResponse("receipt_id is required", 400);
|
|
58053
|
+
}
|
|
58054
|
+
try {
|
|
58055
|
+
const identity = {
|
|
58056
|
+
authority_id: String(body["authority_id"] ?? ""),
|
|
58057
|
+
tenant_id: String(body["tenant_id"] ?? ""),
|
|
58058
|
+
corpus_id: String(body["corpus_id"] ?? "")
|
|
58059
|
+
};
|
|
58060
|
+
return json(getProjectUpdateReceipt(params["id"], body["receipt_id"], identity));
|
|
58061
|
+
} catch (error) {
|
|
58062
|
+
if (error instanceof ProjectGuardedUpdateError)
|
|
58063
|
+
return guardedUpdateError(error);
|
|
58064
|
+
throw error;
|
|
58065
|
+
}
|
|
58066
|
+
});
|
|
56607
58067
|
addRoute("GET", "/api/projects/:id/agents", (_req, _url, params) => {
|
|
56608
58068
|
const project = getProject(params["id"]);
|
|
56609
58069
|
if (!project)
|
|
@@ -56612,6 +58072,1064 @@ addRoute("GET", "/api/projects/:id/agents", (_req, _url, params) => {
|
|
|
56612
58072
|
return json({ agents, count: agents.length });
|
|
56613
58073
|
});
|
|
56614
58074
|
|
|
58075
|
+
// src/server/routes/project-registration.ts
|
|
58076
|
+
init_database();
|
|
58077
|
+
|
|
58078
|
+
// src/project-registration/authority.ts
|
|
58079
|
+
import { createHash as createHash3 } from "crypto";
|
|
58080
|
+
import { resolve as resolve3 } from "path";
|
|
58081
|
+
|
|
58082
|
+
// src/project-registration/project-references.ts
|
|
58083
|
+
var MEMENTOS_PROJECT_REFERENCE_SURFACES = [
|
|
58084
|
+
{ key: "memories", table: "memories", column: "project_id" },
|
|
58085
|
+
{ key: "sessions", table: "sessions", column: "project_id" },
|
|
58086
|
+
{ key: "entities", table: "entities", column: "project_id" },
|
|
58087
|
+
{ key: "agents", table: "agents", column: "active_project_id" },
|
|
58088
|
+
{ key: "tool_events", table: "tool_events", column: "project_id" },
|
|
58089
|
+
{ key: "tasks", table: "tasks", column: "project_id" },
|
|
58090
|
+
{
|
|
58091
|
+
key: "memory_consolidation_runs",
|
|
58092
|
+
table: "memory_consolidation_runs",
|
|
58093
|
+
column: "project_id"
|
|
58094
|
+
},
|
|
58095
|
+
{
|
|
58096
|
+
key: "memory_reflection_runs",
|
|
58097
|
+
table: "memory_reflection_runs",
|
|
58098
|
+
column: "project_id"
|
|
58099
|
+
},
|
|
58100
|
+
{ key: "memory_acl", table: "memory_acl", column: "project_id" },
|
|
58101
|
+
{ key: "search_history", table: "search_history", column: "project_id" },
|
|
58102
|
+
{
|
|
58103
|
+
key: "session_memory_jobs",
|
|
58104
|
+
table: "session_memory_jobs",
|
|
58105
|
+
column: "project_id"
|
|
58106
|
+
},
|
|
58107
|
+
{ key: "synthesis_events", table: "synthesis_events", column: "project_id" },
|
|
58108
|
+
{ key: "synthesis_runs", table: "synthesis_runs", column: "project_id" },
|
|
58109
|
+
{ key: "webhook_hooks", table: "webhook_hooks", column: "project_id" }
|
|
58110
|
+
];
|
|
58111
|
+
function quotedIdentifier(value) {
|
|
58112
|
+
if (!/^[a-z_][a-z0-9_]*$/.test(value)) {
|
|
58113
|
+
throw new Error(`unsafe project-reference identifier: ${value}`);
|
|
58114
|
+
}
|
|
58115
|
+
return `"${value}"`;
|
|
58116
|
+
}
|
|
58117
|
+
function mementosProjectReferenceCounts(db, projectId) {
|
|
58118
|
+
const projections = MEMENTOS_PROJECT_REFERENCE_SURFACES.map(({ key, table, column }) => `(SELECT COUNT(*) FROM ${quotedIdentifier(table)} ` + `WHERE ${quotedIdentifier(column)} = ?) AS ${quotedIdentifier(key)}`).join(`,
|
|
58119
|
+
`);
|
|
58120
|
+
const row = db.get(`SELECT
|
|
58121
|
+
${projections}`, ...MEMENTOS_PROJECT_REFERENCE_SURFACES.map(() => projectId));
|
|
58122
|
+
return Object.fromEntries(MEMENTOS_PROJECT_REFERENCE_SURFACES.map(({ key }) => [key, Number(row[key] ?? 0)]));
|
|
58123
|
+
}
|
|
58124
|
+
function hasMementosProjectReferences(counts) {
|
|
58125
|
+
return MEMENTOS_PROJECT_REFERENCE_SURFACES.some(({ key }) => counts[key] > 0);
|
|
58126
|
+
}
|
|
58127
|
+
function deleteMementosProjectIfUnreferenced(db, projectId) {
|
|
58128
|
+
const guards = MEMENTOS_PROJECT_REFERENCE_SURFACES.map(({ table, column }) => `NOT EXISTS (SELECT 1 FROM ${quotedIdentifier(table)} ` + `WHERE ${quotedIdentifier(column)} = ?)`).join(`
|
|
58129
|
+
AND `);
|
|
58130
|
+
const result = db.run(`DELETE FROM projects
|
|
58131
|
+
WHERE id = ?
|
|
58132
|
+
AND ${guards}`, projectId, ...MEMENTOS_PROJECT_REFERENCE_SURFACES.map(() => projectId));
|
|
58133
|
+
return result.changes;
|
|
58134
|
+
}
|
|
58135
|
+
|
|
58136
|
+
// src/project-registration/authority.ts
|
|
58137
|
+
var WORKSPACE_ID_PATTERN = /^wks_[A-Za-z0-9][A-Za-z0-9_-]{11,}$/;
|
|
58138
|
+
var OPERATION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
|
|
58139
|
+
var STEP_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$/;
|
|
58140
|
+
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
58141
|
+
var IDEMPOTENCY_PATTERN = /^prk_[0-9a-f]{48}$/;
|
|
58142
|
+
var PROJECT_ID_PATTERN = /^mm_project_[0-9a-f]{40}$/;
|
|
58143
|
+
|
|
58144
|
+
class WriteBoundaryError extends Error {
|
|
58145
|
+
point;
|
|
58146
|
+
cause;
|
|
58147
|
+
constructor(point, cause) {
|
|
58148
|
+
super(`Mementos project registration failed at ${point}`);
|
|
58149
|
+
this.point = point;
|
|
58150
|
+
this.cause = cause;
|
|
58151
|
+
}
|
|
58152
|
+
}
|
|
58153
|
+
function canonicalMementosProjectRegistrationJson(value) {
|
|
58154
|
+
return JSON.stringify(canonicalize2(value));
|
|
58155
|
+
}
|
|
58156
|
+
function canonicalize2(value) {
|
|
58157
|
+
if (Array.isArray(value))
|
|
58158
|
+
return value.map(canonicalize2);
|
|
58159
|
+
if (!value || typeof value !== "object")
|
|
58160
|
+
return value;
|
|
58161
|
+
const out = {};
|
|
58162
|
+
for (const key of Object.keys(value).sort()) {
|
|
58163
|
+
const item = value[key];
|
|
58164
|
+
if (item !== undefined)
|
|
58165
|
+
out[key] = canonicalize2(item);
|
|
58166
|
+
}
|
|
58167
|
+
return out;
|
|
58168
|
+
}
|
|
58169
|
+
function digestMementosProjectRegistrationValue(value) {
|
|
58170
|
+
return createHash3("sha256").update(canonicalMementosProjectRegistrationJson(value)).digest("hex");
|
|
58171
|
+
}
|
|
58172
|
+
function digestOwnedPath(path) {
|
|
58173
|
+
return createHash3("sha256").update(path).digest("hex");
|
|
58174
|
+
}
|
|
58175
|
+
function deriveMementosProjectRegistrationIdempotencyKey(input) {
|
|
58176
|
+
return `prk_${digestMementosProjectRegistrationValue({
|
|
58177
|
+
route: MEMENTOS_PROJECT_REGISTRATION_CALLER_ROUTE,
|
|
58178
|
+
operation_id: input.operation_id,
|
|
58179
|
+
step_id: input.step_id,
|
|
58180
|
+
direction: input.direction,
|
|
58181
|
+
target_selector: input.target_selector,
|
|
58182
|
+
request_digest: input.request_digest,
|
|
58183
|
+
precondition_digest: input.precondition_digest
|
|
58184
|
+
}).slice(0, 48)}`;
|
|
58185
|
+
}
|
|
58186
|
+
function responseBytes(value) {
|
|
58187
|
+
return Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
58188
|
+
}
|
|
58189
|
+
function assertBounds(bounds) {
|
|
58190
|
+
if (!Number.isSafeInteger(bounds.response_byte_limit) || bounds.response_byte_limit <= 0) {
|
|
58191
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "response_byte_limit must be a positive integer");
|
|
58192
|
+
}
|
|
58193
|
+
if (!Number.isSafeInteger(bounds.time_budget_ms) || bounds.time_budget_ms <= 0) {
|
|
58194
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "time_budget_ms must be a positive integer");
|
|
58195
|
+
}
|
|
58196
|
+
}
|
|
58197
|
+
function assertWithinBounds(value, bounds, startedAt) {
|
|
58198
|
+
const bytes = responseBytes(value);
|
|
58199
|
+
if (bytes > bounds.response_byte_limit) {
|
|
58200
|
+
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 });
|
|
58201
|
+
}
|
|
58202
|
+
const elapsed = Date.now() - startedAt;
|
|
58203
|
+
if (elapsed > bounds.time_budget_ms) {
|
|
58204
|
+
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 });
|
|
58205
|
+
}
|
|
58206
|
+
return { response_bytes: bytes, elapsed_ms: elapsed };
|
|
58207
|
+
}
|
|
58208
|
+
function withResponseControl(receipt, bounds, startedAt) {
|
|
58209
|
+
const result = {
|
|
58210
|
+
receipt,
|
|
58211
|
+
response_control: {
|
|
58212
|
+
response_byte_limit: bounds.response_byte_limit,
|
|
58213
|
+
time_budget_ms: bounds.time_budget_ms,
|
|
58214
|
+
response_bytes: 0,
|
|
58215
|
+
elapsed_ms: 0,
|
|
58216
|
+
complete: true,
|
|
58217
|
+
truncated: false
|
|
58218
|
+
}
|
|
58219
|
+
};
|
|
58220
|
+
for (let attempt = 0;attempt < 8; attempt += 1) {
|
|
58221
|
+
const measured2 = assertWithinBounds(result, bounds, startedAt);
|
|
58222
|
+
const stable = result.response_control.response_bytes === measured2.response_bytes && result.response_control.elapsed_ms === measured2.elapsed_ms;
|
|
58223
|
+
result.response_control.response_bytes = measured2.response_bytes;
|
|
58224
|
+
result.response_control.elapsed_ms = measured2.elapsed_ms;
|
|
58225
|
+
if (stable)
|
|
58226
|
+
break;
|
|
58227
|
+
}
|
|
58228
|
+
const measured = assertWithinBounds(result, bounds, startedAt);
|
|
58229
|
+
result.response_control.response_bytes = measured.response_bytes;
|
|
58230
|
+
result.response_control.elapsed_ms = measured.elapsed_ms;
|
|
58231
|
+
return result;
|
|
58232
|
+
}
|
|
58233
|
+
function requireString(value, field, options = {}) {
|
|
58234
|
+
const min = options.min ?? 1;
|
|
58235
|
+
const max = options.max ?? 512;
|
|
58236
|
+
if (typeof value !== "string" || value.length < min || value.length > max || /[\u0000-\u001f]/.test(value) || options.pattern && !options.pattern.test(value)) {
|
|
58237
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field} is not a valid bounded registration identifier`);
|
|
58238
|
+
}
|
|
58239
|
+
return value;
|
|
58240
|
+
}
|
|
58241
|
+
function exactKeys(value, expected, field) {
|
|
58242
|
+
const actual = Object.keys(value).sort();
|
|
58243
|
+
const wanted = [...expected].sort();
|
|
58244
|
+
if (actual.length !== wanted.length || actual.some((key, i) => key !== wanted[i])) {
|
|
58245
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field} must contain exactly: ${wanted.join(", ")}`);
|
|
58246
|
+
}
|
|
58247
|
+
}
|
|
58248
|
+
function ownedPath(target) {
|
|
58249
|
+
if (!target || typeof target.withOwnedPath !== "function") {
|
|
58250
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "target must be a package-owned path handle");
|
|
58251
|
+
}
|
|
58252
|
+
const path = target.withOwnedPath((value) => value);
|
|
58253
|
+
requireString(path, "target path", { max: 4096 });
|
|
58254
|
+
if (path !== resolve3(path)) {
|
|
58255
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "target path must already be canonical and absolute");
|
|
58256
|
+
}
|
|
58257
|
+
return path;
|
|
58258
|
+
}
|
|
58259
|
+
function normalizedCallDigest(request) {
|
|
58260
|
+
return digestMementosProjectRegistrationValue({
|
|
58261
|
+
authority_route: request.authority_route,
|
|
58262
|
+
package_version: request.package_version,
|
|
58263
|
+
authority_id: request.authority_id,
|
|
58264
|
+
tenant_id: request.tenant_id,
|
|
58265
|
+
corpus_id: request.corpus_id,
|
|
58266
|
+
operation_id: request.operation_id,
|
|
58267
|
+
step_id: request.step_id,
|
|
58268
|
+
resource_kind: request.resource_kind,
|
|
58269
|
+
direction: request.direction,
|
|
58270
|
+
target_selector: request.target_selector,
|
|
58271
|
+
idempotency_key: request.idempotency_key,
|
|
58272
|
+
request_digest: request.request_digest,
|
|
58273
|
+
precondition_digest: request.precondition_digest,
|
|
58274
|
+
project_id: request.project_id,
|
|
58275
|
+
project_slug: request.project_slug,
|
|
58276
|
+
project_name: request.project_name,
|
|
58277
|
+
desired: request.desired,
|
|
58278
|
+
accepted_receipt_id: request.accepted_receipt?.receipt_id ?? null
|
|
58279
|
+
});
|
|
58280
|
+
}
|
|
58281
|
+
function publicReceipt(row) {
|
|
58282
|
+
const { target_selector: _selector, normalized_call_digest: _digest, ...receipt } = row;
|
|
58283
|
+
return receipt;
|
|
58284
|
+
}
|
|
58285
|
+
function timestampString3(value) {
|
|
58286
|
+
return value instanceof Date ? value.toISOString() : String(value);
|
|
58287
|
+
}
|
|
58288
|
+
function receiptFromRow2(row) {
|
|
58289
|
+
return {
|
|
58290
|
+
...row,
|
|
58291
|
+
authority: "mementos",
|
|
58292
|
+
resource_kind: "project",
|
|
58293
|
+
created_by_operation: row["created_by_operation"] === true || Number(row["created_by_operation"]) === 1,
|
|
58294
|
+
created_at: timestampString3(row["created_at"])
|
|
58295
|
+
};
|
|
58296
|
+
}
|
|
58297
|
+
function bindingFromRow(row) {
|
|
58298
|
+
return {
|
|
58299
|
+
...row,
|
|
58300
|
+
created_at: timestampString3(row["created_at"]),
|
|
58301
|
+
updated_at: timestampString3(row["updated_at"])
|
|
58302
|
+
};
|
|
58303
|
+
}
|
|
58304
|
+
function getStoredReceipt(db, receiptId) {
|
|
58305
|
+
const row = db.get("SELECT * FROM mementos_project_registration_receipts WHERE receipt_id = ? LIMIT 1", receiptId);
|
|
58306
|
+
return row ? receiptFromRow2(row) : null;
|
|
58307
|
+
}
|
|
58308
|
+
function getAcceptedReceipt(db, request) {
|
|
58309
|
+
const row = db.get(`
|
|
58310
|
+
SELECT * FROM mementos_project_registration_receipts
|
|
58311
|
+
WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
58312
|
+
AND operation_id = ? AND step_id = ? AND resource_kind = 'project'
|
|
58313
|
+
AND direction = ? AND outcome = 'accepted'
|
|
58314
|
+
ORDER BY created_at ASC, receipt_id ASC
|
|
58315
|
+
LIMIT 1
|
|
58316
|
+
`, request.authority_id, request.tenant_id, request.corpus_id, request.operation_id, request.step_id, request.direction);
|
|
58317
|
+
return row ? receiptFromRow2(row) : null;
|
|
58318
|
+
}
|
|
58319
|
+
function getReceiptForLookup(db, request) {
|
|
58320
|
+
const row = db.get(`
|
|
58321
|
+
SELECT * FROM mementos_project_registration_receipts
|
|
58322
|
+
WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
58323
|
+
AND operation_id = ? AND step_id = ? AND resource_kind = 'project'
|
|
58324
|
+
AND direction = ? AND idempotency_key = ? AND target_selector = ?
|
|
58325
|
+
ORDER BY CASE outcome
|
|
58326
|
+
WHEN 'terminal_nonacceptance' THEN 0
|
|
58327
|
+
WHEN 'duplicate_of_accepted' THEN 1
|
|
58328
|
+
ELSE 2
|
|
58329
|
+
END, created_at DESC, receipt_id DESC
|
|
58330
|
+
LIMIT 1
|
|
58331
|
+
`, request.authority_id, request.tenant_id, request.corpus_id, request.operation_id, request.step_id, request.direction, request.idempotency_key, request.target_selector);
|
|
58332
|
+
return row ? receiptFromRow2(row) : null;
|
|
58333
|
+
}
|
|
58334
|
+
function getBinding(db, capability, targetSelector) {
|
|
58335
|
+
const row = db.get(`
|
|
58336
|
+
SELECT * FROM mementos_project_registration_bindings
|
|
58337
|
+
WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
58338
|
+
AND resource_kind = 'project' AND target_selector = ?
|
|
58339
|
+
LIMIT 1
|
|
58340
|
+
`, capability.authority_id, capability.tenant_id, capability.corpus_id, targetSelector);
|
|
58341
|
+
return row ? bindingFromRow(row) : null;
|
|
58342
|
+
}
|
|
58343
|
+
function getProjectByExactId3(db, id) {
|
|
58344
|
+
const row = db.get("SELECT * FROM projects WHERE id = ? LIMIT 1", id);
|
|
58345
|
+
if (!row)
|
|
58346
|
+
return null;
|
|
58347
|
+
return {
|
|
58348
|
+
id: String(row["id"]),
|
|
58349
|
+
name: String(row["name"]),
|
|
58350
|
+
path: String(row["path"]),
|
|
58351
|
+
description: row["description"] === null ? null : String(row["description"]),
|
|
58352
|
+
memory_prefix: row["memory_prefix"] === null ? null : String(row["memory_prefix"]),
|
|
58353
|
+
created_at: timestampString3(row["created_at"]),
|
|
58354
|
+
updated_at: timestampString3(row["updated_at"])
|
|
58355
|
+
};
|
|
58356
|
+
}
|
|
58357
|
+
function getProjectByPath(db, path) {
|
|
58358
|
+
const row = db.get("SELECT * FROM projects WHERE path = ? LIMIT 1", path);
|
|
58359
|
+
return row ? getProjectByExactId3(db, String(row["id"])) : null;
|
|
58360
|
+
}
|
|
58361
|
+
function projectRecord(db, project, references = mementosProjectReferenceCounts(db, project.id)) {
|
|
58362
|
+
return {
|
|
58363
|
+
target_id: project.id,
|
|
58364
|
+
revision: project.updated_at,
|
|
58365
|
+
digest: digestMementosProjectRegistrationValue({
|
|
58366
|
+
id: project.id,
|
|
58367
|
+
name: project.name,
|
|
58368
|
+
path: project.path,
|
|
58369
|
+
description: project.description,
|
|
58370
|
+
memory_prefix: project.memory_prefix,
|
|
58371
|
+
created_at: project.created_at,
|
|
58372
|
+
updated_at: project.updated_at,
|
|
58373
|
+
references
|
|
58374
|
+
})
|
|
58375
|
+
};
|
|
58376
|
+
}
|
|
58377
|
+
function targetId(capability, selector) {
|
|
58378
|
+
return `mm_project_${digestMementosProjectRegistrationValue({
|
|
58379
|
+
authority_id: capability.authority_id,
|
|
58380
|
+
tenant_id: capability.tenant_id,
|
|
58381
|
+
corpus_id: capability.corpus_id,
|
|
58382
|
+
selector
|
|
58383
|
+
}).slice(0, 40)}`;
|
|
58384
|
+
}
|
|
58385
|
+
function receiptId(input) {
|
|
58386
|
+
return `mmpr_${digestMementosProjectRegistrationValue(input).slice(0, 40)}`;
|
|
58387
|
+
}
|
|
58388
|
+
function makeReceipt2(request, capability, callDigest, now2, values) {
|
|
58389
|
+
const withoutId = {
|
|
58390
|
+
authority: "mementos",
|
|
58391
|
+
route: MEMENTOS_PROJECT_REGISTRATION_ROUTE,
|
|
58392
|
+
package_version: capability.package_version,
|
|
58393
|
+
authority_id: capability.authority_id,
|
|
58394
|
+
tenant_id: capability.tenant_id,
|
|
58395
|
+
corpus_id: capability.corpus_id,
|
|
58396
|
+
operation_id: request.operation_id,
|
|
58397
|
+
step_id: request.step_id,
|
|
58398
|
+
resource_kind: "project",
|
|
58399
|
+
direction: request.direction,
|
|
58400
|
+
target_selector: values.target_selector ?? request.target_selector,
|
|
58401
|
+
idempotency_key: request.idempotency_key,
|
|
58402
|
+
request_digest: request.request_digest,
|
|
58403
|
+
precondition_digest: request.precondition_digest,
|
|
58404
|
+
normalized_call_digest: callDigest,
|
|
58405
|
+
outcome: values.outcome,
|
|
58406
|
+
reason: values.reason ?? null,
|
|
58407
|
+
target_id: values.target_id ?? null,
|
|
58408
|
+
result_revision: values.result_revision ?? null,
|
|
58409
|
+
result_digest: values.result_digest ?? null,
|
|
58410
|
+
duplicate_of_receipt_id: values.duplicate_of_receipt_id ?? null,
|
|
58411
|
+
accepted_receipt_id: values.accepted_receipt_id ?? null,
|
|
58412
|
+
created_by_operation: values.created_by_operation ?? false
|
|
58413
|
+
};
|
|
58414
|
+
return {
|
|
58415
|
+
receipt_id: receiptId(withoutId),
|
|
58416
|
+
...withoutId,
|
|
58417
|
+
created_at: now2
|
|
58418
|
+
};
|
|
58419
|
+
}
|
|
58420
|
+
function insertReceipt2(db, receipt) {
|
|
58421
|
+
db.run(`
|
|
58422
|
+
INSERT INTO mementos_project_registration_receipts (
|
|
58423
|
+
receipt_id, authority, route, package_version, authority_id, tenant_id,
|
|
58424
|
+
corpus_id, operation_id, step_id, resource_kind, direction,
|
|
58425
|
+
target_selector, idempotency_key, request_digest, precondition_digest,
|
|
58426
|
+
normalized_call_digest, outcome, reason, target_id, result_revision,
|
|
58427
|
+
result_digest, duplicate_of_receipt_id, accepted_receipt_id,
|
|
58428
|
+
created_by_operation, created_at
|
|
58429
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
58430
|
+
ON CONFLICT DO NOTHING
|
|
58431
|
+
`, 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);
|
|
58432
|
+
const stored = getStoredReceipt(db, receipt.receipt_id);
|
|
58433
|
+
const withoutCreatedAt = (value) => {
|
|
58434
|
+
const { created_at: _createdAt, ...logical } = value;
|
|
58435
|
+
return logical;
|
|
58436
|
+
};
|
|
58437
|
+
if (!stored || canonicalMementosProjectRegistrationJson(withoutCreatedAt(stored)) !== canonicalMementosProjectRegistrationJson(withoutCreatedAt(receipt))) {
|
|
58438
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "immutable receipt id resolved to different content");
|
|
58439
|
+
}
|
|
58440
|
+
return stored;
|
|
58441
|
+
}
|
|
58442
|
+
function insertBinding(db, request, callDigest, now2) {
|
|
58443
|
+
const result = db.run(`
|
|
58444
|
+
INSERT INTO mementos_project_registration_bindings (
|
|
58445
|
+
authority_id, tenant_id, corpus_id, resource_kind, target_selector,
|
|
58446
|
+
operation_id, step_id, direction, idempotency_key, request_digest,
|
|
58447
|
+
precondition_digest, normalized_call_digest, state, target_id,
|
|
58448
|
+
accepted_receipt_id, result_revision, result_digest, removed_receipt_id,
|
|
58449
|
+
created_at, updated_at
|
|
58450
|
+
) VALUES (?, ?, ?, 'project', ?, ?, ?, 'forward', ?, ?, ?, ?, 'pending',
|
|
58451
|
+
NULL, NULL, NULL, NULL, NULL, ?, ?)
|
|
58452
|
+
ON CONFLICT DO NOTHING
|
|
58453
|
+
`, 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);
|
|
58454
|
+
return result.changes === 1;
|
|
58455
|
+
}
|
|
58456
|
+
function lockBinding(db, capability, targetSelector) {
|
|
58457
|
+
const result = db.run(`
|
|
58458
|
+
UPDATE mementos_project_registration_bindings
|
|
58459
|
+
SET updated_at = updated_at
|
|
58460
|
+
WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
58461
|
+
AND resource_kind = 'project' AND target_selector = ?
|
|
58462
|
+
`, capability.authority_id, capability.tenant_id, capability.corpus_id, targetSelector);
|
|
58463
|
+
return result.changes === 1;
|
|
58464
|
+
}
|
|
58465
|
+
function setBindingAccepted(db, request, receipt, now2) {
|
|
58466
|
+
const result = db.run(`
|
|
58467
|
+
UPDATE mementos_project_registration_bindings
|
|
58468
|
+
SET state = 'accepted', target_id = ?, accepted_receipt_id = ?,
|
|
58469
|
+
result_revision = ?, result_digest = ?, updated_at = ?
|
|
58470
|
+
WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
58471
|
+
AND resource_kind = 'project' AND target_selector = ? AND state = 'pending'
|
|
58472
|
+
`, 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);
|
|
58473
|
+
if (result.changes !== 1) {
|
|
58474
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "registration binding was not pending at acceptance");
|
|
58475
|
+
}
|
|
58476
|
+
}
|
|
58477
|
+
function setBindingRemoved(db, accepted, inverseReceiptId, now2) {
|
|
58478
|
+
const result = db.run(`
|
|
58479
|
+
UPDATE mementos_project_registration_bindings
|
|
58480
|
+
SET state = 'removed', removed_receipt_id = ?, updated_at = ?
|
|
58481
|
+
WHERE authority_id = ? AND tenant_id = ? AND corpus_id = ?
|
|
58482
|
+
AND resource_kind = 'project' AND target_selector = ?
|
|
58483
|
+
AND state = 'accepted' AND accepted_receipt_id = ?
|
|
58484
|
+
`, inverseReceiptId, now2, accepted.authority_id, accepted.tenant_id, accepted.corpus_id, accepted.target_selector, accepted.receipt_id);
|
|
58485
|
+
if (result.changes !== 1) {
|
|
58486
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "registration binding was not accepted at inverse");
|
|
58487
|
+
}
|
|
58488
|
+
}
|
|
58489
|
+
function capabilityMatches(request, capability) {
|
|
58490
|
+
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;
|
|
58491
|
+
}
|
|
58492
|
+
function assertCommonRequest(request, capability) {
|
|
58493
|
+
assertBounds(request);
|
|
58494
|
+
if (request.resource_kind !== "project") {
|
|
58495
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "resource_kind must be project");
|
|
58496
|
+
}
|
|
58497
|
+
if (request.direction !== "forward" && request.direction !== "inverse") {
|
|
58498
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "direction must be forward or inverse");
|
|
58499
|
+
}
|
|
58500
|
+
if (!capabilityMatches(request, capability)) {
|
|
58501
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH", "registration request does not match this authority capability identity");
|
|
58502
|
+
}
|
|
58503
|
+
requireString(request.operation_id, "operation_id", {
|
|
58504
|
+
min: 8,
|
|
58505
|
+
max: 128,
|
|
58506
|
+
pattern: OPERATION_PATTERN
|
|
58507
|
+
});
|
|
58508
|
+
requireString(request.step_id, "step_id", { min: 3, max: 128, pattern: STEP_PATTERN });
|
|
58509
|
+
requireString(request.target_selector, "target_selector", { max: 512 });
|
|
58510
|
+
requireString(request.project_id, "project_id", {
|
|
58511
|
+
min: 16,
|
|
58512
|
+
max: 128,
|
|
58513
|
+
pattern: WORKSPACE_ID_PATTERN
|
|
58514
|
+
});
|
|
58515
|
+
requireString(request.project_name, "project_name", { max: 256 });
|
|
58516
|
+
requireString(request.project_slug, "project_slug", { max: 128 });
|
|
58517
|
+
requireString(request.request_digest, "request_digest", {
|
|
58518
|
+
min: 64,
|
|
58519
|
+
max: 64,
|
|
58520
|
+
pattern: SHA256_PATTERN
|
|
58521
|
+
});
|
|
58522
|
+
requireString(request.precondition_digest, "precondition_digest", {
|
|
58523
|
+
min: 64,
|
|
58524
|
+
max: 64,
|
|
58525
|
+
pattern: SHA256_PATTERN
|
|
58526
|
+
});
|
|
58527
|
+
requireString(request.idempotency_key, "idempotency_key", {
|
|
58528
|
+
min: 52,
|
|
58529
|
+
max: 52,
|
|
58530
|
+
pattern: IDEMPOTENCY_PATTERN
|
|
58531
|
+
});
|
|
58532
|
+
if (!request.desired || typeof request.desired !== "object" || Array.isArray(request.desired)) {
|
|
58533
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "desired must be a JSON object");
|
|
58534
|
+
}
|
|
58535
|
+
if (digestMementosProjectRegistrationValue(request.desired) !== request.request_digest) {
|
|
58536
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "request_digest does not match the canonical desired payload");
|
|
58537
|
+
}
|
|
58538
|
+
const expectedKey = deriveMementosProjectRegistrationIdempotencyKey(request);
|
|
58539
|
+
if (request.idempotency_key !== expectedKey) {
|
|
58540
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_IDEMPOTENCY_MISMATCH", "idempotency_key does not match the deterministic request", { expected: expectedKey });
|
|
58541
|
+
}
|
|
58542
|
+
}
|
|
58543
|
+
function assertForwardRequest2(request, capability) {
|
|
58544
|
+
assertCommonRequest(request, capability);
|
|
58545
|
+
if (request.direction !== "forward" || request.accepted_receipt !== undefined) {
|
|
58546
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "conditional create requires a forward request without an accepted receipt");
|
|
58547
|
+
}
|
|
58548
|
+
exactKeys(request.desired, [
|
|
58549
|
+
"name",
|
|
58550
|
+
"source_project_id",
|
|
58551
|
+
"source_project_slug",
|
|
58552
|
+
"target_path_digest"
|
|
58553
|
+
], "forward desired");
|
|
58554
|
+
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) {
|
|
58555
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "forward project identity fields disagree");
|
|
58556
|
+
}
|
|
58557
|
+
const expectedPrecondition = digestMementosProjectRegistrationValue({
|
|
58558
|
+
target_selector: request.target_selector,
|
|
58559
|
+
expected: "absent"
|
|
58560
|
+
});
|
|
58561
|
+
if (request.precondition_digest !== expectedPrecondition) {
|
|
58562
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "forward precondition must bind expected absence");
|
|
58563
|
+
}
|
|
58564
|
+
const path = ownedPath(request.target);
|
|
58565
|
+
if (request.desired["target_path_digest"] !== digestOwnedPath(path)) {
|
|
58566
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "target path does not match the package-owned digest");
|
|
58567
|
+
}
|
|
58568
|
+
return path;
|
|
58569
|
+
}
|
|
58570
|
+
function sameReceipt(left, right) {
|
|
58571
|
+
return canonicalMementosProjectRegistrationJson(publicReceipt(left)) === canonicalMementosProjectRegistrationJson(right);
|
|
58572
|
+
}
|
|
58573
|
+
function assertInverseRequest(request, capability) {
|
|
58574
|
+
assertCommonRequest(request, capability);
|
|
58575
|
+
if (request.direction !== "inverse" || !request.accepted_receipt) {
|
|
58576
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "conditional inverse requires the accepted forward receipt");
|
|
58577
|
+
}
|
|
58578
|
+
const accepted = request.accepted_receipt;
|
|
58579
|
+
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) {
|
|
58580
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "accepted receipt is not an exact attempt-created forward receipt");
|
|
58581
|
+
}
|
|
58582
|
+
exactKeys(request.desired, ["accepted_receipt_id", "target_id"], "inverse desired");
|
|
58583
|
+
if (request.desired["accepted_receipt_id"] !== accepted.receipt_id || request.desired["target_id"] !== accepted.target_id) {
|
|
58584
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "inverse desired payload does not bind the accepted receipt");
|
|
58585
|
+
}
|
|
58586
|
+
const expectedPrecondition = digestMementosProjectRegistrationValue({
|
|
58587
|
+
expected_revision: accepted.result_revision,
|
|
58588
|
+
expected_digest: accepted.result_digest
|
|
58589
|
+
});
|
|
58590
|
+
if (request.precondition_digest !== expectedPrecondition) {
|
|
58591
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "inverse precondition does not bind the accepted object state");
|
|
58592
|
+
}
|
|
58593
|
+
return accepted;
|
|
58594
|
+
}
|
|
58595
|
+
function assertLookup(request, capability) {
|
|
58596
|
+
assertBounds(request);
|
|
58597
|
+
if (request.max_items !== 1) {
|
|
58598
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "terminal receipt lookup requires max_items exactly 1");
|
|
58599
|
+
}
|
|
58600
|
+
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) {
|
|
58601
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH", "receipt lookup does not match this authority capability identity");
|
|
58602
|
+
}
|
|
58603
|
+
requireString(request.operation_id, "operation_id", { pattern: OPERATION_PATTERN });
|
|
58604
|
+
requireString(request.step_id, "step_id", { pattern: STEP_PATTERN });
|
|
58605
|
+
requireString(request.target_selector, "target_selector");
|
|
58606
|
+
requireString(request.idempotency_key, "idempotency_key", {
|
|
58607
|
+
min: 52,
|
|
58608
|
+
max: 52,
|
|
58609
|
+
pattern: IDEMPOTENCY_PATTERN
|
|
58610
|
+
});
|
|
58611
|
+
if (request.target_id !== undefined) {
|
|
58612
|
+
requireString(request.target_id, "target_id", {
|
|
58613
|
+
min: 51,
|
|
58614
|
+
max: 51,
|
|
58615
|
+
pattern: PROJECT_ID_PATTERN
|
|
58616
|
+
});
|
|
58617
|
+
}
|
|
58618
|
+
}
|
|
58619
|
+
|
|
58620
|
+
class PackageOwnedMementosProjectRegistrationAuthority {
|
|
58621
|
+
db;
|
|
58622
|
+
authority = "mementos";
|
|
58623
|
+
capabilityValue;
|
|
58624
|
+
now;
|
|
58625
|
+
faultInjector;
|
|
58626
|
+
constructor(db, options = {}) {
|
|
58627
|
+
this.db = db;
|
|
58628
|
+
this.now = options.now ?? (() => new Date().toISOString());
|
|
58629
|
+
this.faultInjector = options.faultInjector;
|
|
58630
|
+
this.capabilityValue = {
|
|
58631
|
+
authority: "mementos",
|
|
58632
|
+
route: MEMENTOS_PROJECT_REGISTRATION_ROUTE,
|
|
58633
|
+
package_version: options.packageVersion ?? getMementosPackageVersion(),
|
|
58634
|
+
authority_id: options.authorityId ?? "mementos",
|
|
58635
|
+
tenant_id: options.tenantId ?? "default",
|
|
58636
|
+
corpus_id: options.corpusId ?? "default",
|
|
58637
|
+
supported_resources: ["project"],
|
|
58638
|
+
conditional_create: true,
|
|
58639
|
+
immutable_receipts: true,
|
|
58640
|
+
exact_terminal_lookup: true,
|
|
58641
|
+
exact_readback: true,
|
|
58642
|
+
conditional_inverse: true,
|
|
58643
|
+
ambiguous_outcome_reconciliation: true,
|
|
58644
|
+
guarded_update: true,
|
|
58645
|
+
no_write_dry_run: true,
|
|
58646
|
+
expected_revision_compare_and_swap: true,
|
|
58647
|
+
caller_idempotency: true,
|
|
58648
|
+
exact_inverse_rollback: true
|
|
58649
|
+
};
|
|
58650
|
+
}
|
|
58651
|
+
fault(point, request) {
|
|
58652
|
+
this.faultInjector?.(point, {
|
|
58653
|
+
operation_id: request.operation_id,
|
|
58654
|
+
step_id: request.step_id,
|
|
58655
|
+
resource_kind: "project",
|
|
58656
|
+
direction: request.direction
|
|
58657
|
+
});
|
|
58658
|
+
}
|
|
58659
|
+
writeBoundary(point, request) {
|
|
58660
|
+
try {
|
|
58661
|
+
this.fault(point, request);
|
|
58662
|
+
} catch (cause) {
|
|
58663
|
+
throw new WriteBoundaryError(point, cause);
|
|
58664
|
+
}
|
|
58665
|
+
}
|
|
58666
|
+
terminal(request, callDigest, reason, values = {}) {
|
|
58667
|
+
return insertReceipt2(this.db, makeReceipt2(request, this.capabilityValue, callDigest, this.now(), {
|
|
58668
|
+
outcome: "terminal_nonacceptance",
|
|
58669
|
+
reason,
|
|
58670
|
+
target_id: values.target_id,
|
|
58671
|
+
accepted_receipt_id: values.accepted_receipt_id,
|
|
58672
|
+
target_selector: values.target_selector
|
|
58673
|
+
}));
|
|
58674
|
+
}
|
|
58675
|
+
duplicateForwardOrTerminal(request, callDigest, path, accepted) {
|
|
58676
|
+
if (accepted.normalized_call_digest !== callDigest) {
|
|
58677
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_IDEMPOTENCY_MISMATCH", "accepted operation/step identity is bound to a different request");
|
|
58678
|
+
}
|
|
58679
|
+
const project = accepted.target_id ? getProjectByExactId3(this.db, accepted.target_id) : null;
|
|
58680
|
+
if (!project || project.path !== path) {
|
|
58681
|
+
return this.terminal(request, callDigest, "accepted_target_missing_or_replaced", {
|
|
58682
|
+
target_id: accepted.target_id
|
|
58683
|
+
});
|
|
58684
|
+
}
|
|
58685
|
+
const record = projectRecord(this.db, project);
|
|
58686
|
+
if (record.revision !== accepted.result_revision || record.digest !== accepted.result_digest) {
|
|
58687
|
+
return this.terminal(request, callDigest, "accepted_target_drifted", {
|
|
58688
|
+
target_id: accepted.target_id
|
|
58689
|
+
});
|
|
58690
|
+
}
|
|
58691
|
+
return insertReceipt2(this.db, makeReceipt2(request, this.capabilityValue, callDigest, this.now(), {
|
|
58692
|
+
outcome: "duplicate_of_accepted",
|
|
58693
|
+
target_id: accepted.target_id,
|
|
58694
|
+
result_revision: accepted.result_revision,
|
|
58695
|
+
result_digest: accepted.result_digest,
|
|
58696
|
+
duplicate_of_receipt_id: accepted.receipt_id,
|
|
58697
|
+
created_by_operation: false
|
|
58698
|
+
}));
|
|
58699
|
+
}
|
|
58700
|
+
async capability() {
|
|
58701
|
+
return {
|
|
58702
|
+
...this.capabilityValue,
|
|
58703
|
+
supported_resources: ["project"]
|
|
58704
|
+
};
|
|
58705
|
+
}
|
|
58706
|
+
async create(request) {
|
|
58707
|
+
const startedAt = Date.now();
|
|
58708
|
+
const path = assertForwardRequest2(request, this.capabilityValue);
|
|
58709
|
+
const callDigest = normalizedCallDigest(request);
|
|
58710
|
+
let stored;
|
|
58711
|
+
try {
|
|
58712
|
+
stored = this.db.transaction(() => {
|
|
58713
|
+
const exact = getReceiptForLookup(this.db, {
|
|
58714
|
+
...request,
|
|
58715
|
+
authority: "mementos",
|
|
58716
|
+
max_items: 1
|
|
58717
|
+
});
|
|
58718
|
+
if (exact?.outcome === "terminal_nonacceptance")
|
|
58719
|
+
return exact;
|
|
58720
|
+
const accepted = getAcceptedReceipt(this.db, request);
|
|
58721
|
+
if (accepted) {
|
|
58722
|
+
return this.duplicateForwardOrTerminal(request, callDigest, path, accepted);
|
|
58723
|
+
}
|
|
58724
|
+
const existing = getProjectByPath(this.db, path) ?? getProjectByExactId3(this.db, targetId(this.capabilityValue, request.target_selector));
|
|
58725
|
+
if (existing) {
|
|
58726
|
+
return this.terminal(request, callDigest, "target_preexists", {
|
|
58727
|
+
target_id: existing.id
|
|
58728
|
+
});
|
|
58729
|
+
}
|
|
58730
|
+
const now2 = this.now();
|
|
58731
|
+
const insertedBinding = insertBinding(this.db, request, callDigest, now2);
|
|
58732
|
+
if (!lockBinding(this.db, this.capabilityValue, request.target_selector)) {
|
|
58733
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "registration binding disappeared before it could be locked");
|
|
58734
|
+
}
|
|
58735
|
+
const ownedBinding = getBinding(this.db, this.capabilityValue, request.target_selector);
|
|
58736
|
+
if (!ownedBinding || ownedBinding.normalized_call_digest !== callDigest) {
|
|
58737
|
+
return this.terminal(request, callDigest, "target_selector_preexists", {
|
|
58738
|
+
target_id: ownedBinding?.target_id
|
|
58739
|
+
});
|
|
58740
|
+
}
|
|
58741
|
+
const acceptedAfterBinding = getAcceptedReceipt(this.db, request);
|
|
58742
|
+
if (acceptedAfterBinding) {
|
|
58743
|
+
return this.duplicateForwardOrTerminal(request, callDigest, path, acceptedAfterBinding);
|
|
58744
|
+
}
|
|
58745
|
+
if (!insertedBinding || ownedBinding.state !== "pending") {
|
|
58746
|
+
return this.terminal(request, callDigest, "target_selector_preexists", {
|
|
58747
|
+
target_id: ownedBinding.target_id
|
|
58748
|
+
});
|
|
58749
|
+
}
|
|
58750
|
+
const id = targetId(this.capabilityValue, request.target_selector);
|
|
58751
|
+
this.writeBoundary("before_object_write", request);
|
|
58752
|
+
try {
|
|
58753
|
+
this.db.run(`
|
|
58754
|
+
INSERT INTO projects (
|
|
58755
|
+
id, name, path, description, memory_prefix, created_at, updated_at
|
|
58756
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
58757
|
+
`, id, request.project_name, path, null, request.project_slug, now2, now2);
|
|
58758
|
+
} catch (cause) {
|
|
58759
|
+
throw new WriteBoundaryError("before_object_write", cause);
|
|
58760
|
+
}
|
|
58761
|
+
this.writeBoundary("after_object_write", request);
|
|
58762
|
+
const project = getProjectByExactId3(this.db, id);
|
|
58763
|
+
if (!project || project.path !== path) {
|
|
58764
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "project write did not read back by its full id");
|
|
58765
|
+
}
|
|
58766
|
+
const record = projectRecord(this.db, project);
|
|
58767
|
+
const acceptedReceipt = makeReceipt2(request, this.capabilityValue, callDigest, now2, {
|
|
58768
|
+
outcome: "accepted",
|
|
58769
|
+
target_id: record.target_id,
|
|
58770
|
+
result_revision: record.revision,
|
|
58771
|
+
result_digest: record.digest,
|
|
58772
|
+
created_by_operation: true
|
|
58773
|
+
});
|
|
58774
|
+
this.writeBoundary("before_receipt_write", request);
|
|
58775
|
+
let inserted;
|
|
58776
|
+
try {
|
|
58777
|
+
inserted = insertReceipt2(this.db, acceptedReceipt);
|
|
58778
|
+
} catch (cause) {
|
|
58779
|
+
throw new WriteBoundaryError("before_receipt_write", cause);
|
|
58780
|
+
}
|
|
58781
|
+
this.writeBoundary("after_receipt_write", request);
|
|
58782
|
+
setBindingAccepted(this.db, request, inserted, now2);
|
|
58783
|
+
return inserted;
|
|
58784
|
+
});
|
|
58785
|
+
} catch (error) {
|
|
58786
|
+
if (!(error instanceof WriteBoundaryError))
|
|
58787
|
+
throw error;
|
|
58788
|
+
stored = this.db.transaction(() => this.terminal(request, callDigest, `write_failed:${error.point}`));
|
|
58789
|
+
}
|
|
58790
|
+
this.fault("after_commit", request);
|
|
58791
|
+
const receipt = publicReceipt(stored);
|
|
58792
|
+
assertWithinBounds(receipt, request, startedAt);
|
|
58793
|
+
return receipt;
|
|
58794
|
+
}
|
|
58795
|
+
async readExact(request) {
|
|
58796
|
+
const startedAt = Date.now();
|
|
58797
|
+
assertBounds(request);
|
|
58798
|
+
if (request.resource_kind !== "project") {
|
|
58799
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "resource_kind must be project");
|
|
58800
|
+
}
|
|
58801
|
+
requireString(request.target_id, "target_id", {
|
|
58802
|
+
min: 51,
|
|
58803
|
+
max: 51,
|
|
58804
|
+
pattern: PROJECT_ID_PATTERN
|
|
58805
|
+
});
|
|
58806
|
+
const path = ownedPath(request.target);
|
|
58807
|
+
const project = getProjectByExactId3(this.db, request.target_id);
|
|
58808
|
+
if (!project || project.path !== path) {
|
|
58809
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_RECORD_NOT_FOUND", "exact full-id project readback did not resolve the owned path");
|
|
58810
|
+
}
|
|
58811
|
+
const record = projectRecord(this.db, project);
|
|
58812
|
+
assertWithinBounds(record, request, startedAt);
|
|
58813
|
+
return record;
|
|
58814
|
+
}
|
|
58815
|
+
async lookupReceipt(request) {
|
|
58816
|
+
const startedAt = Date.now();
|
|
58817
|
+
assertLookup(request, this.capabilityValue);
|
|
58818
|
+
const receipt = getReceiptForLookup(this.db, request);
|
|
58819
|
+
if (!receipt || request.target_id !== undefined && receipt.target_id !== request.target_id) {
|
|
58820
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND", "exact immutable terminal receipt was not found");
|
|
58821
|
+
}
|
|
58822
|
+
return withResponseControl(publicReceipt(receipt), request, startedAt);
|
|
58823
|
+
}
|
|
58824
|
+
async compensate(request) {
|
|
58825
|
+
const startedAt = Date.now();
|
|
58826
|
+
const acceptedPublic = assertInverseRequest(request, this.capabilityValue);
|
|
58827
|
+
const callDigest = normalizedCallDigest(request);
|
|
58828
|
+
const storedAccepted = getStoredReceipt(this.db, acceptedPublic.receipt_id);
|
|
58829
|
+
if (!storedAccepted || !sameReceipt(storedAccepted, acceptedPublic)) {
|
|
58830
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND", "accepted forward receipt is not stored immutably in this authority");
|
|
58831
|
+
}
|
|
58832
|
+
const path = ownedPath(request.target);
|
|
58833
|
+
let stored;
|
|
58834
|
+
try {
|
|
58835
|
+
stored = this.db.transaction(() => {
|
|
58836
|
+
const exact = getReceiptForLookup(this.db, {
|
|
58837
|
+
...request,
|
|
58838
|
+
authority: "mementos",
|
|
58839
|
+
max_items: 1
|
|
58840
|
+
});
|
|
58841
|
+
if (exact?.outcome === "terminal_nonacceptance")
|
|
58842
|
+
return exact;
|
|
58843
|
+
if (!lockBinding(this.db, this.capabilityValue, storedAccepted.target_selector)) {
|
|
58844
|
+
return this.terminal(request, callDigest, "accepted_binding_missing", {
|
|
58845
|
+
target_id: storedAccepted.target_id,
|
|
58846
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
58847
|
+
});
|
|
58848
|
+
}
|
|
58849
|
+
const binding = getBinding(this.db, this.capabilityValue, storedAccepted.target_selector);
|
|
58850
|
+
if (!binding || binding.accepted_receipt_id !== storedAccepted.receipt_id) {
|
|
58851
|
+
return this.terminal(request, callDigest, "accepted_binding_replaced", {
|
|
58852
|
+
target_id: storedAccepted.target_id,
|
|
58853
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
58854
|
+
});
|
|
58855
|
+
}
|
|
58856
|
+
const prior = getAcceptedReceipt(this.db, request);
|
|
58857
|
+
if (prior) {
|
|
58858
|
+
if (prior.normalized_call_digest !== callDigest || prior.accepted_receipt_id !== storedAccepted.receipt_id) {
|
|
58859
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_IDEMPOTENCY_MISMATCH", "inverse operation/step identity is bound to a different request");
|
|
58860
|
+
}
|
|
58861
|
+
return insertReceipt2(this.db, makeReceipt2(request, this.capabilityValue, callDigest, this.now(), {
|
|
58862
|
+
outcome: "duplicate_of_accepted",
|
|
58863
|
+
target_id: prior.target_id,
|
|
58864
|
+
result_revision: prior.result_revision,
|
|
58865
|
+
result_digest: prior.result_digest,
|
|
58866
|
+
duplicate_of_receipt_id: prior.receipt_id,
|
|
58867
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
58868
|
+
}));
|
|
58869
|
+
}
|
|
58870
|
+
if (binding.state !== "accepted") {
|
|
58871
|
+
return this.terminal(request, callDigest, "accepted_binding_not_active", {
|
|
58872
|
+
target_id: storedAccepted.target_id,
|
|
58873
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
58874
|
+
});
|
|
58875
|
+
}
|
|
58876
|
+
const project = getProjectByExactId3(this.db, storedAccepted.target_id);
|
|
58877
|
+
if (!project) {
|
|
58878
|
+
return this.terminal(request, callDigest, "target_missing", {
|
|
58879
|
+
target_id: storedAccepted.target_id,
|
|
58880
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
58881
|
+
});
|
|
58882
|
+
}
|
|
58883
|
+
if (project.path !== path) {
|
|
58884
|
+
return this.terminal(request, callDigest, "target_drifted", {
|
|
58885
|
+
target_id: storedAccepted.target_id,
|
|
58886
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
58887
|
+
});
|
|
58888
|
+
}
|
|
58889
|
+
const references = mementosProjectReferenceCounts(this.db, project.id);
|
|
58890
|
+
if (hasMementosProjectReferences(references)) {
|
|
58891
|
+
return this.terminal(request, callDigest, "target_has_dependents", {
|
|
58892
|
+
target_id: storedAccepted.target_id,
|
|
58893
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
58894
|
+
});
|
|
58895
|
+
}
|
|
58896
|
+
const current = projectRecord(this.db, project, references);
|
|
58897
|
+
if (current.revision !== storedAccepted.result_revision || current.digest !== storedAccepted.result_digest) {
|
|
58898
|
+
return this.terminal(request, callDigest, "target_drifted", {
|
|
58899
|
+
target_id: storedAccepted.target_id,
|
|
58900
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
58901
|
+
});
|
|
58902
|
+
}
|
|
58903
|
+
this.writeBoundary("before_object_write", request);
|
|
58904
|
+
let deleted;
|
|
58905
|
+
try {
|
|
58906
|
+
deleted = deleteMementosProjectIfUnreferenced(this.db, storedAccepted.target_id);
|
|
58907
|
+
} catch (cause) {
|
|
58908
|
+
throw new WriteBoundaryError("before_object_write", cause);
|
|
58909
|
+
}
|
|
58910
|
+
if (deleted !== 1) {
|
|
58911
|
+
const remaining = getProjectByExactId3(this.db, storedAccepted.target_id);
|
|
58912
|
+
if (remaining && hasMementosProjectReferences(mementosProjectReferenceCounts(this.db, storedAccepted.target_id))) {
|
|
58913
|
+
return this.terminal(request, callDigest, "target_has_dependents", {
|
|
58914
|
+
target_id: storedAccepted.target_id,
|
|
58915
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
58916
|
+
});
|
|
58917
|
+
}
|
|
58918
|
+
throw new WriteBoundaryError("before_object_write", new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "conditional inverse did not delete exactly one unreferenced project"));
|
|
58919
|
+
}
|
|
58920
|
+
this.writeBoundary("after_object_write", request);
|
|
58921
|
+
const inverseRecord = {
|
|
58922
|
+
target_id: storedAccepted.target_id,
|
|
58923
|
+
revision: "absent",
|
|
58924
|
+
digest: digestMementosProjectRegistrationValue({
|
|
58925
|
+
target_id: storedAccepted.target_id,
|
|
58926
|
+
accepted_receipt_id: storedAccepted.receipt_id,
|
|
58927
|
+
absent: true
|
|
58928
|
+
})
|
|
58929
|
+
};
|
|
58930
|
+
const inverseReceipt = makeReceipt2(request, this.capabilityValue, callDigest, this.now(), {
|
|
58931
|
+
outcome: "accepted",
|
|
58932
|
+
target_id: inverseRecord.target_id,
|
|
58933
|
+
result_revision: inverseRecord.revision,
|
|
58934
|
+
result_digest: inverseRecord.digest,
|
|
58935
|
+
accepted_receipt_id: storedAccepted.receipt_id,
|
|
58936
|
+
created_by_operation: false
|
|
58937
|
+
});
|
|
58938
|
+
this.writeBoundary("before_receipt_write", request);
|
|
58939
|
+
let inserted;
|
|
58940
|
+
try {
|
|
58941
|
+
inserted = insertReceipt2(this.db, inverseReceipt);
|
|
58942
|
+
} catch (cause) {
|
|
58943
|
+
throw new WriteBoundaryError("before_receipt_write", cause);
|
|
58944
|
+
}
|
|
58945
|
+
this.writeBoundary("after_receipt_write", request);
|
|
58946
|
+
setBindingRemoved(this.db, storedAccepted, inserted.receipt_id, this.now());
|
|
58947
|
+
return inserted;
|
|
58948
|
+
});
|
|
58949
|
+
} catch (error) {
|
|
58950
|
+
if (!(error instanceof WriteBoundaryError))
|
|
58951
|
+
throw error;
|
|
58952
|
+
stored = this.db.transaction(() => this.terminal(request, callDigest, `write_failed:${error.point}`, {
|
|
58953
|
+
target_id: storedAccepted.target_id,
|
|
58954
|
+
accepted_receipt_id: storedAccepted.receipt_id
|
|
58955
|
+
}));
|
|
58956
|
+
}
|
|
58957
|
+
this.fault("after_commit", request);
|
|
58958
|
+
const receipt = publicReceipt(stored);
|
|
58959
|
+
assertWithinBounds(receipt, request, startedAt);
|
|
58960
|
+
return receipt;
|
|
58961
|
+
}
|
|
58962
|
+
async verifyInverse(request) {
|
|
58963
|
+
const startedAt = Date.now();
|
|
58964
|
+
const accepted = assertInverseRequest(request, this.capabilityValue);
|
|
58965
|
+
const storedAccepted = getStoredReceipt(this.db, accepted.receipt_id);
|
|
58966
|
+
if (!storedAccepted || !sameReceipt(storedAccepted, accepted)) {
|
|
58967
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND", "accepted forward receipt is not stored immutably in this authority");
|
|
58968
|
+
}
|
|
58969
|
+
const inverse = getAcceptedReceipt(this.db, request);
|
|
58970
|
+
if (!inverse || inverse.outcome !== "accepted" || inverse.accepted_receipt_id !== accepted.receipt_id || inverse.result_revision !== "absent" || !inverse.result_digest) {
|
|
58971
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND", "accepted conditional inverse receipt was not found");
|
|
58972
|
+
}
|
|
58973
|
+
if (getProjectByExactId3(this.db, accepted.target_id)) {
|
|
58974
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "inverse verification found the accepted target still present");
|
|
58975
|
+
}
|
|
58976
|
+
const verification = {
|
|
58977
|
+
target_id: accepted.target_id,
|
|
58978
|
+
accepted_receipt_id: accepted.receipt_id,
|
|
58979
|
+
absent: true,
|
|
58980
|
+
digest: digestMementosProjectRegistrationValue({
|
|
58981
|
+
target_id: accepted.target_id,
|
|
58982
|
+
accepted_receipt_id: accepted.receipt_id,
|
|
58983
|
+
absent: true
|
|
58984
|
+
})
|
|
58985
|
+
};
|
|
58986
|
+
assertWithinBounds(verification, request, startedAt);
|
|
58987
|
+
if (verification.digest !== inverse.result_digest) {
|
|
58988
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "inverse verification digest does not match its immutable receipt");
|
|
58989
|
+
}
|
|
58990
|
+
return verification;
|
|
58991
|
+
}
|
|
58992
|
+
}
|
|
58993
|
+
function createMementosProjectRegistrationAuthority(db, options = {}) {
|
|
58994
|
+
return new PackageOwnedMementosProjectRegistrationAuthority(db, options);
|
|
58995
|
+
}
|
|
58996
|
+
// src/project-registration/http.ts
|
|
58997
|
+
var JSON_HEADERS = { "Content-Type": "application/json" };
|
|
58998
|
+
|
|
58999
|
+
class WirePathHandle {
|
|
59000
|
+
absolutePath;
|
|
59001
|
+
constructor(absolutePath) {
|
|
59002
|
+
this.absolutePath = absolutePath;
|
|
59003
|
+
}
|
|
59004
|
+
withOwnedPath(consumer) {
|
|
59005
|
+
return consumer(this.absolutePath);
|
|
59006
|
+
}
|
|
59007
|
+
}
|
|
59008
|
+
function json2(body, status = 200) {
|
|
59009
|
+
return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS });
|
|
59010
|
+
}
|
|
59011
|
+
function errorStatus(error) {
|
|
59012
|
+
switch (error.code) {
|
|
59013
|
+
case "MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT":
|
|
59014
|
+
case "MEMENTOS_PROJECT_REGISTRATION_INVALID_BOUNDS":
|
|
59015
|
+
case "MEMENTOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH":
|
|
59016
|
+
case "MEMENTOS_PROJECT_REGISTRATION_DIGEST_MISMATCH":
|
|
59017
|
+
case "MEMENTOS_PROJECT_REGISTRATION_IDEMPOTENCY_MISMATCH":
|
|
59018
|
+
case "MEMENTOS_PROJECT_REGISTRATION_EXACT_ID_REQUIRED":
|
|
59019
|
+
return 400;
|
|
59020
|
+
case "MEMENTOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND":
|
|
59021
|
+
case "MEMENTOS_PROJECT_REGISTRATION_RECORD_NOT_FOUND":
|
|
59022
|
+
case "MEMENTOS_PROJECT_REGISTRATION_ACCEPTED_RECEIPT_NOT_FOUND":
|
|
59023
|
+
return 404;
|
|
59024
|
+
case "MEMENTOS_PROJECT_REGISTRATION_RESPONSE_TOO_LARGE":
|
|
59025
|
+
return 413;
|
|
59026
|
+
case "MEMENTOS_PROJECT_REGISTRATION_TIME_BUDGET_EXCEEDED":
|
|
59027
|
+
return 408;
|
|
59028
|
+
case "MEMENTOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE":
|
|
59029
|
+
return 503;
|
|
59030
|
+
default:
|
|
59031
|
+
return 409;
|
|
59032
|
+
}
|
|
59033
|
+
}
|
|
59034
|
+
async function readJson2(request) {
|
|
59035
|
+
try {
|
|
59036
|
+
const value = await request.json();
|
|
59037
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
59038
|
+
} catch {
|
|
59039
|
+
return null;
|
|
59040
|
+
}
|
|
59041
|
+
}
|
|
59042
|
+
function fromWireRequest(body) {
|
|
59043
|
+
const { canonical_path: canonicalPath, ...request } = body;
|
|
59044
|
+
if (typeof canonicalPath !== "string") {
|
|
59045
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "canonical_path is required on the private registration transport");
|
|
59046
|
+
}
|
|
59047
|
+
return {
|
|
59048
|
+
...request,
|
|
59049
|
+
target: new WirePathHandle(canonicalPath)
|
|
59050
|
+
};
|
|
59051
|
+
}
|
|
59052
|
+
function fromWireReadRequest(body) {
|
|
59053
|
+
const { canonical_path: canonicalPath, ...request } = body;
|
|
59054
|
+
if (typeof canonicalPath !== "string") {
|
|
59055
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "canonical_path is required on the private exact-read transport");
|
|
59056
|
+
}
|
|
59057
|
+
return {
|
|
59058
|
+
...request,
|
|
59059
|
+
target: new WirePathHandle(canonicalPath)
|
|
59060
|
+
};
|
|
59061
|
+
}
|
|
59062
|
+
async function handleMementosProjectRegistrationHttpRequest(request, url, authority, basePath = "/v1/project-registration") {
|
|
59063
|
+
const path = url.pathname;
|
|
59064
|
+
if (path !== basePath && !path.startsWith(`${basePath}/`))
|
|
59065
|
+
return null;
|
|
59066
|
+
const action = path.slice(basePath.length).split("/").filter(Boolean).join("/");
|
|
59067
|
+
const method = request.method.toUpperCase();
|
|
59068
|
+
try {
|
|
59069
|
+
if ((action === "" || action === "capability") && method === "GET") {
|
|
59070
|
+
return json2({ capability: await authority.capability() });
|
|
59071
|
+
}
|
|
59072
|
+
if (method !== "POST")
|
|
59073
|
+
return json2({ error: "method not allowed" }, 405);
|
|
59074
|
+
const body = await readJson2(request);
|
|
59075
|
+
if (!body) {
|
|
59076
|
+
return json2({
|
|
59077
|
+
error: "invalid JSON body",
|
|
59078
|
+
code: "MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT"
|
|
59079
|
+
}, 400);
|
|
59080
|
+
}
|
|
59081
|
+
if (action === "create") {
|
|
59082
|
+
return json2({ receipt: await authority.create(fromWireRequest(body)) }, 201);
|
|
59083
|
+
}
|
|
59084
|
+
if (action === "receipts/lookup") {
|
|
59085
|
+
return json2(await authority.lookupReceipt(body));
|
|
59086
|
+
}
|
|
59087
|
+
if (action === "read-exact") {
|
|
59088
|
+
return json2({ record: await authority.readExact(fromWireReadRequest(body)) });
|
|
59089
|
+
}
|
|
59090
|
+
if (action === "compensate") {
|
|
59091
|
+
return json2({ receipt: await authority.compensate(fromWireRequest(body)) }, 201);
|
|
59092
|
+
}
|
|
59093
|
+
if (action === "verify-inverse") {
|
|
59094
|
+
return json2({ verification: await authority.verifyInverse(fromWireRequest(body)) });
|
|
59095
|
+
}
|
|
59096
|
+
return json2({
|
|
59097
|
+
error: "unknown Mementos project-registration route",
|
|
59098
|
+
code: "MEMENTOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND"
|
|
59099
|
+
}, 404);
|
|
59100
|
+
} catch (cause) {
|
|
59101
|
+
if (cause instanceof MementosProjectRegistrationError) {
|
|
59102
|
+
return json2({
|
|
59103
|
+
error: cause.message,
|
|
59104
|
+
code: cause.code,
|
|
59105
|
+
details: cause.details,
|
|
59106
|
+
authoritative: true
|
|
59107
|
+
}, errorStatus(cause));
|
|
59108
|
+
}
|
|
59109
|
+
return json2({
|
|
59110
|
+
error: cause instanceof Error ? cause.message : "internal registration error",
|
|
59111
|
+
code: "MEMENTOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE"
|
|
59112
|
+
}, 500);
|
|
59113
|
+
}
|
|
59114
|
+
}
|
|
59115
|
+
// src/server/routes/project-registration.ts
|
|
59116
|
+
init_router();
|
|
59117
|
+
var handle = async (request, url) => {
|
|
59118
|
+
const basePath = url.pathname.startsWith("/v1/") ? "/v1/project-registration" : "/api/project-registration";
|
|
59119
|
+
const response = await handleMementosProjectRegistrationHttpRequest(request, url, createMementosProjectRegistrationAuthority(getDatabase()), basePath);
|
|
59120
|
+
return response ?? new Response(JSON.stringify({ error: "Not found" }), {
|
|
59121
|
+
status: 404,
|
|
59122
|
+
headers: { "Content-Type": "application/json" }
|
|
59123
|
+
});
|
|
59124
|
+
};
|
|
59125
|
+
addRoute("GET", "/api/project-registration", handle);
|
|
59126
|
+
addRoute("GET", "/api/project-registration/capability", handle);
|
|
59127
|
+
addRoute("POST", "/api/project-registration/create", handle);
|
|
59128
|
+
addRoute("POST", "/api/project-registration/receipts/lookup", handle);
|
|
59129
|
+
addRoute("POST", "/api/project-registration/read-exact", handle);
|
|
59130
|
+
addRoute("POST", "/api/project-registration/compensate", handle);
|
|
59131
|
+
addRoute("POST", "/api/project-registration/verify-inverse", handle);
|
|
59132
|
+
|
|
56615
59133
|
// src/server/routes/entities.ts
|
|
56616
59134
|
init_entities();
|
|
56617
59135
|
init_relations();
|
|
@@ -57654,12 +60172,12 @@ function executeAddTag(proposal, d) {
|
|
|
57654
60172
|
return { old_tags: rollback };
|
|
57655
60173
|
}
|
|
57656
60174
|
function executeMerge(proposal, d) {
|
|
57657
|
-
const
|
|
57658
|
-
if (!
|
|
60175
|
+
const targetId2 = proposal.target_memory_id;
|
|
60176
|
+
if (!targetId2)
|
|
57659
60177
|
throw new Error("merge proposal missing target_memory_id");
|
|
57660
|
-
const target = getMemory(
|
|
60178
|
+
const target = getMemory(targetId2, d);
|
|
57661
60179
|
if (!target)
|
|
57662
|
-
throw new Error(`Target memory ${
|
|
60180
|
+
throw new Error(`Target memory ${targetId2} not found`);
|
|
57663
60181
|
const rollback = {
|
|
57664
60182
|
target_old_value: target.value,
|
|
57665
60183
|
target_old_version: target.version,
|
|
@@ -57667,7 +60185,7 @@ function executeMerge(proposal, d) {
|
|
|
57667
60185
|
};
|
|
57668
60186
|
const sourceValues = [];
|
|
57669
60187
|
for (const memId of proposal.memory_ids) {
|
|
57670
|
-
if (memId ===
|
|
60188
|
+
if (memId === targetId2)
|
|
57671
60189
|
continue;
|
|
57672
60190
|
const mem = getMemory(memId, d);
|
|
57673
60191
|
if (!mem)
|
|
@@ -57678,9 +60196,9 @@ function executeMerge(proposal, d) {
|
|
|
57678
60196
|
const mergedValue = proposal.proposed_changes["merged_value"] ?? [target.value, ...sourceValues].join(`
|
|
57679
60197
|
---
|
|
57680
60198
|
`);
|
|
57681
|
-
d.run("UPDATE memories SET value = ?, version = version + 1, updated_at = ? WHERE id = ?", [mergedValue, now(),
|
|
60199
|
+
d.run("UPDATE memories SET value = ?, version = version + 1, updated_at = ? WHERE id = ?", [mergedValue, now(), targetId2]);
|
|
57682
60200
|
for (const memId of proposal.memory_ids) {
|
|
57683
|
-
if (memId ===
|
|
60201
|
+
if (memId === targetId2)
|
|
57684
60202
|
continue;
|
|
57685
60203
|
d.run("UPDATE memories SET status = 'archived', updated_at = ? WHERE id = ?", [now(), memId]);
|
|
57686
60204
|
}
|
|
@@ -57770,9 +60288,9 @@ function rollbackProposal(proposal, d) {
|
|
|
57770
60288
|
const targetOldValue = rb["target_old_value"];
|
|
57771
60289
|
const targetOldVersion = rb["target_old_version"];
|
|
57772
60290
|
const archivedMemories = rb["archived_memories"];
|
|
57773
|
-
const
|
|
57774
|
-
if (
|
|
57775
|
-
d.run("UPDATE memories SET value = ?, version = ?, updated_at = ? WHERE id = ?", [targetOldValue, targetOldVersion, now(),
|
|
60291
|
+
const targetId2 = proposal.target_memory_id;
|
|
60292
|
+
if (targetId2 && targetOldValue !== undefined && targetOldVersion !== undefined) {
|
|
60293
|
+
d.run("UPDATE memories SET value = ?, version = ?, updated_at = ? WHERE id = ?", [targetOldValue, targetOldVersion, now(), targetId2]);
|
|
57776
60294
|
}
|
|
57777
60295
|
if (archivedMemories) {
|
|
57778
60296
|
for (const [memId, status] of Object.entries(archivedMemories)) {
|
|
@@ -59370,18 +61888,18 @@ function pkgVersion() {
|
|
|
59370
61888
|
}
|
|
59371
61889
|
async function findFreePort(start) {
|
|
59372
61890
|
const net = await import("net");
|
|
59373
|
-
return new Promise((
|
|
61891
|
+
return new Promise((resolve6) => {
|
|
59374
61892
|
const server = net.createServer();
|
|
59375
61893
|
server.unref();
|
|
59376
61894
|
server.on("error", () => {
|
|
59377
|
-
|
|
61895
|
+
resolve6(findFreePort(start + 1));
|
|
59378
61896
|
});
|
|
59379
61897
|
server.listen(start, () => {
|
|
59380
61898
|
const address = server.address();
|
|
59381
61899
|
if (address && typeof address === "object") {
|
|
59382
|
-
server.close(() =>
|
|
61900
|
+
server.close(() => resolve6(address.port));
|
|
59383
61901
|
} else {
|
|
59384
|
-
|
|
61902
|
+
resolve6(start);
|
|
59385
61903
|
}
|
|
59386
61904
|
});
|
|
59387
61905
|
});
|
|
@@ -59556,15 +62074,15 @@ function startServer(port) {
|
|
|
59556
62074
|
const dashDir = resolveDashboardDir();
|
|
59557
62075
|
if (existsSync5(dashDir) && (req.method === "GET" || req.method === "HEAD")) {
|
|
59558
62076
|
if (pathname !== "/") {
|
|
59559
|
-
const resolvedDash =
|
|
59560
|
-
const requestedPath =
|
|
62077
|
+
const resolvedDash = resolve5(dashDir) + sep;
|
|
62078
|
+
const requestedPath = resolve5(join7(dashDir, pathname));
|
|
59561
62079
|
if (requestedPath.startsWith(resolvedDash)) {
|
|
59562
62080
|
const staticRes = serveStaticFile(requestedPath);
|
|
59563
62081
|
if (staticRes)
|
|
59564
62082
|
return staticRes;
|
|
59565
62083
|
}
|
|
59566
62084
|
}
|
|
59567
|
-
const indexRes = serveStaticFile(
|
|
62085
|
+
const indexRes = serveStaticFile(join7(dashDir, "index.html"));
|
|
59568
62086
|
if (indexRes)
|
|
59569
62087
|
return indexRes;
|
|
59570
62088
|
}
|