@remnic/core 9.3.704 → 9.3.705

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 (161) hide show
  1. package/dist/access-boundary.d.ts +1 -1
  2. package/dist/access-boundary.js +25 -25
  3. package/dist/access-cli.js +53 -52
  4. package/dist/access-cli.js.map +1 -1
  5. package/dist/access-http.js +30 -29
  6. package/dist/access-mcp.js +29 -28
  7. package/dist/access-operations-batch.d.ts +2 -0
  8. package/dist/access-operations-batch.js +120 -0
  9. package/dist/access-operations.d.ts +1 -0
  10. package/dist/access-operations.js +28 -27
  11. package/dist/access-schema.js +3 -3
  12. package/dist/access-service.js +24 -24
  13. package/dist/access-surface-catalog.js +57 -54
  14. package/dist/access-surface-catalog.js.map +1 -1
  15. package/dist/{auto-sync-3AAP25FV.js → auto-sync-24NNS3GU.js} +3 -3
  16. package/dist/briefing.js +4 -4
  17. package/dist/{capsule-crypto-GWVG7LGC.js → capsule-crypto-7FJQINUR.js} +2 -2
  18. package/dist/capsule-crypto-7FJQINUR.js.map +1 -0
  19. package/dist/causal-behavior.js +5 -5
  20. package/dist/causal-chain.js +3 -3
  21. package/dist/causal-consolidation.js +8 -8
  22. package/dist/causal-retrieval.js +3 -3
  23. package/dist/causal-trajectory-graph.js +2 -2
  24. package/dist/causal-trajectory.js +1 -1
  25. package/dist/{chunk-OWHERGF2.js → chunk-2NLLXCJG.js} +2 -2
  26. package/dist/{chunk-PJ5MDXOF.js → chunk-2SYPOX45.js} +10 -10
  27. package/dist/{chunk-7G4KPGR3.js → chunk-47LPHBIP.js} +2 -2
  28. package/dist/{chunk-RYDT7LAI.js → chunk-5DTWZ66X.js} +2 -2
  29. package/dist/chunk-5DTWZ66X.js.map +1 -0
  30. package/dist/{chunk-OCFNG4LB.js → chunk-6GJ2WQRK.js} +2 -2
  31. package/dist/{chunk-4NFVPDIL.js → chunk-74MYJSKB.js} +5 -5
  32. package/dist/{chunk-PKMQK2WY.js → chunk-773DPI6T.js} +4 -4
  33. package/dist/{chunk-DE2OPIQD.js → chunk-7ARKLLCV.js} +6 -6
  34. package/dist/{chunk-3EHCV6YV.js → chunk-7L54YZUI.js} +63 -63
  35. package/dist/{chunk-AGNBY3VG.js → chunk-APJQ6UEA.js} +4 -4
  36. package/dist/{chunk-UNNDFYUX.js → chunk-ARV3AUOM.js} +5 -5
  37. package/dist/{chunk-S24J5LLQ.js → chunk-BKBSE3BU.js} +3 -3
  38. package/dist/{chunk-AE3C4PY6.js → chunk-BMKGG7GC.js} +2 -2
  39. package/dist/{chunk-NMW3T6T6.js → chunk-C2EHWNP2.js} +9 -9
  40. package/dist/{chunk-VKJHM6PL.js → chunk-DGTDRIUO.js} +5 -5
  41. package/dist/chunk-GI3MTYDU.js +1303 -0
  42. package/dist/chunk-GI3MTYDU.js.map +1 -0
  43. package/dist/{chunk-25FHSOEB.js → chunk-GT4VBKM4.js} +59 -5
  44. package/dist/chunk-GT4VBKM4.js.map +1 -0
  45. package/dist/{chunk-OOEVMJK4.js → chunk-HEHPVBWA.js} +6 -6
  46. package/dist/{chunk-YOI3ELXF.js → chunk-IF362TF6.js} +2 -2
  47. package/dist/{chunk-ABCGDW3A.js → chunk-IFR23TC5.js} +4 -4
  48. package/dist/{chunk-ZXFNSKPY.js → chunk-JHAYR2JY.js} +32 -32
  49. package/dist/{chunk-CTY6QSLZ.js → chunk-JZCKDTAS.js} +4 -4
  50. package/dist/{chunk-CTY6QSLZ.js.map → chunk-JZCKDTAS.js.map} +1 -1
  51. package/dist/{chunk-6AFVIOVZ.js → chunk-K22IJO4R.js} +2 -2
  52. package/dist/{chunk-QV5WFNZ2.js → chunk-KFU2SKDD.js} +2 -2
  53. package/dist/{chunk-5W7CTZ2E.js → chunk-KQAFEZQX.js} +4 -4
  54. package/dist/{chunk-TUPNAHHQ.js → chunk-KZMMAVR2.js} +2 -2
  55. package/dist/{chunk-6Q7JFY4P.js → chunk-LY4FJLPT.js} +2 -2
  56. package/dist/{chunk-XXDOJ5NL.js → chunk-M5MWSG4S.js} +7 -7
  57. package/dist/{chunk-YLB7NMLD.js → chunk-MNH2OODZ.js} +2 -2
  58. package/dist/{chunk-ORXVO6TH.js → chunk-NFGJBEPT.js} +6 -6
  59. package/dist/{chunk-QDIVMS25.js → chunk-NUWZFSKC.js} +2 -2
  60. package/dist/{chunk-6GHWF4QH.js → chunk-OM6ETLLH.js} +2 -2
  61. package/dist/{chunk-53EEQYE5.js → chunk-OMSTU233.js} +4 -4
  62. package/dist/{chunk-R64MNFPV.js → chunk-S3WYRPSA.js} +59 -59
  63. package/dist/{chunk-UOIRJ45Z.js → chunk-S5APSA3U.js} +2 -2
  64. package/dist/{chunk-WAHI7SPK.js → chunk-SY6WG6DB.js} +2 -2
  65. package/dist/{chunk-K7QRDDSR.js → chunk-ULVM34NP.js} +5 -5
  66. package/dist/{chunk-UVYI6VIX.js → chunk-X7Y7WX73.js} +1 -1
  67. package/dist/{chunk-QHNJVWF6.js → chunk-Y4PMATX2.js} +6 -6
  68. package/dist/{chunk-KF74X62T.js → chunk-YGKUAX2B.js} +4 -4
  69. package/dist/{chunk-YN4ZT4CW.js → chunk-YI7RZO7Y.js} +1 -1
  70. package/dist/cli.js +53 -52
  71. package/dist/compounding/engine.js +4 -4
  72. package/dist/connectors/codex-materialize-runner.js +4 -4
  73. package/dist/connectors/index.js +4 -4
  74. package/dist/entity-retrieval.js +4 -4
  75. package/dist/extraction.js +2 -2
  76. package/dist/{graph-edge-decay-D7OESCBR.js → graph-edge-decay-JLOF3YW4.js} +3 -3
  77. package/dist/graph-snapshot.js +3 -3
  78. package/dist/graph.js +2 -2
  79. package/dist/index.js +95 -94
  80. package/dist/index.js.map +1 -1
  81. package/dist/lcm/engine.js +2 -2
  82. package/dist/lcm/index.js +4 -4
  83. package/dist/maintenance/memory-governance.js +5 -5
  84. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +4 -4
  85. package/dist/maintenance/rebuild-memory-projection.js +6 -6
  86. package/dist/namespaces/migrate.js +8 -8
  87. package/dist/namespaces/search.js +3 -3
  88. package/dist/namespaces/storage.js +4 -4
  89. package/dist/operator-toolkit.js +15 -15
  90. package/dist/orchestration/maintenance.js +6 -6
  91. package/dist/orchestrator.js +40 -40
  92. package/dist/resume-bundles.js +1 -1
  93. package/dist/search/factory.js +2 -2
  94. package/dist/search/index.js +4 -4
  95. package/dist/semantic-consolidation.js +6 -6
  96. package/dist/semantic-rule-promotion.js +4 -4
  97. package/dist/semantic-rule-verifier.js +5 -5
  98. package/dist/storage.js +3 -3
  99. package/dist/transfer/backup.js +2 -2
  100. package/dist/transfer/capsule-export.js +2 -2
  101. package/dist/transfer/capsule-import.js +2 -2
  102. package/dist/transfer/import-sqlite.js +2 -2
  103. package/dist/verified-recall.js +5 -5
  104. package/package.json +2 -2
  105. package/src/access-boundary.ts +102 -1
  106. package/src/access-http.ts +54 -0
  107. package/src/access-operations-batch.ts +1468 -0
  108. package/src/access-operations.ts +4 -0
  109. package/src/access-surface-catalog.test.ts +17 -1
  110. package/src/access-surface-catalog.ts +57 -54
  111. package/src/storage-contract/atomicity.test.ts +113 -0
  112. package/src/storage-contract/cache-coherence.test.ts +100 -0
  113. package/src/storage-contract/content-hash.test.ts +102 -0
  114. package/src/storage-contract/failure-semantics.test.ts +108 -0
  115. package/src/storage-contract/harness.ts +148 -0
  116. package/src/storage-contract/namespace-routing.test.ts +135 -0
  117. package/src/storage-contract/round-trip.test.ts +226 -0
  118. package/src/storage-contract/surface.test.ts +90 -0
  119. package/src/storage-contract/versioning.test.ts +139 -0
  120. package/dist/chunk-25FHSOEB.js.map +0 -1
  121. package/dist/chunk-RYDT7LAI.js.map +0 -1
  122. /package/dist/{capsule-crypto-GWVG7LGC.js.map → access-operations-batch.js.map} +0 -0
  123. /package/dist/{auto-sync-3AAP25FV.js.map → auto-sync-24NNS3GU.js.map} +0 -0
  124. /package/dist/{chunk-OWHERGF2.js.map → chunk-2NLLXCJG.js.map} +0 -0
  125. /package/dist/{chunk-PJ5MDXOF.js.map → chunk-2SYPOX45.js.map} +0 -0
  126. /package/dist/{chunk-7G4KPGR3.js.map → chunk-47LPHBIP.js.map} +0 -0
  127. /package/dist/{chunk-OCFNG4LB.js.map → chunk-6GJ2WQRK.js.map} +0 -0
  128. /package/dist/{chunk-4NFVPDIL.js.map → chunk-74MYJSKB.js.map} +0 -0
  129. /package/dist/{chunk-PKMQK2WY.js.map → chunk-773DPI6T.js.map} +0 -0
  130. /package/dist/{chunk-DE2OPIQD.js.map → chunk-7ARKLLCV.js.map} +0 -0
  131. /package/dist/{chunk-3EHCV6YV.js.map → chunk-7L54YZUI.js.map} +0 -0
  132. /package/dist/{chunk-AGNBY3VG.js.map → chunk-APJQ6UEA.js.map} +0 -0
  133. /package/dist/{chunk-UNNDFYUX.js.map → chunk-ARV3AUOM.js.map} +0 -0
  134. /package/dist/{chunk-S24J5LLQ.js.map → chunk-BKBSE3BU.js.map} +0 -0
  135. /package/dist/{chunk-AE3C4PY6.js.map → chunk-BMKGG7GC.js.map} +0 -0
  136. /package/dist/{chunk-NMW3T6T6.js.map → chunk-C2EHWNP2.js.map} +0 -0
  137. /package/dist/{chunk-VKJHM6PL.js.map → chunk-DGTDRIUO.js.map} +0 -0
  138. /package/dist/{chunk-OOEVMJK4.js.map → chunk-HEHPVBWA.js.map} +0 -0
  139. /package/dist/{chunk-YOI3ELXF.js.map → chunk-IF362TF6.js.map} +0 -0
  140. /package/dist/{chunk-ABCGDW3A.js.map → chunk-IFR23TC5.js.map} +0 -0
  141. /package/dist/{chunk-ZXFNSKPY.js.map → chunk-JHAYR2JY.js.map} +0 -0
  142. /package/dist/{chunk-6AFVIOVZ.js.map → chunk-K22IJO4R.js.map} +0 -0
  143. /package/dist/{chunk-QV5WFNZ2.js.map → chunk-KFU2SKDD.js.map} +0 -0
  144. /package/dist/{chunk-5W7CTZ2E.js.map → chunk-KQAFEZQX.js.map} +0 -0
  145. /package/dist/{chunk-TUPNAHHQ.js.map → chunk-KZMMAVR2.js.map} +0 -0
  146. /package/dist/{chunk-6Q7JFY4P.js.map → chunk-LY4FJLPT.js.map} +0 -0
  147. /package/dist/{chunk-XXDOJ5NL.js.map → chunk-M5MWSG4S.js.map} +0 -0
  148. /package/dist/{chunk-YLB7NMLD.js.map → chunk-MNH2OODZ.js.map} +0 -0
  149. /package/dist/{chunk-ORXVO6TH.js.map → chunk-NFGJBEPT.js.map} +0 -0
  150. /package/dist/{chunk-QDIVMS25.js.map → chunk-NUWZFSKC.js.map} +0 -0
  151. /package/dist/{chunk-6GHWF4QH.js.map → chunk-OM6ETLLH.js.map} +0 -0
  152. /package/dist/{chunk-53EEQYE5.js.map → chunk-OMSTU233.js.map} +0 -0
  153. /package/dist/{chunk-R64MNFPV.js.map → chunk-S3WYRPSA.js.map} +0 -0
  154. /package/dist/{chunk-UOIRJ45Z.js.map → chunk-S5APSA3U.js.map} +0 -0
  155. /package/dist/{chunk-WAHI7SPK.js.map → chunk-SY6WG6DB.js.map} +0 -0
  156. /package/dist/{chunk-K7QRDDSR.js.map → chunk-ULVM34NP.js.map} +0 -0
  157. /package/dist/{chunk-UVYI6VIX.js.map → chunk-X7Y7WX73.js.map} +0 -0
  158. /package/dist/{chunk-QHNJVWF6.js.map → chunk-Y4PMATX2.js.map} +0 -0
  159. /package/dist/{chunk-KF74X62T.js.map → chunk-YGKUAX2B.js.map} +0 -0
  160. /package/dist/{chunk-YN4ZT4CW.js.map → chunk-YI7RZO7Y.js.map} +0 -0
  161. /package/dist/{graph-edge-decay-D7OESCBR.js.map → graph-edge-decay-JLOF3YW4.js.map} +0 -0
@@ -549,6 +549,10 @@ export const codegraphIngestTracesOperation = defineOperation<
549
549
  },
550
550
  });
551
551
 
552
+ // Batch-migrated operations (issue #1525): registers all remaining
553
+ // handlers through the boundary as a side effect of importing this module.
554
+ import "./access-operations-batch.js";
555
+
552
556
  export const REGISTERED_OPERATIONS = [
553
557
  memoryGetOperation.spec.name,
554
558
  memorySearchOperation.spec.name,
@@ -44,7 +44,7 @@ import {
44
44
  // catalog-completeness correction: adding routes that were always live but
45
45
  // omitted from the catalog (review-caught). Such a bump MUST be accompanied
46
46
  // by the newly-cataloged entries; the higher count is the honest baseline.
47
- const UNMIGRATED_HANDLER_BASELINE = 134;
47
+ const UNMIGRATED_HANDLER_BASELINE = 80;
48
48
 
49
49
  // Keep the import live — `getOperation` is the call surfaces use at dispatch
50
50
  // time; referencing it here pins the registry's lookup contract.
@@ -187,6 +187,7 @@ function extractHttpRouteDispatchMap(): Map<string, Set<string>> {
187
187
  const lines = source.split("\n");
188
188
  const routeOps = new Map<string, Set<string>>();
189
189
  let currentKey: string | null = null;
190
+ let lastSeenPathname: string | null = null;
190
191
 
191
192
  for (let i = 0; i < lines.length; i++) {
192
193
  const line = lines[i]!;
@@ -217,6 +218,7 @@ function extractHttpRouteDispatchMap(): Map<string, Set<string>> {
217
218
  }
218
219
 
219
220
  if (pathname) {
221
+ lastSeenPathname = pathname;
220
222
  // Determine the HTTP method: same line first, then backward for
221
223
  // exact-match routes, forward for dynamic routes. Check both positive
222
224
  // (===) and negated (!==) method patterns — `req.method !== "GET"` as
@@ -253,6 +255,20 @@ function extractHttpRouteDispatchMap(): Map<string, Set<string>> {
253
255
  continue;
254
256
  }
255
257
 
258
+ // Detect method changes within a shared route block (e.g. a single
259
+ // regex match with GET/PUT/DELETE branches). When a new method check
260
+ // appears after a route pattern was already detected, update currentKey
261
+ // to the new method + the same pathname so per-method getOperation
262
+ // calls are tracked against the right route key.
263
+ const methodChange = line.match(/req\.method\s*===\s*"(\w+)"/);
264
+ if (methodChange && lastSeenPathname) {
265
+ const newKey = methodChange[1]! + " " + lastSeenPathname;
266
+ if (!routeOps.has(newKey)) {
267
+ routeOps.set(newKey, new Set<string>());
268
+ }
269
+ currentKey = newKey;
270
+ }
271
+
256
272
  // Collect getOperation calls within the current route block.
257
273
  if (currentKey) {
258
274
  const opMatch = line.match(/getOperation\("(\w+)"\)/);
@@ -160,65 +160,68 @@ export const MCP_TOOLS: readonly McpToolEntry[] = [
160
160
  // (health, adapters, the /mcp delegate, admin console, SSE-only endpoints)
161
161
  // carry no user request envelope and are intentionally excluded.
162
162
  export const HTTP_ROUTES: readonly HttpRouteEntry[] = [
163
- { method: "POST", pathname: "/engram/v1/recall", operation: null },
164
- { method: "POST", pathname: "/engram/v1/coding-context", operation: null },
165
- { method: "POST", pathname: "/engram/v1/capsules/export", operation: null },
166
- { method: "POST", pathname: "/engram/v1/capsules/import", operation: null },
167
- { method: "GET", pathname: "/engram/v1/offline-sync/snapshot", operation: null },
168
- { method: "GET", pathname: "/engram/v1/offline-sync/snapshot-stream", operation: null },
169
- { method: "POST", pathname: "/engram/v1/offline-sync/snapshot", operation: null },
170
- { method: "POST", pathname: "/engram/v1/offline-sync/files", operation: null },
171
- { method: "POST", pathname: "/engram/v1/offline-sync/file-content", operation: null },
172
- { method: "POST", pathname: "/engram/v1/offline-sync/apply-file-content", operation: null },
173
- { method: "POST", pathname: "/engram/v1/offline-sync/apply", operation: null },
174
- { method: "POST", pathname: "/engram/v1/recall/explain", operation: null },
175
- { method: "POST", pathname: "/engram/v1/action-confidence", operation: null },
176
- { method: "GET", pathname: "/engram/v1/recall/tier-explain", operation: null },
177
- { method: "GET", pathname: "/engram/v1/recall/xray", operation: null },
178
- { method: "GET", pathname: "/engram/v1/wearables/status", operation: null },
179
- { method: "POST", pathname: "/engram/v1/wearables/sync", operation: null },
180
- { method: "GET", pathname: "/engram/v1/wearables/transcript", operation: null },
181
- { method: "GET", pathname: "/engram/v1/wearables/transcripts/search", operation: null },
182
- { method: "GET", pathname: "/engram/v1/wearables/memories", operation: null },
183
- { method: "POST", pathname: "/engram/v1/observe", operation: null },
184
- { method: "POST", pathname: "/engram/v1/lcm/search", operation: null },
185
- { method: "POST", pathname: "/engram/v1/lcm/compaction/flush", operation: null },
186
- { method: "POST", pathname: "/engram/v1/lcm/compaction/record", operation: null },
187
- { method: "GET", pathname: "/engram/v1/lcm/status", operation: null },
163
+ { method: "POST", pathname: "/engram/v1/recall", operation: "recall" },
164
+ { method: "POST", pathname: "/engram/v1/coding-context", operation: "set_coding_context" },
165
+ { method: "POST", pathname: "/engram/v1/capsules/export", operation: "capsule_export" },
166
+ { method: "POST", pathname: "/engram/v1/capsules/import", operation: "capsule_import" },
167
+ { method: "GET", pathname: "/engram/v1/offline-sync/snapshot", operation: "offline_sync_snapshot" },
168
+ { method: "POST", pathname: "/engram/v1/offline-sync/snapshot", operation: "offline_sync_snapshot" },
169
+ { method: "POST", pathname: "/engram/v1/offline-sync/files", operation: "offline_sync_files" },
170
+ { method: "POST", pathname: "/engram/v1/offline-sync/file-content", operation: "offline_sync_file_content" },
171
+ { method: "POST", pathname: "/engram/v1/offline-sync/apply-file-content", operation: "offline_sync_apply_file_content" },
172
+ { method: "POST", pathname: "/engram/v1/offline-sync/apply", operation: "offline_sync_apply" },
173
+ { method: "POST", pathname: "/engram/v1/recall/explain", operation: "recall_explain" },
174
+ { method: "POST", pathname: "/engram/v1/action-confidence", operation: "action_confidence" },
175
+ { method: "GET", pathname: "/engram/v1/recall/tier-explain", operation: "recall_tier_explain" },
176
+ { method: "GET", pathname: "/engram/v1/recall/xray", operation: "recall_xray" },
177
+ { method: "GET", pathname: "/engram/v1/wearables/status", operation: "wearables_status" },
178
+ { method: "POST", pathname: "/engram/v1/wearables/sync", operation: "wearables_sync" },
179
+ { method: "GET", pathname: "/engram/v1/wearables/transcript", operation: "transcript_day" },
180
+ { method: "GET", pathname: "/engram/v1/wearables/transcripts/search", operation: "transcript_search" },
181
+ { method: "GET", pathname: "/engram/v1/wearables/memories", operation: "transcript_memories" },
182
+ { method: "POST", pathname: "/engram/v1/observe", operation: "observe" },
183
+ { method: "POST", pathname: "/engram/v1/lcm/search", operation: "lcm_search" },
184
+ { method: "POST", pathname: "/engram/v1/lcm/compaction/flush", operation: "lcm_compaction_flush" },
185
+ { method: "POST", pathname: "/engram/v1/lcm/compaction/record", operation: "lcm_compaction_record" },
186
+ { method: "GET", pathname: "/engram/v1/lcm/status", operation: "lcm_status" },
188
187
  { method: "POST", pathname: "/engram/v1/memories", operation: "memory_store" },
189
188
  { method: "POST", pathname: "/engram/v1/coding/decisions", operation: "coding_decision" },
190
189
  { method: "POST", pathname: "/engram/v1/coding/architecture", operation: "coding_architecture" },
191
190
  { method: "POST", pathname: "/engram/v1/coding/delta", operation: "coding_delta" },
192
- { method: "POST", pathname: "/engram/v1/suggestions", operation: null },
193
- { method: "GET", pathname: "/engram/v1/memories", operation: null },
191
+ { method: "POST", pathname: "/engram/v1/suggestions", operation: "suggestion_submit" },
192
+ { method: "GET", pathname: "/engram/v1/memories", operation: "memory_list" },
194
193
  { method: "GET", pathname: "/engram/v1/memories/:id", operation: "memory_get" },
195
- { method: "GET", pathname: "/engram/v1/memories/:id/timeline", operation: null },
196
- { method: "GET", pathname: "/engram/v1/entities", operation: null },
197
- { method: "GET", pathname: "/engram/v1/entities/:id", operation: null },
198
- { method: "GET", pathname: "/engram/v1/review-queue", operation: null },
199
- { method: "GET", pathname: "/engram/v1/maintenance", operation: null },
200
- { method: "GET", pathname: "/engram/v1/quality", operation: null },
201
- { method: "GET", pathname: "/engram/v1/trust-zones/status", operation: null },
202
- { method: "GET", pathname: "/engram/v1/procedural/stats", operation: null },
203
- { method: "GET", pathname: "/engram/v1/trust-zones/records", operation: null },
204
- { method: "POST", pathname: "/engram/v1/review-disposition", operation: null },
205
- { method: "POST", pathname: "/engram/v1/trust-zones/promote", operation: null },
206
- { method: "POST", pathname: "/engram/v1/trust-zones/demo-seed", operation: null },
207
- { method: "POST", pathname: "/v1/citations/observed", operation: null },
208
- { method: "GET", pathname: "/engram/v1/review/contradictions", operation: null },
209
- { method: "GET", pathname: "/engram/v1/review/contradictions/:id", operation: null },
210
- { method: "POST", pathname: "/engram/v1/review/resolve", operation: null },
211
- { method: "GET", pathname: "/engram/v1/graph/snapshot", operation: null },
212
- { method: "POST", pathname: "/engram/v1/contradiction-scan", operation: null },
213
- { method: "GET", pathname: "/engram/v1/graph/events", operation: null },
214
- { method: "GET", pathname: "/engram/v1/console/state", operation: null },
215
- { method: "GET", pathname: "/engram/v1/peers", operation: null },
216
- { method: "GET", pathname: "/engram/v1/peers/:id/profile", operation: null },
217
- { method: "GET", pathname: "/engram/v1/peers/:id", operation: null },
218
- { method: "PUT", pathname: "/engram/v1/peers/:id", operation: null },
219
- { method: "DELETE", pathname: "/engram/v1/peers/:id", operation: null },
220
- { method: "GET", pathname: "/engram/v1/dreams/status", operation: null },
221
- { method: "POST", pathname: "/engram/v1/dreams/run", operation: null },
194
+ { method: "GET", pathname: "/engram/v1/memories/:id/timeline", operation: "memory_timeline" },
195
+ { method: "GET", pathname: "/engram/v1/entities", operation: "entity_list" },
196
+ { method: "GET", pathname: "/engram/v1/entities/:id", operation: "entity_get" },
197
+ { method: "GET", pathname: "/engram/v1/review-queue", operation: "review_queue_list" },
198
+ { method: "GET", pathname: "/engram/v1/maintenance", operation: "maintenance_status" },
199
+ { method: "GET", pathname: "/engram/v1/quality", operation: "quality_status" },
200
+ { method: "GET", pathname: "/engram/v1/trust-zones/status", operation: "trust_zones_status" },
201
+ { method: "GET", pathname: "/engram/v1/procedural/stats", operation: "procedural_stats" },
202
+ { method: "GET", pathname: "/engram/v1/trust-zones/records", operation: "trust_zones_records" },
203
+ { method: "POST", pathname: "/engram/v1/review-disposition", operation: "review_disposition" },
204
+ { method: "POST", pathname: "/engram/v1/trust-zones/promote", operation: "trust_zones_promote" },
205
+ { method: "POST", pathname: "/engram/v1/trust-zones/demo-seed", operation: "trust_zones_demo_seed" },
206
+ { method: "POST", pathname: "/v1/citations/observed", operation: "citations_observed" },
207
+ { method: "GET", pathname: "/engram/v1/review/contradictions", operation: "review_list" },
208
+ { method: "GET", pathname: "/engram/v1/review/contradictions/:id", operation: "contradiction_detail" },
209
+ { method: "POST", pathname: "/engram/v1/review/resolve", operation: "review_resolve" },
210
+ { method: "GET", pathname: "/engram/v1/graph/snapshot", operation: "graph_snapshot" },
211
+ { method: "POST", pathname: "/engram/v1/contradiction-scan", operation: "contradiction_scan_run" },
212
+ { method: "GET", pathname: "/engram/v1/console/state", operation: "console_state" },
213
+ { method: "GET", pathname: "/engram/v1/peers", operation: "peer_list" },
214
+ { method: "GET", pathname: "/engram/v1/peers/:id/profile", operation: "peer_profile_get" },
215
+ { method: "GET", pathname: "/engram/v1/peers/:id", operation: "peer_get" },
216
+ { method: "PUT", pathname: "/engram/v1/peers/:id", operation: "peer_set" },
217
+ { method: "DELETE", pathname: "/engram/v1/peers/:id", operation: "peer_delete" },
218
+ { method: "GET", pathname: "/engram/v1/dreams/status", operation: "dreams_status" },
219
+ { method: "POST", pathname: "/engram/v1/dreams/run", operation: "dreams_run" },
220
+ // SSE-only endpoints: carried in the catalog for completeness tracking.
221
+ // The operation registration makes them visible to the boundary; the
222
+ // streaming lifecycle is owned by the HTTP transport (handleGraphEventsSSE).
223
+ { method: "GET", pathname: "/engram/v1/offline-sync/snapshot-stream", operation: "offline_sync_snapshot_stream" },
224
+ { method: "GET", pathname: "/engram/v1/graph/events", operation: "graph_events" },
222
225
  ];
223
226
 
224
227
  /**
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Issue #1533 — Phase A contract test: atomic write semantics.
3
+ *
4
+ * Rule 54 (CLAUDE.md): replace operations MUST write temp-then-rename, NEVER
5
+ * delete-before-write. This test pins that invariant by making rename fail
6
+ * (read-only target directory) and asserting the original file survives the
7
+ * failed overwrite.
8
+ *
9
+ * The atomic write primitive lives in secure-fs (`writeMaybeEncryptedFile`)
10
+ * and is consumed by StorageManager via `writeStorageSecureFile`. We test the
11
+ * primitive directly here because it is the single atomicity seam every write
12
+ * entry point depends on.
13
+ */
14
+
15
+ import assert from "node:assert/strict";
16
+ import { chmod, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
17
+ import os from "node:os";
18
+ import path from "node:path";
19
+ import test from "node:test";
20
+
21
+ import { writeMaybeEncryptedFile, readMaybeEncryptedFile } from "../secure-store/secure-fs.js";
22
+
23
+ test("atomicity: writeMaybeEncryptedFile writes temp-then-rename (no temp files left behind on success)", async () => {
24
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-atomic-"));
25
+ try {
26
+ const target = path.join(dir, "target.md");
27
+ await writeMaybeEncryptedFile(target, "first content", null, {}, dir);
28
+
29
+ // No temp files left behind
30
+ const { readdir } = await import("node:fs/promises");
31
+ const entries = await readdir(dir);
32
+ assert.deepEqual(entries.sort(), ["target.md"]);
33
+
34
+ // Overwrite — still only the target, no leftover temp
35
+ await writeMaybeEncryptedFile(target, "second content", null, {}, dir);
36
+ const entries2 = await readdir(dir);
37
+ assert.deepEqual(entries2.sort(), ["target.md"]);
38
+
39
+ const read = await readMaybeEncryptedFile(target, null, dir);
40
+ assert.equal(read, "second content");
41
+ } finally {
42
+ await rm(dir, { recursive: true, force: true });
43
+ }
44
+ });
45
+
46
+ test("atomicity: a failed rename leaves the original file intact (never delete-before-write)", async (t) => {
47
+ // Skip on root — chmod restrictions don't apply.
48
+ if (process.getuid && process.getuid() === 0) {
49
+ t.skip("chmod-based test is unreliable when running as root");
50
+ return;
51
+ }
52
+
53
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-atomic-fail-"));
54
+ try {
55
+ const target = path.join(dir, "survivor.md");
56
+ await writeMaybeEncryptedFile(target, "original", null, {}, dir);
57
+
58
+ // Make the directory read-only so the rename into place fails.
59
+ await chmod(dir, 0o500);
60
+ try {
61
+ await assert.rejects(
62
+ () => writeMaybeEncryptedFile(target, "would-be-replacement", null, {}, dir),
63
+ // rename (or temp write) fails — the exact error varies by platform
64
+ );
65
+
66
+ // THE CONTRACT: the original file must still be readable with its
67
+ // original content. Delete-before-write would have destroyed it before
68
+ // the failed rename.
69
+ const survived = await readMaybeEncryptedFile(target, null, dir);
70
+ assert.equal(survived, "original", "original file must survive a failed atomic replace");
71
+ } finally {
72
+ // Restore perms so cleanup works.
73
+ await chmod(dir, 0o700);
74
+ }
75
+ } finally {
76
+ await rm(dir, { recursive: true, force: true });
77
+ }
78
+ });
79
+
80
+ test("atomicity: StorageManager.updateMemory preserves the original on overwrite success", async (t) => {
81
+ const { StorageManager } = await import("../storage.js");
82
+ const { makeStorage } = await import("./harness.js");
83
+ const { storage, cleanup } = await makeStorage();
84
+ try {
85
+ const id = await storage.writeMemory("fact", "version one");
86
+ const before = await storage.getMemoryById(id);
87
+ assert.ok(before);
88
+ assert.equal(before!.content, "version one");
89
+
90
+ const updated = await storage.updateMemory(id, "version two");
91
+ assert.equal(updated, true);
92
+
93
+ const after = await storage.getMemoryById(id);
94
+ assert.ok(after);
95
+ assert.equal(after!.content, "version two");
96
+ // The id is stable across the update (same file path, not a new memory)
97
+ assert.equal(after!.frontmatter.id, id);
98
+ } finally {
99
+ await cleanup();
100
+ }
101
+ });
102
+
103
+ test("atomicity: mkdir is implicit — writeMaybeEncryptedFile creates parent dirs", async () => {
104
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-atomic-mkdir-"));
105
+ try {
106
+ const target = path.join(dir, "nested", "deep", "file.md");
107
+ await writeMaybeEncryptedFile(target, "content", null, {}, dir);
108
+ const read = await readMaybeEncryptedFile(target, null, dir);
109
+ assert.equal(read, "content");
110
+ } finally {
111
+ await rm(dir, { recursive: true, force: true });
112
+ }
113
+ });
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Issue #1533 — Phase A contract test: cache coherence across instances.
3
+ *
4
+ * Rule 37 (memory-cache): write via instance A, read via a FRESH instance B
5
+ * over the same dir → B sees the write. The module-level caches
6
+ * (readAllMemories in-flight, version sentinels) are keyed by baseDir and
7
+ * shared/static — this test pins that a write invalidates correctly so a
8
+ * second instance never serves stale data.
9
+ */
10
+
11
+ import assert from "node:assert/strict";
12
+ import test from "node:test";
13
+
14
+ import { StorageManager } from "../storage.js";
15
+ import { resetStaticCaches } from "./harness.js";
16
+
17
+ test("cache-coherence: write via instance A, read via fresh instance B → B sees the write", async () => {
18
+ const { mkdtemp, rm } = await import("node:fs/promises");
19
+ const os = await import("node:os");
20
+ const path = await import("node:path");
21
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-cache-coherence-"));
22
+ try {
23
+ resetStaticCaches();
24
+ const smA = new StorageManager(dir);
25
+ await smA.ensureDirectories();
26
+
27
+ const id = await smA.writeMemory("fact", "written by A");
28
+
29
+ // Fresh instance over the same dir
30
+ const smB = new StorageManager(dir);
31
+ await smB.ensureDirectories();
32
+ const found = await smB.getMemoryById(id);
33
+ assert.ok(found, "instance B must see the memory written by instance A");
34
+ assert.equal(found!.content, "written by A");
35
+ } finally {
36
+ resetStaticCaches();
37
+ await rm(dir, { recursive: true, force: true });
38
+ }
39
+ });
40
+
41
+ test("cache-coherence: delete via instance A is reflected in fresh instance B", async () => {
42
+ const { mkdtemp, rm } = await import("node:fs/promises");
43
+ const os = await import("node:os");
44
+ const path = await import("node:path");
45
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-cache-del-"));
46
+ try {
47
+ resetStaticCaches();
48
+ const smA = new StorageManager(dir);
49
+ await smA.ensureDirectories();
50
+ const id = await smA.writeMemory("fact", "to be deleted");
51
+
52
+ // Confirm B sees it
53
+ const smB = new StorageManager(dir);
54
+ await smB.ensureDirectories();
55
+ assert.ok(await smB.getMemoryById(id));
56
+
57
+ // A deletes
58
+ await smA.invalidateMemory(id);
59
+
60
+ // Fresh instance C must NOT see it
61
+ const smC = new StorageManager(dir);
62
+ await smC.ensureDirectories();
63
+ const found = await smC.getMemoryById(id);
64
+ assert.equal(found, null, "fresh instance must not see a deleted memory");
65
+ } finally {
66
+ resetStaticCaches();
67
+ await rm(dir, { recursive: true, force: true });
68
+ }
69
+ });
70
+
71
+ test("cache-coherence: readAllMemories reflects writes across instances after invalidation", async () => {
72
+ const { mkdtemp, rm } = await import("node:fs/promises");
73
+ const os = await import("node:os");
74
+ const path = await import("node:path");
75
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-cache-list-"));
76
+ try {
77
+ resetStaticCaches();
78
+ const smA = new StorageManager(dir);
79
+ await smA.ensureDirectories();
80
+ await smA.writeMemory("fact", "first");
81
+ await smA.writeMemory("decision", "second");
82
+
83
+ const smB = new StorageManager(dir);
84
+ await smB.ensureDirectories();
85
+ const all = await smB.readAllMemories();
86
+ assert.ok(all.length >= 2, "fresh instance must list all written memories");
87
+
88
+ // Write more via A
89
+ await smA.writeMemory("principle", "third");
90
+
91
+ const smC = new StorageManager(dir);
92
+ await smC.ensureDirectories();
93
+ const allAfter = await smC.readAllMemories();
94
+ assert.ok(allAfter.length >= 3, "fresh instance must see the additional write");
95
+ assert.ok(allAfter.some((m) => m.content === "third"));
96
+ } finally {
97
+ resetStaticCaches();
98
+ await rm(dir, { recursive: true, force: true });
99
+ }
100
+ });
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Issue #1533 — Phase A contract test: content hash identity.
3
+ *
4
+ * Rule 23: the write-side and dedup-side MUST hash the same `rawContent`,
5
+ * never the timestamped/cited form. `writeMemory` persists a `contentHash` on
6
+ * the frontmatter for facts; `hasFactContentHash` queries the dedup index.
7
+ * This test pins that the two sides agree so subsequent extractions of the
8
+ * same logical fact are deduped correctly even when citation timestamps differ.
9
+ */
10
+
11
+ import assert from "node:assert/strict";
12
+ import test from "node:test";
13
+
14
+ import { ContentHashIndex } from "../storage.js";
15
+ import { makeStorage } from "./harness.js";
16
+
17
+ test("content-hash: ContentHashIndex.computeHash is deterministic for the same input", () => {
18
+ const a = ContentHashIndex.computeHash("the sky is blue");
19
+ const b = ContentHashIndex.computeHash("the sky is blue");
20
+ assert.equal(a, b);
21
+ assert.notEqual(
22
+ ContentHashIndex.computeHash("the sky is blue"),
23
+ ContentHashIndex.computeHash("the sky is green"),
24
+ );
25
+ });
26
+
27
+ test("content-hash: writeMemory for fact registers the hash so hasFactContentHash returns true", async () => {
28
+ const { storage, cleanup } = await makeStorage();
29
+ try {
30
+ const rawFact = "PostgreSQL is our primary database";
31
+ await storage.writeMemory("fact", rawFact);
32
+
33
+ const has = await storage.hasFactContentHash(rawFact);
34
+ assert.equal(has, true, "hasFactContentHash must return true for a just-written fact");
35
+ } finally {
36
+ await cleanup();
37
+ }
38
+ });
39
+
40
+ test("content-hash: contentHashSource overrides the persisted body for dedup (rule 23)", async () => {
41
+ const { storage, cleanup } = await makeStorage();
42
+ try {
43
+ const rawFact = "We migrated to MySQL in March";
44
+ // Persist a citation-annotated body, but index the RAW fact text
45
+ await storage.writeMemory("fact", `${rawFact} [cited 2026-01-01]`, {
46
+ contentHashSource: rawFact,
47
+ });
48
+
49
+ // Querying with the RAW fact (not the cited form) must hit
50
+ assert.equal(
51
+ await storage.hasFactContentHash(rawFact),
52
+ true,
53
+ "hasFactContentHash must match on the raw fact, not the cited body",
54
+ );
55
+ // The cited form should NOT be separately indexed (it has the citation suffix)
56
+ assert.equal(
57
+ await storage.hasFactContentHash(`${rawFact} [cited 2026-01-01]`),
58
+ false,
59
+ "the cited/timestamped form must not be the indexed hash",
60
+ );
61
+ } finally {
62
+ await cleanup();
63
+ }
64
+ });
65
+
66
+ test("content-hash: non-fact categories do not register a content hash", async () => {
67
+ const { storage, cleanup } = await makeStorage();
68
+ try {
69
+ await storage.writeMemory("decision", "we chose option B");
70
+ assert.equal(
71
+ await storage.hasFactContentHash("we chose option B"),
72
+ false,
73
+ "non-fact categories must not enter the fact dedup index",
74
+ );
75
+ } finally {
76
+ await cleanup();
77
+ }
78
+ });
79
+
80
+ test("content-hash: normalizeContent produces stable text for equivalent inputs", () => {
81
+ const a = ContentHashIndex.normalizeContent(" Hello World ");
82
+ const b = ContentHashIndex.normalizeContent("Hello World");
83
+ assert.equal(a, b, "normalizeContent must collapse whitespace consistently");
84
+ });
85
+
86
+ test("content-hash: the frontmatter contentHash matches computeHash of the raw content", async () => {
87
+ const { storage, cleanup } = await makeStorage();
88
+ try {
89
+ const rawFact = "deterministic hash check";
90
+ const id = await storage.writeMemory("fact", rawFact);
91
+ const memory = await storage.getMemoryById(id);
92
+ assert.ok(memory);
93
+ assert.ok(memory!.frontmatter.contentHash, "frontmatter must carry a contentHash for facts");
94
+ assert.equal(
95
+ memory!.frontmatter.contentHash,
96
+ ContentHashIndex.computeHash(rawFact),
97
+ "frontmatter contentHash must equal computeHash of the raw content",
98
+ );
99
+ } finally {
100
+ await cleanup();
101
+ }
102
+ });
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Issue #1533 — Phase A contract test: failure semantics.
3
+ *
4
+ * Rule 34: reads distinguish empty-dir from unreadable-dir — `{ok:false}` vs
5
+ * `[]`. Where the current surface already does this, we pin it; where it
6
+ * doesn't, we record the gap (for the silent-failures issue) rather than
7
+ * change behavior here.
8
+ *
9
+ * Also pins containment: a FILE where a directory is expected is skipped via
10
+ * lstat().isDirectory(), not existsSync() (rule 24).
11
+ */
12
+
13
+ import assert from "node:assert/strict";
14
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
15
+ import os from "node:os";
16
+ import path from "node:path";
17
+ import test from "node:test";
18
+
19
+ import { StorageManager } from "../storage.js";
20
+ import { makeStorage } from "./harness.js";
21
+
22
+ test("failure-semantics: readAllMemories on a fresh (empty) dir returns [] not null", async () => {
23
+ const { storage, cleanup } = await makeStorage();
24
+ try {
25
+ const result = await storage.readAllMemories();
26
+ assert.ok(Array.isArray(result), "readAllMemories must return an array");
27
+ assert.equal(result.length, 0, "empty store must return an empty array");
28
+ } finally {
29
+ await cleanup();
30
+ }
31
+ });
32
+
33
+ test("failure-semantics: getMemoryById on empty store returns null (not throw)", async () => {
34
+ const { storage, cleanup } = await makeStorage();
35
+ try {
36
+ const result = await storage.getMemoryById("anything");
37
+ assert.equal(result, null);
38
+ } finally {
39
+ await cleanup();
40
+ }
41
+ });
42
+
43
+ test("failure-semantics: readMemoryByPath on a non-existent file returns null (not throw)", async () => {
44
+ const { storage, baseDir, cleanup } = await makeStorage();
45
+ try {
46
+ const result = await storage.readMemoryByPath(path.join(baseDir, "facts", "2026-01-01", "nope.md"));
47
+ assert.equal(result, null);
48
+ } finally {
49
+ await cleanup();
50
+ }
51
+ });
52
+
53
+ test("failure-semantics: readMemoryByPath on a malformed markdown file returns null", async () => {
54
+ const { storage, baseDir, cleanup } = await makeStorage();
55
+ try {
56
+ const dir = path.join(baseDir, "facts", "2026-01-01");
57
+ await mkdir(dir, { recursive: true });
58
+ const filePath = path.join(dir, "garbage.md");
59
+ await writeFile(filePath, "this has no frontmatter at all\njust random text", "utf-8");
60
+
61
+ const result = await storage.readMemoryByPath(filePath);
62
+ assert.equal(result, null, "malformed file (no frontmatter, not entity) must return null");
63
+ } finally {
64
+ await cleanup();
65
+ }
66
+ });
67
+
68
+ test("failure-semantics: a FILE where a category directory is expected does not crash readAllMemories", async () => {
69
+ // Create a FILE named "facts" (not a directory) at the base dir, then scan.
70
+ // collectActiveMemoryPaths uses lstat().isDirectory() (not existsSync) so a
71
+ // file-as-directory entry is skipped without crashing (rule 24 containment).
72
+ // We do NOT call ensureDirectories — it would try to mkdir facts/<today> and
73
+ // fail on the file. The scan path must be robust independently of dir creation.
74
+ const baseDir = await mkdtemp(path.join(os.tmpdir(), "remnic-fail-file-as-dir-"));
75
+ try {
76
+ StorageManager.clearAllStaticCaches();
77
+ await writeFile(path.join(baseDir, "facts"), "I am a file not a directory", "utf-8");
78
+ const storage = new StorageManager(baseDir);
79
+
80
+ const result = await storage.readAllMemories();
81
+ assert.ok(Array.isArray(result));
82
+ assert.equal(result.length, 0, "a file-as-directory must not produce spurious memories");
83
+ } finally {
84
+ StorageManager.clearAllStaticCaches();
85
+ await rm(baseDir, { recursive: true, force: true });
86
+ }
87
+ });
88
+
89
+ test("failure-semantics: invalidateMemory on a missing memory returns false (not throw)", async () => {
90
+ const { storage, cleanup } = await makeStorage();
91
+ try {
92
+ const result = await storage.invalidateMemory("totally-missing-id-99999");
93
+ assert.equal(result, false);
94
+ } finally {
95
+ await cleanup();
96
+ }
97
+ });
98
+
99
+ test("failure-semantics: ensureDirectories is idempotent (safe to call multiple times)", async () => {
100
+ const { storage, cleanup } = await makeStorage();
101
+ try {
102
+ await storage.ensureDirectories();
103
+ await storage.ensureDirectories();
104
+ await storage.ensureDirectories();
105
+ } finally {
106
+ await cleanup();
107
+ }
108
+ });