@hasna/mementos 0.14.74 → 0.14.78

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