@hasna/mementos 0.14.75 → 0.14.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +93 -0
- package/dist/cli/commands/info-context.d.ts.map +1 -1
- package/dist/cli/commands/info-stale.d.ts.map +1 -1
- package/dist/cli/commands/io-export.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-crud.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-list.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-recall.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-search.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-tail.d.ts.map +1 -1
- package/dist/cli/commands/project.d.ts.map +1 -1
- package/dist/cli/commands/system-watch.d.ts.map +1 -1
- package/dist/cli/helpers.d.ts +38 -0
- package/dist/cli/helpers.d.ts.map +1 -1
- package/dist/cli/index.js +2009 -334
- package/dist/db/__fixtures__/fail-closed-stub-server.d.ts.map +1 -1
- package/dist/db/memory-project-link.d.ts +13 -0
- package/dist/db/memory-project-link.d.ts.map +1 -0
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts +0 -6
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/projects.d.ts +22 -1
- package/dist/db/projects.d.ts.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2828 -163
- package/dist/lib/package-version.d.ts +3 -0
- package/dist/lib/package-version.d.ts.map +1 -0
- package/dist/mcp/index.js +414 -0
- package/dist/memory-project-link/index.d.ts +2 -0
- package/dist/memory-project-link/index.d.ts.map +1 -0
- package/dist/memory-project-link/schema.d.ts +5 -0
- package/dist/memory-project-link/schema.d.ts.map +1 -0
- package/dist/project-registration/authority.d.ts +39 -0
- package/dist/project-registration/authority.d.ts.map +1 -0
- package/dist/project-registration/http.d.ts +29 -0
- package/dist/project-registration/http.d.ts.map +1 -0
- package/dist/project-registration/index.d.ts +8 -0
- package/dist/project-registration/index.d.ts.map +1 -0
- package/dist/project-registration/project-references.d.ts +71 -0
- package/dist/project-registration/project-references.d.ts.map +1 -0
- package/dist/project-registration/schema.d.ts +5 -0
- package/dist/project-registration/schema.d.ts.map +1 -0
- package/dist/project-registration/types.d.ts +168 -0
- package/dist/project-registration/types.d.ts.map +1 -0
- package/dist/project-registration.d.ts +2 -0
- package/dist/project-registration.d.ts.map +1 -0
- package/dist/project-registration.js +1496 -0
- package/dist/sdk/index.d.ts +127 -0
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +74 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2573 -138
- package/dist/server/routes/memories.d.ts +1 -0
- package/dist/server/routes/memories.d.ts.map +1 -1
- package/dist/server/routes/memory-project-link.d.ts +2 -0
- package/dist/server/routes/memory-project-link.d.ts.map +1 -0
- package/dist/server/routes/project-registration.d.ts +2 -0
- package/dist/server/routes/project-registration.d.ts.map +1 -0
- package/dist/types/index.d.ts +119 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +9 -3
- package/dashboard/bun.lock +0 -519
package/dist/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 (
|
|
@@ -1755,6 +1951,18 @@ UPDATE memory_audit_log SET new_value_hash = NULL
|
|
|
1755
1951
|
AND new_value_hash NOT GLOB '*[^0-9A-F]*';
|
|
1756
1952
|
|
|
1757
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);
|
|
1758
1966
|
`
|
|
1759
1967
|
];
|
|
1760
1968
|
});
|
|
@@ -6192,8 +6400,8 @@ var init_util = __esm(() => {
|
|
|
6192
6400
|
|
|
6193
6401
|
// node_modules/zod/v3/ZodError.js
|
|
6194
6402
|
var ZodIssueCode, quotelessJson = (obj) => {
|
|
6195
|
-
const
|
|
6196
|
-
return
|
|
6403
|
+
const json3 = JSON.stringify(obj, null, 2);
|
|
6404
|
+
return json3.replace(/"([^"]+)":/g, "$1:");
|
|
6197
6405
|
}, ZodError;
|
|
6198
6406
|
var init_ZodError = __esm(() => {
|
|
6199
6407
|
init_util();
|
|
@@ -19385,24 +19593,24 @@ class JSONSchemaGenerator {
|
|
|
19385
19593
|
const _json = result.schema;
|
|
19386
19594
|
switch (def.type) {
|
|
19387
19595
|
case "string": {
|
|
19388
|
-
const
|
|
19389
|
-
|
|
19596
|
+
const json3 = _json;
|
|
19597
|
+
json3.type = "string";
|
|
19390
19598
|
const { minimum, maximum, format, patterns, contentEncoding } = schema._zod.bag;
|
|
19391
19599
|
if (typeof minimum === "number")
|
|
19392
|
-
|
|
19600
|
+
json3.minLength = minimum;
|
|
19393
19601
|
if (typeof maximum === "number")
|
|
19394
|
-
|
|
19602
|
+
json3.maxLength = maximum;
|
|
19395
19603
|
if (format) {
|
|
19396
|
-
|
|
19397
|
-
if (
|
|
19398
|
-
delete
|
|
19604
|
+
json3.format = formatMap[format] ?? format;
|
|
19605
|
+
if (json3.format === "")
|
|
19606
|
+
delete json3.format;
|
|
19399
19607
|
}
|
|
19400
19608
|
if (contentEncoding)
|
|
19401
|
-
|
|
19609
|
+
json3.contentEncoding = contentEncoding;
|
|
19402
19610
|
if (patterns && patterns.size > 0) {
|
|
19403
19611
|
const regexes = [...patterns];
|
|
19404
19612
|
if (regexes.length === 1)
|
|
19405
|
-
|
|
19613
|
+
json3.pattern = regexes[0].source;
|
|
19406
19614
|
else if (regexes.length > 1) {
|
|
19407
19615
|
result.schema.allOf = [
|
|
19408
19616
|
...regexes.map((regex) => ({
|
|
@@ -19415,41 +19623,41 @@ class JSONSchemaGenerator {
|
|
|
19415
19623
|
break;
|
|
19416
19624
|
}
|
|
19417
19625
|
case "number": {
|
|
19418
|
-
const
|
|
19626
|
+
const json3 = _json;
|
|
19419
19627
|
const { minimum, maximum, format, multipleOf, exclusiveMaximum, exclusiveMinimum } = schema._zod.bag;
|
|
19420
19628
|
if (typeof format === "string" && format.includes("int"))
|
|
19421
|
-
|
|
19629
|
+
json3.type = "integer";
|
|
19422
19630
|
else
|
|
19423
|
-
|
|
19631
|
+
json3.type = "number";
|
|
19424
19632
|
if (typeof exclusiveMinimum === "number")
|
|
19425
|
-
|
|
19633
|
+
json3.exclusiveMinimum = exclusiveMinimum;
|
|
19426
19634
|
if (typeof minimum === "number") {
|
|
19427
|
-
|
|
19635
|
+
json3.minimum = minimum;
|
|
19428
19636
|
if (typeof exclusiveMinimum === "number") {
|
|
19429
19637
|
if (exclusiveMinimum >= minimum)
|
|
19430
|
-
delete
|
|
19638
|
+
delete json3.minimum;
|
|
19431
19639
|
else
|
|
19432
|
-
delete
|
|
19640
|
+
delete json3.exclusiveMinimum;
|
|
19433
19641
|
}
|
|
19434
19642
|
}
|
|
19435
19643
|
if (typeof exclusiveMaximum === "number")
|
|
19436
|
-
|
|
19644
|
+
json3.exclusiveMaximum = exclusiveMaximum;
|
|
19437
19645
|
if (typeof maximum === "number") {
|
|
19438
|
-
|
|
19646
|
+
json3.maximum = maximum;
|
|
19439
19647
|
if (typeof exclusiveMaximum === "number") {
|
|
19440
19648
|
if (exclusiveMaximum <= maximum)
|
|
19441
|
-
delete
|
|
19649
|
+
delete json3.maximum;
|
|
19442
19650
|
else
|
|
19443
|
-
delete
|
|
19651
|
+
delete json3.exclusiveMaximum;
|
|
19444
19652
|
}
|
|
19445
19653
|
}
|
|
19446
19654
|
if (typeof multipleOf === "number")
|
|
19447
|
-
|
|
19655
|
+
json3.multipleOf = multipleOf;
|
|
19448
19656
|
break;
|
|
19449
19657
|
}
|
|
19450
19658
|
case "boolean": {
|
|
19451
|
-
const
|
|
19452
|
-
|
|
19659
|
+
const json3 = _json;
|
|
19660
|
+
json3.type = "boolean";
|
|
19453
19661
|
break;
|
|
19454
19662
|
}
|
|
19455
19663
|
case "bigint": {
|
|
@@ -19497,23 +19705,23 @@ class JSONSchemaGenerator {
|
|
|
19497
19705
|
break;
|
|
19498
19706
|
}
|
|
19499
19707
|
case "array": {
|
|
19500
|
-
const
|
|
19708
|
+
const json3 = _json;
|
|
19501
19709
|
const { minimum, maximum } = schema._zod.bag;
|
|
19502
19710
|
if (typeof minimum === "number")
|
|
19503
|
-
|
|
19711
|
+
json3.minItems = minimum;
|
|
19504
19712
|
if (typeof maximum === "number")
|
|
19505
|
-
|
|
19506
|
-
|
|
19507
|
-
|
|
19713
|
+
json3.maxItems = maximum;
|
|
19714
|
+
json3.type = "array";
|
|
19715
|
+
json3.items = this.process(def.element, { ...params, path: [...params.path, "items"] });
|
|
19508
19716
|
break;
|
|
19509
19717
|
}
|
|
19510
19718
|
case "object": {
|
|
19511
|
-
const
|
|
19512
|
-
|
|
19513
|
-
|
|
19719
|
+
const json3 = _json;
|
|
19720
|
+
json3.type = "object";
|
|
19721
|
+
json3.properties = {};
|
|
19514
19722
|
const shape = def.shape;
|
|
19515
19723
|
for (const key in shape) {
|
|
19516
|
-
|
|
19724
|
+
json3.properties[key] = this.process(shape[key], {
|
|
19517
19725
|
...params,
|
|
19518
19726
|
path: [...params.path, "properties", key]
|
|
19519
19727
|
});
|
|
@@ -19528,15 +19736,15 @@ class JSONSchemaGenerator {
|
|
|
19528
19736
|
}
|
|
19529
19737
|
}));
|
|
19530
19738
|
if (requiredKeys.size > 0) {
|
|
19531
|
-
|
|
19739
|
+
json3.required = Array.from(requiredKeys);
|
|
19532
19740
|
}
|
|
19533
19741
|
if (def.catchall?._zod.def.type === "never") {
|
|
19534
|
-
|
|
19742
|
+
json3.additionalProperties = false;
|
|
19535
19743
|
} else if (!def.catchall) {
|
|
19536
19744
|
if (this.io === "output")
|
|
19537
|
-
|
|
19745
|
+
json3.additionalProperties = false;
|
|
19538
19746
|
} else if (def.catchall) {
|
|
19539
|
-
|
|
19747
|
+
json3.additionalProperties = this.process(def.catchall, {
|
|
19540
19748
|
...params,
|
|
19541
19749
|
path: [...params.path, "additionalProperties"]
|
|
19542
19750
|
});
|
|
@@ -19544,15 +19752,15 @@ class JSONSchemaGenerator {
|
|
|
19544
19752
|
break;
|
|
19545
19753
|
}
|
|
19546
19754
|
case "union": {
|
|
19547
|
-
const
|
|
19548
|
-
|
|
19755
|
+
const json3 = _json;
|
|
19756
|
+
json3.anyOf = def.options.map((x, i) => this.process(x, {
|
|
19549
19757
|
...params,
|
|
19550
19758
|
path: [...params.path, "anyOf", i]
|
|
19551
19759
|
}));
|
|
19552
19760
|
break;
|
|
19553
19761
|
}
|
|
19554
19762
|
case "intersection": {
|
|
19555
|
-
const
|
|
19763
|
+
const json3 = _json;
|
|
19556
19764
|
const a = this.process(def.left, {
|
|
19557
19765
|
...params,
|
|
19558
19766
|
path: [...params.path, "allOf", 0]
|
|
@@ -19566,17 +19774,17 @@ class JSONSchemaGenerator {
|
|
|
19566
19774
|
...isSimpleIntersection(a) ? a.allOf : [a],
|
|
19567
19775
|
...isSimpleIntersection(b) ? b.allOf : [b]
|
|
19568
19776
|
];
|
|
19569
|
-
|
|
19777
|
+
json3.allOf = allOf;
|
|
19570
19778
|
break;
|
|
19571
19779
|
}
|
|
19572
19780
|
case "tuple": {
|
|
19573
|
-
const
|
|
19574
|
-
|
|
19781
|
+
const json3 = _json;
|
|
19782
|
+
json3.type = "array";
|
|
19575
19783
|
const prefixItems = def.items.map((x, i) => this.process(x, { ...params, path: [...params.path, "prefixItems", i] }));
|
|
19576
19784
|
if (this.target === "draft-2020-12") {
|
|
19577
|
-
|
|
19785
|
+
json3.prefixItems = prefixItems;
|
|
19578
19786
|
} else {
|
|
19579
|
-
|
|
19787
|
+
json3.items = prefixItems;
|
|
19580
19788
|
}
|
|
19581
19789
|
if (def.rest) {
|
|
19582
19790
|
const rest = this.process(def.rest, {
|
|
@@ -19584,29 +19792,29 @@ class JSONSchemaGenerator {
|
|
|
19584
19792
|
path: [...params.path, "items"]
|
|
19585
19793
|
});
|
|
19586
19794
|
if (this.target === "draft-2020-12") {
|
|
19587
|
-
|
|
19795
|
+
json3.items = rest;
|
|
19588
19796
|
} else {
|
|
19589
|
-
|
|
19797
|
+
json3.additionalItems = rest;
|
|
19590
19798
|
}
|
|
19591
19799
|
}
|
|
19592
19800
|
if (def.rest) {
|
|
19593
|
-
|
|
19801
|
+
json3.items = this.process(def.rest, {
|
|
19594
19802
|
...params,
|
|
19595
19803
|
path: [...params.path, "items"]
|
|
19596
19804
|
});
|
|
19597
19805
|
}
|
|
19598
19806
|
const { minimum, maximum } = schema._zod.bag;
|
|
19599
19807
|
if (typeof minimum === "number")
|
|
19600
|
-
|
|
19808
|
+
json3.minItems = minimum;
|
|
19601
19809
|
if (typeof maximum === "number")
|
|
19602
|
-
|
|
19810
|
+
json3.maxItems = maximum;
|
|
19603
19811
|
break;
|
|
19604
19812
|
}
|
|
19605
19813
|
case "record": {
|
|
19606
|
-
const
|
|
19607
|
-
|
|
19608
|
-
|
|
19609
|
-
|
|
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, {
|
|
19610
19818
|
...params,
|
|
19611
19819
|
path: [...params.path, "additionalProperties"]
|
|
19612
19820
|
});
|
|
@@ -19625,17 +19833,17 @@ class JSONSchemaGenerator {
|
|
|
19625
19833
|
break;
|
|
19626
19834
|
}
|
|
19627
19835
|
case "enum": {
|
|
19628
|
-
const
|
|
19836
|
+
const json3 = _json;
|
|
19629
19837
|
const values = getEnumValues(def.entries);
|
|
19630
19838
|
if (values.every((v) => typeof v === "number"))
|
|
19631
|
-
|
|
19839
|
+
json3.type = "number";
|
|
19632
19840
|
if (values.every((v) => typeof v === "string"))
|
|
19633
|
-
|
|
19634
|
-
|
|
19841
|
+
json3.type = "string";
|
|
19842
|
+
json3.enum = values;
|
|
19635
19843
|
break;
|
|
19636
19844
|
}
|
|
19637
19845
|
case "literal": {
|
|
19638
|
-
const
|
|
19846
|
+
const json3 = _json;
|
|
19639
19847
|
const vals = [];
|
|
19640
19848
|
for (const val of def.values) {
|
|
19641
19849
|
if (val === undefined) {
|
|
@@ -19654,23 +19862,23 @@ class JSONSchemaGenerator {
|
|
|
19654
19862
|
}
|
|
19655
19863
|
if (vals.length === 0) {} else if (vals.length === 1) {
|
|
19656
19864
|
const val = vals[0];
|
|
19657
|
-
|
|
19658
|
-
|
|
19865
|
+
json3.type = val === null ? "null" : typeof val;
|
|
19866
|
+
json3.const = val;
|
|
19659
19867
|
} else {
|
|
19660
19868
|
if (vals.every((v) => typeof v === "number"))
|
|
19661
|
-
|
|
19869
|
+
json3.type = "number";
|
|
19662
19870
|
if (vals.every((v) => typeof v === "string"))
|
|
19663
|
-
|
|
19871
|
+
json3.type = "string";
|
|
19664
19872
|
if (vals.every((v) => typeof v === "boolean"))
|
|
19665
|
-
|
|
19873
|
+
json3.type = "string";
|
|
19666
19874
|
if (vals.every((v) => v === null))
|
|
19667
|
-
|
|
19668
|
-
|
|
19875
|
+
json3.type = "null";
|
|
19876
|
+
json3.enum = vals;
|
|
19669
19877
|
}
|
|
19670
19878
|
break;
|
|
19671
19879
|
}
|
|
19672
19880
|
case "file": {
|
|
19673
|
-
const
|
|
19881
|
+
const json3 = _json;
|
|
19674
19882
|
const file = {
|
|
19675
19883
|
type: "string",
|
|
19676
19884
|
format: "binary",
|
|
@@ -19684,15 +19892,15 @@ class JSONSchemaGenerator {
|
|
|
19684
19892
|
if (mime) {
|
|
19685
19893
|
if (mime.length === 1) {
|
|
19686
19894
|
file.contentMediaType = mime[0];
|
|
19687
|
-
Object.assign(
|
|
19895
|
+
Object.assign(json3, file);
|
|
19688
19896
|
} else {
|
|
19689
|
-
|
|
19897
|
+
json3.anyOf = mime.map((m) => {
|
|
19690
19898
|
const mFile = { ...file, contentMediaType: m };
|
|
19691
19899
|
return mFile;
|
|
19692
19900
|
});
|
|
19693
19901
|
}
|
|
19694
19902
|
} else {
|
|
19695
|
-
Object.assign(
|
|
19903
|
+
Object.assign(json3, file);
|
|
19696
19904
|
}
|
|
19697
19905
|
break;
|
|
19698
19906
|
}
|
|
@@ -19713,8 +19921,8 @@ class JSONSchemaGenerator {
|
|
|
19713
19921
|
break;
|
|
19714
19922
|
}
|
|
19715
19923
|
case "success": {
|
|
19716
|
-
const
|
|
19717
|
-
|
|
19924
|
+
const json3 = _json;
|
|
19925
|
+
json3.type = "boolean";
|
|
19718
19926
|
break;
|
|
19719
19927
|
}
|
|
19720
19928
|
case "default": {
|
|
@@ -19749,12 +19957,12 @@ class JSONSchemaGenerator {
|
|
|
19749
19957
|
break;
|
|
19750
19958
|
}
|
|
19751
19959
|
case "template_literal": {
|
|
19752
|
-
const
|
|
19960
|
+
const json3 = _json;
|
|
19753
19961
|
const pattern = schema._zod.pattern;
|
|
19754
19962
|
if (!pattern)
|
|
19755
19963
|
throw new Error("Pattern not found in template literal");
|
|
19756
|
-
|
|
19757
|
-
|
|
19964
|
+
json3.type = "string";
|
|
19965
|
+
json3.pattern = pattern.source;
|
|
19758
19966
|
break;
|
|
19759
19967
|
}
|
|
19760
19968
|
case "pipe": {
|
|
@@ -20847,7 +21055,7 @@ function _instanceof(cls, params = {
|
|
|
20847
21055
|
inst._zod.bag.Class = cls;
|
|
20848
21056
|
return inst;
|
|
20849
21057
|
}
|
|
20850
|
-
function
|
|
21058
|
+
function json3(params) {
|
|
20851
21059
|
const jsonSchema = lazy(() => {
|
|
20852
21060
|
return union([string2(params), number2(), boolean2(), _null3(), array(jsonSchema), record(string2(), jsonSchema)]);
|
|
20853
21061
|
});
|
|
@@ -21534,7 +21742,7 @@ __export(exports_external2, {
|
|
|
21534
21742
|
ksuid: () => ksuid2,
|
|
21535
21743
|
keyof: () => keyof,
|
|
21536
21744
|
jwt: () => jwt,
|
|
21537
|
-
json: () =>
|
|
21745
|
+
json: () => json3,
|
|
21538
21746
|
iso: () => exports_iso,
|
|
21539
21747
|
ipv6: () => ipv62,
|
|
21540
21748
|
ipv4: () => ipv42,
|
|
@@ -23394,7 +23602,7 @@ function createProviderToolFactoryWithOutputSchema({
|
|
|
23394
23602
|
supportsDeferredResults
|
|
23395
23603
|
});
|
|
23396
23604
|
}
|
|
23397
|
-
async function
|
|
23605
|
+
async function resolve4(value) {
|
|
23398
23606
|
if (typeof value === "function") {
|
|
23399
23607
|
value = value();
|
|
23400
23608
|
}
|
|
@@ -26216,11 +26424,11 @@ var VERSION2 = "3.0.82", anthropicErrorDataSchema, anthropicFailedResponseHandle
|
|
|
26216
26424
|
betas,
|
|
26217
26425
|
headers
|
|
26218
26426
|
}) {
|
|
26219
|
-
return combineHeaders(await
|
|
26427
|
+
return combineHeaders(await resolve4(this.config.headers), headers, betas.size > 0 ? { "anthropic-beta": Array.from(betas).join(",") } : {});
|
|
26220
26428
|
}
|
|
26221
26429
|
async getBetasFromHeaders(requestHeaders) {
|
|
26222
26430
|
var _a16, _b16;
|
|
26223
|
-
const configHeaders = await
|
|
26431
|
+
const configHeaders = await resolve4(this.config.headers);
|
|
26224
26432
|
const configBetaHeader = (_a16 = configHeaders["anthropic-beta"]) != null ? _a16 : "";
|
|
26225
26433
|
const requestBetaHeader = (_b16 = requestHeaders == null ? undefined : requestHeaders["anthropic-beta"]) != null ? _b16 : "";
|
|
26226
26434
|
return new Set([
|
|
@@ -36890,24 +37098,24 @@ var require_oauth = __commonJS((exports, module) => {
|
|
|
36890
37098
|
});
|
|
36891
37099
|
}
|
|
36892
37100
|
async function processTokenResponse(response) {
|
|
36893
|
-
const
|
|
37101
|
+
const json4 = await response.json();
|
|
36894
37102
|
if (!response.ok) {
|
|
36895
|
-
const errorMsg = typeof
|
|
37103
|
+
const errorMsg = typeof json4 === "object" && json4 && "error" in json4 ? String(json4.error) : "Token refresh failed";
|
|
36896
37104
|
return [new Error(errorMsg)];
|
|
36897
37105
|
}
|
|
36898
|
-
if (typeof
|
|
37106
|
+
if (typeof json4 !== "object" || json4 === null) {
|
|
36899
37107
|
return [new Error("Invalid token response")];
|
|
36900
37108
|
}
|
|
36901
|
-
if (typeof
|
|
37109
|
+
if (typeof json4.access_token !== "string") {
|
|
36902
37110
|
return [new Error("Missing access_token in response")];
|
|
36903
37111
|
}
|
|
36904
|
-
if (
|
|
37112
|
+
if (json4.token_type !== "Bearer") {
|
|
36905
37113
|
return [new Error("Invalid token_type in response")];
|
|
36906
37114
|
}
|
|
36907
|
-
if (typeof
|
|
37115
|
+
if (typeof json4.expires_in !== "number") {
|
|
36908
37116
|
return [new Error("Missing expires_in in response")];
|
|
36909
37117
|
}
|
|
36910
|
-
return [null,
|
|
37118
|
+
return [null, json4];
|
|
36911
37119
|
}
|
|
36912
37120
|
});
|
|
36913
37121
|
|
|
@@ -37644,7 +37852,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37644
37852
|
try {
|
|
37645
37853
|
const { value } = await getFromApi({
|
|
37646
37854
|
url: `${this.config.baseURL}/config`,
|
|
37647
|
-
headers: await
|
|
37855
|
+
headers: await resolve4(this.config.headers()),
|
|
37648
37856
|
successfulResponseHandler: createJsonResponseHandler(gatewayAvailableModelsResponseSchema),
|
|
37649
37857
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
37650
37858
|
errorSchema: exports_external2.any(),
|
|
@@ -37662,7 +37870,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37662
37870
|
const baseUrl = new URL(this.config.baseURL);
|
|
37663
37871
|
const { value } = await getFromApi({
|
|
37664
37872
|
url: `${baseUrl.origin}/v1/credits`,
|
|
37665
|
-
headers: await
|
|
37873
|
+
headers: await resolve4(this.config.headers()),
|
|
37666
37874
|
successfulResponseHandler: createJsonResponseHandler(gatewayCreditsResponseSchema),
|
|
37667
37875
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
37668
37876
|
errorSchema: exports_external2.any(),
|
|
@@ -37708,7 +37916,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37708
37916
|
}
|
|
37709
37917
|
const { value } = await getFromApi({
|
|
37710
37918
|
url: `${baseUrl.origin}/v1/report?${searchParams.toString()}`,
|
|
37711
|
-
headers: await
|
|
37919
|
+
headers: await resolve4(this.config.headers()),
|
|
37712
37920
|
successfulResponseHandler: createJsonResponseHandler(gatewaySpendReportResponseSchema),
|
|
37713
37921
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
37714
37922
|
errorSchema: exports_external2.any(),
|
|
@@ -37730,7 +37938,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37730
37938
|
const baseUrl = new URL(this.config.baseURL);
|
|
37731
37939
|
const { value } = await getFromApi({
|
|
37732
37940
|
url: `${baseUrl.origin}/v1/generation?id=${encodeURIComponent(params.id)}`,
|
|
37733
|
-
headers: await
|
|
37941
|
+
headers: await resolve4(this.config.headers()),
|
|
37734
37942
|
successfulResponseHandler: createJsonResponseHandler(gatewayGenerationInfoResponseSchema),
|
|
37735
37943
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
37736
37944
|
errorSchema: exports_external2.any(),
|
|
@@ -37763,7 +37971,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37763
37971
|
async doGenerate(options) {
|
|
37764
37972
|
const { args, warnings } = await this.getArgs(options);
|
|
37765
37973
|
const { abortSignal } = options;
|
|
37766
|
-
const resolvedHeaders = await
|
|
37974
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
37767
37975
|
try {
|
|
37768
37976
|
const {
|
|
37769
37977
|
responseHeaders,
|
|
@@ -37771,7 +37979,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37771
37979
|
rawValue: rawResponse
|
|
37772
37980
|
} = await postJsonToApi({
|
|
37773
37981
|
url: this.getUrl(),
|
|
37774
|
-
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)),
|
|
37775
37983
|
body: args,
|
|
37776
37984
|
successfulResponseHandler: createJsonResponseHandler(exports_external2.any()),
|
|
37777
37985
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
@@ -37794,11 +38002,11 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37794
38002
|
async doStream(options) {
|
|
37795
38003
|
const { args, warnings } = await this.getArgs(options);
|
|
37796
38004
|
const { abortSignal } = options;
|
|
37797
|
-
const resolvedHeaders = await
|
|
38005
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
37798
38006
|
try {
|
|
37799
38007
|
const { value: response, responseHeaders } = await postJsonToApi({
|
|
37800
38008
|
url: this.getUrl(),
|
|
37801
|
-
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)),
|
|
37802
38010
|
body: args,
|
|
37803
38011
|
successfulResponseHandler: createEventSourceResponseHandler(exports_external2.any()),
|
|
37804
38012
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
@@ -37883,7 +38091,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37883
38091
|
providerOptions
|
|
37884
38092
|
}) {
|
|
37885
38093
|
var _a102;
|
|
37886
|
-
const resolvedHeaders = await
|
|
38094
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
37887
38095
|
try {
|
|
37888
38096
|
const {
|
|
37889
38097
|
responseHeaders,
|
|
@@ -37891,7 +38099,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37891
38099
|
rawValue
|
|
37892
38100
|
} = await postJsonToApi({
|
|
37893
38101
|
url: this.getUrl(),
|
|
37894
|
-
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await
|
|
38102
|
+
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders)),
|
|
37895
38103
|
body: {
|
|
37896
38104
|
values,
|
|
37897
38105
|
...providerOptions ? { providerOptions } : {}
|
|
@@ -37947,7 +38155,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37947
38155
|
abortSignal
|
|
37948
38156
|
}) {
|
|
37949
38157
|
var _a102, _b102, _c, _d;
|
|
37950
|
-
const resolvedHeaders = await
|
|
38158
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
37951
38159
|
try {
|
|
37952
38160
|
const {
|
|
37953
38161
|
responseHeaders,
|
|
@@ -37955,7 +38163,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
37955
38163
|
rawValue
|
|
37956
38164
|
} = await postJsonToApi({
|
|
37957
38165
|
url: this.getUrl(),
|
|
37958
|
-
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await
|
|
38166
|
+
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders)),
|
|
37959
38167
|
body: {
|
|
37960
38168
|
prompt,
|
|
37961
38169
|
n,
|
|
@@ -38030,11 +38238,11 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
38030
38238
|
abortSignal
|
|
38031
38239
|
}) {
|
|
38032
38240
|
var _a102;
|
|
38033
|
-
const resolvedHeaders = await
|
|
38241
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
38034
38242
|
try {
|
|
38035
38243
|
const { responseHeaders, value: responseBody } = await postJsonToApi({
|
|
38036
38244
|
url: this.getUrl(),
|
|
38037
|
-
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" }),
|
|
38038
38246
|
body: {
|
|
38039
38247
|
prompt,
|
|
38040
38248
|
n,
|
|
@@ -38157,7 +38365,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
38157
38365
|
abortSignal,
|
|
38158
38366
|
providerOptions
|
|
38159
38367
|
}) {
|
|
38160
|
-
const resolvedHeaders = await
|
|
38368
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
38161
38369
|
try {
|
|
38162
38370
|
const {
|
|
38163
38371
|
responseHeaders,
|
|
@@ -38165,7 +38373,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
38165
38373
|
rawValue
|
|
38166
38374
|
} = await postJsonToApi({
|
|
38167
38375
|
url: this.getUrl(),
|
|
38168
|
-
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await
|
|
38376
|
+
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders)),
|
|
38169
38377
|
body: {
|
|
38170
38378
|
documents,
|
|
38171
38379
|
query,
|
|
@@ -47769,7 +47977,7 @@ var import_api2, import_api3, __defProp2, __export2 = (target, all) => {
|
|
|
47769
47977
|
const schema = asSchema(inputSchema);
|
|
47770
47978
|
return {
|
|
47771
47979
|
name: "object",
|
|
47772
|
-
responseFormat:
|
|
47980
|
+
responseFormat: resolve4(schema.jsonSchema).then((jsonSchema2) => ({
|
|
47773
47981
|
type: "json",
|
|
47774
47982
|
schema: jsonSchema2,
|
|
47775
47983
|
...name21 != null && { name: name21 },
|
|
@@ -47830,7 +48038,7 @@ var import_api2, import_api3, __defProp2, __export2 = (target, all) => {
|
|
|
47830
48038
|
const elementSchema = asSchema(inputElementSchema);
|
|
47831
48039
|
return {
|
|
47832
48040
|
name: "array",
|
|
47833
|
-
responseFormat:
|
|
48041
|
+
responseFormat: resolve4(elementSchema.jsonSchema).then((jsonSchema2) => {
|
|
47834
48042
|
const { $schema, ...itemSchema } = jsonSchema2;
|
|
47835
48043
|
return {
|
|
47836
48044
|
type: "json",
|
|
@@ -48007,7 +48215,7 @@ var import_api2, import_api3, __defProp2, __export2 = (target, all) => {
|
|
|
48007
48215
|
return;
|
|
48008
48216
|
}
|
|
48009
48217
|
};
|
|
48010
|
-
},
|
|
48218
|
+
}, json4 = ({
|
|
48011
48219
|
name: name21,
|
|
48012
48220
|
description
|
|
48013
48221
|
} = {}) => {
|
|
@@ -50743,9 +50951,9 @@ var import_api2, import_api3, __defProp2, __export2 = (target, all) => {
|
|
|
50743
50951
|
...options
|
|
50744
50952
|
}) {
|
|
50745
50953
|
var _a21, _b16, _c, _d, _e;
|
|
50746
|
-
const resolvedBody = await
|
|
50747
|
-
const resolvedHeaders = await
|
|
50748
|
-
const resolvedCredentials = await
|
|
50954
|
+
const resolvedBody = await resolve4(this.body);
|
|
50955
|
+
const resolvedHeaders = await resolve4(this.headers);
|
|
50956
|
+
const resolvedCredentials = await resolve4(this.credentials);
|
|
50749
50957
|
const baseHeaders = {
|
|
50750
50958
|
...normalizeHeaders(resolvedHeaders),
|
|
50751
50959
|
...normalizeHeaders(options.headers)
|
|
@@ -50793,9 +51001,9 @@ var import_api2, import_api3, __defProp2, __export2 = (target, all) => {
|
|
|
50793
51001
|
}
|
|
50794
51002
|
async reconnectToStream(options) {
|
|
50795
51003
|
var _a21, _b16, _c, _d, _e;
|
|
50796
|
-
const resolvedBody = await
|
|
50797
|
-
const resolvedHeaders = await
|
|
50798
|
-
const resolvedCredentials = await
|
|
51004
|
+
const resolvedBody = await resolve4(this.body);
|
|
51005
|
+
const resolvedHeaders = await resolve4(this.headers);
|
|
51006
|
+
const resolvedCredentials = await resolve4(this.credentials);
|
|
50799
51007
|
const baseHeaders = {
|
|
50800
51008
|
...normalizeHeaders(resolvedHeaders),
|
|
50801
51009
|
...normalizeHeaders(options.headers)
|
|
@@ -52075,7 +52283,7 @@ var init_dist8 = __esm(() => {
|
|
|
52075
52283
|
__export2(output_exports, {
|
|
52076
52284
|
array: () => array2,
|
|
52077
52285
|
choice: () => choice,
|
|
52078
|
-
json: () =>
|
|
52286
|
+
json: () => json4,
|
|
52079
52287
|
object: () => object2,
|
|
52080
52288
|
text: () => text
|
|
52081
52289
|
});
|
|
@@ -52738,7 +52946,7 @@ var init_openapi = __esm(() => {
|
|
|
52738
52946
|
// src/server/index.ts
|
|
52739
52947
|
import { existsSync as existsSync5 } from "fs";
|
|
52740
52948
|
import { createRequire } from "module";
|
|
52741
|
-
import { join as
|
|
52949
|
+
import { join as join7, resolve as resolve5, sep } from "path";
|
|
52742
52950
|
|
|
52743
52951
|
// src/lib/config.ts
|
|
52744
52952
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, unlinkSync, cpSync } from "fs";
|
|
@@ -54828,6 +55036,9 @@ addRoute("PATCH", "/api/memories/:id", async (req, _url, params) => {
|
|
|
54828
55036
|
if (!body) {
|
|
54829
55037
|
return errorResponse("Invalid JSON body", 400);
|
|
54830
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
|
+
}
|
|
54831
55042
|
const patchViolation = validateMemoryEnums(body);
|
|
54832
55043
|
if (patchViolation) {
|
|
54833
55044
|
return errorResponse(formatEnumViolation(patchViolation), 400, {
|
|
@@ -55710,6 +55921,10 @@ addRoute("POST", "/api/memories/search", async (req) => {
|
|
|
55710
55921
|
filter.category = body["category"];
|
|
55711
55922
|
if (body["tags"])
|
|
55712
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"];
|
|
55713
55928
|
if (body["session_id"])
|
|
55714
55929
|
filter.session_id = body["session_id"];
|
|
55715
55930
|
if (body["namespace"])
|
|
@@ -56175,6 +56390,701 @@ ${lines.join(`
|
|
|
56175
56390
|
return json({ context, memories_count: lines.length });
|
|
56176
56391
|
});
|
|
56177
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
|
+
|
|
56178
57088
|
// src/db/agents.ts
|
|
56179
57089
|
init_types();
|
|
56180
57090
|
init_database();
|
|
@@ -56596,7 +57506,26 @@ addRoute("POST", "/api/locks/clean", () => {
|
|
|
56596
57506
|
// src/db/projects.ts
|
|
56597
57507
|
init_database();
|
|
56598
57508
|
init_api_mode();
|
|
56599
|
-
|
|
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) {
|
|
56600
57529
|
return {
|
|
56601
57530
|
id: row["id"],
|
|
56602
57531
|
name: row["name"],
|
|
@@ -56607,6 +57536,229 @@ function parseProjectRow(row) {
|
|
|
56607
57536
|
updated_at: row["updated_at"]
|
|
56608
57537
|
};
|
|
56609
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
|
+
}
|
|
56610
57762
|
function registerProject(name, path, description, memoryPrefix, db) {
|
|
56611
57763
|
if (!db && isApiMode()) {
|
|
56612
57764
|
const { data } = apiJson("POST", "/projects", {
|
|
@@ -56626,7 +57778,7 @@ function registerProject(name, path, description, memoryPrefix, db) {
|
|
|
56626
57778
|
timestamp,
|
|
56627
57779
|
existingId
|
|
56628
57780
|
]);
|
|
56629
|
-
return
|
|
57781
|
+
return parseProjectRow2(existing);
|
|
56630
57782
|
}
|
|
56631
57783
|
const id = uuid();
|
|
56632
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]);
|
|
@@ -56642,13 +57794,13 @@ function getProject(idOrPath, db) {
|
|
|
56642
57794
|
const d = db || getDatabase();
|
|
56643
57795
|
let row = d.query("SELECT * FROM projects WHERE id = ?").get(idOrPath);
|
|
56644
57796
|
if (row)
|
|
56645
|
-
return
|
|
57797
|
+
return parseProjectRow2(row);
|
|
56646
57798
|
row = d.query("SELECT * FROM projects WHERE path = ?").get(idOrPath);
|
|
56647
57799
|
if (row)
|
|
56648
|
-
return
|
|
57800
|
+
return parseProjectRow2(row);
|
|
56649
57801
|
row = d.query("SELECT * FROM projects WHERE LOWER(name) = ?").get(idOrPath.toLowerCase());
|
|
56650
57802
|
if (row)
|
|
56651
|
-
return
|
|
57803
|
+
return parseProjectRow2(row);
|
|
56652
57804
|
return null;
|
|
56653
57805
|
}
|
|
56654
57806
|
function listProjects(db) {
|
|
@@ -56658,7 +57810,184 @@ function listProjects(db) {
|
|
|
56658
57810
|
}
|
|
56659
57811
|
const d = db || getDatabase();
|
|
56660
57812
|
const rows = d.query("SELECT * FROM projects ORDER BY updated_at DESC").all();
|
|
56661
|
-
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;
|
|
56662
57991
|
}
|
|
56663
57992
|
|
|
56664
57993
|
// src/server/routes/projects.ts
|
|
@@ -56687,6 +58016,54 @@ addRoute("GET", "/api/projects/:id", (_req, _url, params) => {
|
|
|
56687
58016
|
return errorResponse("Project not found", 404);
|
|
56688
58017
|
return json(project);
|
|
56689
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
|
+
});
|
|
56690
58067
|
addRoute("GET", "/api/projects/:id/agents", (_req, _url, params) => {
|
|
56691
58068
|
const project = getProject(params["id"]);
|
|
56692
58069
|
if (!project)
|
|
@@ -56695,6 +58072,1064 @@ addRoute("GET", "/api/projects/:id/agents", (_req, _url, params) => {
|
|
|
56695
58072
|
return json({ agents, count: agents.length });
|
|
56696
58073
|
});
|
|
56697
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
|
+
|
|
56698
59133
|
// src/server/routes/entities.ts
|
|
56699
59134
|
init_entities();
|
|
56700
59135
|
init_relations();
|
|
@@ -57737,12 +60172,12 @@ function executeAddTag(proposal, d) {
|
|
|
57737
60172
|
return { old_tags: rollback };
|
|
57738
60173
|
}
|
|
57739
60174
|
function executeMerge(proposal, d) {
|
|
57740
|
-
const
|
|
57741
|
-
if (!
|
|
60175
|
+
const targetId2 = proposal.target_memory_id;
|
|
60176
|
+
if (!targetId2)
|
|
57742
60177
|
throw new Error("merge proposal missing target_memory_id");
|
|
57743
|
-
const target = getMemory(
|
|
60178
|
+
const target = getMemory(targetId2, d);
|
|
57744
60179
|
if (!target)
|
|
57745
|
-
throw new Error(`Target memory ${
|
|
60180
|
+
throw new Error(`Target memory ${targetId2} not found`);
|
|
57746
60181
|
const rollback = {
|
|
57747
60182
|
target_old_value: target.value,
|
|
57748
60183
|
target_old_version: target.version,
|
|
@@ -57750,7 +60185,7 @@ function executeMerge(proposal, d) {
|
|
|
57750
60185
|
};
|
|
57751
60186
|
const sourceValues = [];
|
|
57752
60187
|
for (const memId of proposal.memory_ids) {
|
|
57753
|
-
if (memId ===
|
|
60188
|
+
if (memId === targetId2)
|
|
57754
60189
|
continue;
|
|
57755
60190
|
const mem = getMemory(memId, d);
|
|
57756
60191
|
if (!mem)
|
|
@@ -57761,9 +60196,9 @@ function executeMerge(proposal, d) {
|
|
|
57761
60196
|
const mergedValue = proposal.proposed_changes["merged_value"] ?? [target.value, ...sourceValues].join(`
|
|
57762
60197
|
---
|
|
57763
60198
|
`);
|
|
57764
|
-
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]);
|
|
57765
60200
|
for (const memId of proposal.memory_ids) {
|
|
57766
|
-
if (memId ===
|
|
60201
|
+
if (memId === targetId2)
|
|
57767
60202
|
continue;
|
|
57768
60203
|
d.run("UPDATE memories SET status = 'archived', updated_at = ? WHERE id = ?", [now(), memId]);
|
|
57769
60204
|
}
|
|
@@ -57853,9 +60288,9 @@ function rollbackProposal(proposal, d) {
|
|
|
57853
60288
|
const targetOldValue = rb["target_old_value"];
|
|
57854
60289
|
const targetOldVersion = rb["target_old_version"];
|
|
57855
60290
|
const archivedMemories = rb["archived_memories"];
|
|
57856
|
-
const
|
|
57857
|
-
if (
|
|
57858
|
-
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]);
|
|
57859
60294
|
}
|
|
57860
60295
|
if (archivedMemories) {
|
|
57861
60296
|
for (const [memId, status] of Object.entries(archivedMemories)) {
|
|
@@ -59453,18 +61888,18 @@ function pkgVersion() {
|
|
|
59453
61888
|
}
|
|
59454
61889
|
async function findFreePort(start) {
|
|
59455
61890
|
const net = await import("net");
|
|
59456
|
-
return new Promise((
|
|
61891
|
+
return new Promise((resolve6) => {
|
|
59457
61892
|
const server = net.createServer();
|
|
59458
61893
|
server.unref();
|
|
59459
61894
|
server.on("error", () => {
|
|
59460
|
-
|
|
61895
|
+
resolve6(findFreePort(start + 1));
|
|
59461
61896
|
});
|
|
59462
61897
|
server.listen(start, () => {
|
|
59463
61898
|
const address = server.address();
|
|
59464
61899
|
if (address && typeof address === "object") {
|
|
59465
|
-
server.close(() =>
|
|
61900
|
+
server.close(() => resolve6(address.port));
|
|
59466
61901
|
} else {
|
|
59467
|
-
|
|
61902
|
+
resolve6(start);
|
|
59468
61903
|
}
|
|
59469
61904
|
});
|
|
59470
61905
|
});
|
|
@@ -59639,15 +62074,15 @@ function startServer(port) {
|
|
|
59639
62074
|
const dashDir = resolveDashboardDir();
|
|
59640
62075
|
if (existsSync5(dashDir) && (req.method === "GET" || req.method === "HEAD")) {
|
|
59641
62076
|
if (pathname !== "/") {
|
|
59642
|
-
const resolvedDash =
|
|
59643
|
-
const requestedPath =
|
|
62077
|
+
const resolvedDash = resolve5(dashDir) + sep;
|
|
62078
|
+
const requestedPath = resolve5(join7(dashDir, pathname));
|
|
59644
62079
|
if (requestedPath.startsWith(resolvedDash)) {
|
|
59645
62080
|
const staticRes = serveStaticFile(requestedPath);
|
|
59646
62081
|
if (staticRes)
|
|
59647
62082
|
return staticRes;
|
|
59648
62083
|
}
|
|
59649
62084
|
}
|
|
59650
|
-
const indexRes = serveStaticFile(
|
|
62085
|
+
const indexRes = serveStaticFile(join7(dashDir, "index.html"));
|
|
59651
62086
|
if (indexRes)
|
|
59652
62087
|
return indexRes;
|
|
59653
62088
|
}
|