@remnic/core 9.51.1 → 9.52.0

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 (234) hide show
  1. package/dist/access-admin-ops-surface.d.ts +6 -5
  2. package/dist/access-admin-ops-surface.js +16 -16
  3. package/dist/access-authorization-probe.d.ts +6 -5
  4. package/dist/access-authorization-probe.js +17 -17
  5. package/dist/access-boundary.d.ts +6 -5
  6. package/dist/access-boundary.js +16 -16
  7. package/dist/access-cli.js +39 -39
  8. package/dist/access-extraction-force-flush.d.ts +6 -5
  9. package/dist/access-extraction-force-flush.js +16 -16
  10. package/dist/access-http-lcm-compaction.d.ts +6 -5
  11. package/dist/access-http-lifecycle-flush.d.ts +6 -5
  12. package/dist/access-http-offline-stream.d.ts +6 -5
  13. package/dist/access-http-query.js +16 -16
  14. package/dist/access-http.d.ts +6 -5
  15. package/dist/access-http.js +22 -22
  16. package/dist/access-identity-continuity-surface.d.ts +5 -4
  17. package/dist/access-identity-continuity-surface.js +16 -16
  18. package/dist/access-lcm-surface.d.ts +6 -5
  19. package/dist/access-lcm-surface.js +16 -16
  20. package/dist/access-mcp.d.ts +6 -5
  21. package/dist/access-mcp.js +20 -20
  22. package/dist/access-namespace-preflight.js +16 -16
  23. package/dist/access-observe-write-surface.d.ts +6 -5
  24. package/dist/access-observe-write-surface.js +16 -16
  25. package/dist/access-offline-manifest.d.ts +5 -4
  26. package/dist/access-operations-batch.js +17 -17
  27. package/dist/access-operations.d.ts +9 -8
  28. package/dist/access-operations.js +19 -19
  29. package/dist/access-recall-concurrency.d.ts +6 -5
  30. package/dist/access-recall-concurrency.js +16 -16
  31. package/dist/access-recall-response.d.ts +6 -5
  32. package/dist/access-recall-response.js +16 -16
  33. package/dist/access-recall-surface.d.ts +6 -5
  34. package/dist/access-recall-surface.js +16 -16
  35. package/dist/access-schema.d.ts +88 -88
  36. package/dist/{access-service-BY0KJ44c.d.ts → access-service-Cbk3bNbD.d.ts} +3 -3
  37. package/dist/access-service-helpers.d.ts +6 -5
  38. package/dist/access-service.d.ts +6 -5
  39. package/dist/access-service.js +16 -16
  40. package/dist/access-surface-catalog.d.ts +6 -5
  41. package/dist/access-wearables-meetings-surface.d.ts +2 -2
  42. package/dist/bootstrap.d.ts +5 -4
  43. package/dist/briefing.d.ts +1 -1
  44. package/dist/briefing.js +7 -7
  45. package/dist/buffer.d.ts +1 -1
  46. package/dist/{capsule-merge-XMFBXVDP.js → capsule-merge-HCJJMLPS.js} +4 -4
  47. package/dist/causal-consolidation.js +8 -8
  48. package/dist/{chunk-PWN32TAG.js → chunk-2ASAS3CQ.js} +2 -2
  49. package/dist/{chunk-WQM326GS.js → chunk-2LY6GIIE.js} +2 -2
  50. package/dist/{chunk-JL43BVDE.js → chunk-2OIQDTDC.js} +2 -2
  51. package/dist/{chunk-33SV3E43.js → chunk-3OY7KXHM.js} +3 -3
  52. package/dist/{chunk-AIUBBHSF.js → chunk-5UZ6UCOZ.js} +3 -3
  53. package/dist/chunk-5WITETWU.js +591 -0
  54. package/dist/chunk-5WITETWU.js.map +1 -0
  55. package/dist/{chunk-UT5BV5OG.js → chunk-7HXZJAID.js} +2 -2
  56. package/dist/{chunk-M7BXDKPR.js → chunk-7M6NYQWB.js} +3 -3
  57. package/dist/{chunk-TPTECFWR.js → chunk-B6LZ4IBH.js} +3 -3
  58. package/dist/{chunk-J5LLX6IL.js → chunk-BF6SXNQJ.js} +10 -10
  59. package/dist/{chunk-4PC4PRJE.js → chunk-BKIFTXHR.js} +2 -2
  60. package/dist/{chunk-VVG2BOKA.js → chunk-BMEYMKWX.js} +2 -2
  61. package/dist/{chunk-DCZBSE4F.js → chunk-D5YOZ66N.js} +18 -18
  62. package/dist/{chunk-BHXYAVUM.js → chunk-DLG5V6ND.js} +2 -2
  63. package/dist/{chunk-52UKFTUK.js → chunk-F4EU6VUC.js} +2 -2
  64. package/dist/{chunk-M4A44LUA.js → chunk-GSTIBXBW.js} +2 -2
  65. package/dist/{chunk-W5XVQFLM.js → chunk-GXFSQ6SD.js} +2 -2
  66. package/dist/{chunk-XADV5IZ3.js → chunk-HDIUYMSB.js} +5 -5
  67. package/dist/{chunk-FXTYIA46.js → chunk-J272WKX2.js} +2 -2
  68. package/dist/{chunk-ENTCVTQR.js → chunk-JJRVPWFY.js} +11 -1
  69. package/dist/chunk-JJRVPWFY.js.map +1 -0
  70. package/dist/{chunk-JYL3BVVZ.js → chunk-JSEJH2YQ.js} +2 -2
  71. package/dist/{chunk-5UDMFE3Q.js → chunk-MLXZD4WM.js} +2 -2
  72. package/dist/{chunk-PUSLMZBQ.js → chunk-NOEMGE56.js} +2 -2
  73. package/dist/{chunk-C5QLI33R.js → chunk-QINJITDS.js} +2 -2
  74. package/dist/{chunk-45BRYON6.js → chunk-QPOHMO6M.js} +2 -2
  75. package/dist/{chunk-ZQOCRIGA.js → chunk-QYN5PXDZ.js} +3 -3
  76. package/dist/{chunk-PA45XCUD.js → chunk-RANKJI5T.js} +2 -2
  77. package/dist/{chunk-ZHY5TMJG.js → chunk-RMHMUUBW.js} +2392 -628
  78. package/dist/chunk-RMHMUUBW.js.map +1 -0
  79. package/dist/{chunk-37ZO6LLJ.js → chunk-RYLEDKQ2.js} +2 -2
  80. package/dist/{chunk-CV6BBVYV.js → chunk-TEMSWHQD.js} +2 -2
  81. package/dist/{chunk-QGR6OVGT.js → chunk-VFSAWRMZ.js} +2 -2
  82. package/dist/{chunk-NLK2VZYZ.js → chunk-VXCNQYTC.js} +2 -2
  83. package/dist/{chunk-AZ2QBGYB.js → chunk-W7Y5JDVB.js} +1 -1
  84. package/dist/{chunk-VBHPOWBJ.js → chunk-WD5B5AIE.js} +1051 -591
  85. package/dist/chunk-WD5B5AIE.js.map +1 -0
  86. package/dist/{chunk-OYESWJ43.js → chunk-WLRD2TXF.js} +15 -10
  87. package/dist/chunk-WLRD2TXF.js.map +1 -0
  88. package/dist/{chunk-4F6X3RXW.js → chunk-X57M2Q4I.js} +2 -2
  89. package/dist/{chunk-6MTSFX7A.js → chunk-X7XEDOSL.js} +8 -8
  90. package/dist/{chunk-UEVLYVNW.js → chunk-YXKVLYXU.js} +2 -2
  91. package/dist/{chunk-OQAL6SIR.js → chunk-ZMSZVISA.js} +2 -2
  92. package/dist/{cli-CnOkH9hA.d.ts → cli-DMAFI0qt.d.ts} +3 -3
  93. package/dist/cli.d.ts +7 -6
  94. package/dist/cli.js +33 -33
  95. package/dist/compounding/engine.d.ts +1 -1
  96. package/dist/compounding/engine.js +7 -7
  97. package/dist/connectors/codex-materialize-runner.js +7 -7
  98. package/dist/connectors/index.js +7 -7
  99. package/dist/consolidation-provenance-check.d.ts +1 -1
  100. package/dist/consolidation-provenance-check.js +4 -4
  101. package/dist/consolidation-undo.d.ts +1 -1
  102. package/dist/consolidation-undo.js +3 -3
  103. package/dist/contradiction/index.d.ts +1 -1
  104. package/dist/convergence-refresh.d.ts +1 -1
  105. package/dist/convergence-refresh.js +9 -9
  106. package/dist/corpus-watermark.js +9 -9
  107. package/dist/dependency-propagation-delivery-D63r24pH.d.ts +41 -0
  108. package/dist/entity-retrieval.d.ts +1 -1
  109. package/dist/entity-retrieval.js +7 -7
  110. package/dist/explicit-capture.d.ts +5 -4
  111. package/dist/explicit-capture.js +4 -4
  112. package/dist/external-wiki-access.d.ts +6 -5
  113. package/dist/external-wiki-access.js +17 -17
  114. package/dist/external-wiki-mcp-tools.d.ts +6 -5
  115. package/dist/extraction.js +7 -7
  116. package/dist/{forget-UJDJHG6R.js → forget-HDS4N6CG.js} +3 -3
  117. package/dist/harmonic-retrieval.js +1 -1
  118. package/dist/index.d.ts +687 -686
  119. package/dist/index.js +39 -39
  120. package/dist/maintenance/memory-governance.js +7 -7
  121. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +1 -1
  122. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -7
  123. package/dist/maintenance/rebuild-memory-projection.d.ts +1 -1
  124. package/dist/maintenance/rebuild-memory-projection.js +8 -8
  125. package/dist/{maintenance-CKwxpdRx.d.ts → maintenance-kBII2qUI.d.ts} +3 -2
  126. package/dist/mcp-memory-inspector-app.d.ts +6 -5
  127. package/dist/memory-worth-outcomes.d.ts +1 -1
  128. package/dist/namespaces/migrate.d.ts +1 -1
  129. package/dist/namespaces/migrate.js +8 -8
  130. package/dist/namespaces/storage.d.ts +1 -1
  131. package/dist/namespaces/storage.js +7 -7
  132. package/dist/operator-doctor-corpus.js +10 -10
  133. package/dist/operator-toolkit.d.ts +1 -1
  134. package/dist/operator-toolkit.js +15 -15
  135. package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
  136. package/dist/orchestration/maintenance.d.ts +2 -2
  137. package/dist/orchestration/maintenance.js +12 -12
  138. package/dist/{orchestrator-BshGam-o.d.ts → orchestrator-DHy5LtTy.d.ts} +20 -35
  139. package/dist/orchestrator.d.ts +5 -4
  140. package/dist/orchestrator.js +39 -39
  141. package/dist/page-versioning.js +3 -3
  142. package/dist/schemas.d.ts +138 -138
  143. package/dist/semantic-consolidation.js +8 -8
  144. package/dist/semantic-rule-promotion.js +7 -7
  145. package/dist/semantic-rule-verifier.js +7 -7
  146. package/dist/{service-BIgVM7fL.d.ts → service-PT2_z7xm.d.ts} +1 -1
  147. package/dist/shared-context/manager.d.ts +8 -8
  148. package/dist/source-agent-qualifier.js +7 -7
  149. package/dist/{storage-CVfGWqCI.d.ts → storage-BmVudhk4.d.ts} +46 -27
  150. package/dist/storage.d.ts +1 -1
  151. package/dist/storage.js +6 -6
  152. package/dist/temporal-supersession.d.ts +51 -26
  153. package/dist/temporal-supersession.js +3 -1
  154. package/dist/tier-migration.d.ts +1 -1
  155. package/dist/transfer/capsule-import.js +4 -4
  156. package/dist/transfer/types.d.ts +66 -66
  157. package/dist/verified-recall.js +7 -7
  158. package/package.json +2 -2
  159. package/src/lifecycle/tombstones.ts +30 -0
  160. package/src/orchestration/consolidation-run.ts +324 -106
  161. package/src/orchestration/contradiction-linking-coordinator.ts +237 -64
  162. package/src/orchestration/dependency-propagation-delivery.test.ts +2353 -0
  163. package/src/orchestration/dependency-propagation-delivery.ts +1143 -0
  164. package/src/orchestration/dependency-propagation-queue-state.ts +220 -0
  165. package/src/orchestration/dependency-propagation-recovery-replay.ts +237 -0
  166. package/src/orchestration/dependency-propagation-replay-reconciliation.ts +73 -0
  167. package/src/orchestration/dependency-propagation-triggers.test.ts +1069 -11
  168. package/src/orchestration/dependency-propagation.test.ts +82 -31
  169. package/src/orchestration/dependency-propagation.ts +55 -17
  170. package/src/orchestration/extraction-persist-deps.ts +2 -2
  171. package/src/orchestration/extraction-persist.ts +14 -15
  172. package/src/orchestration/extraction-queue-coordinator.ts +23 -1
  173. package/src/orchestration/extraction-shutdown.ts +29 -0
  174. package/src/orchestration/maintenance.ts +13 -7
  175. package/src/orchestration/turn-ingestion.ts +5 -1
  176. package/src/orchestrator-background-writes.test.ts +265 -1
  177. package/src/orchestrator-extraction-queue.test.ts +39 -0
  178. package/src/orchestrator.ts +59 -58
  179. package/src/storage/committed-invalidation.ts +34 -0
  180. package/src/storage/deletion-revision-store.ts +529 -0
  181. package/src/storage/supersession-audit.ts +41 -0
  182. package/src/storage/supersession-side-effects.ts +52 -25
  183. package/src/storage-contract/failure-semantics.test.ts +12 -0
  184. package/src/storage-contract/round-trip.test.ts +184 -2
  185. package/src/storage-deletion-revisions.test.ts +213 -1
  186. package/src/storage-frontmatter-cas.test.ts +141 -0
  187. package/src/storage-memory-lifecycle-ledger.test.ts +372 -0
  188. package/src/storage-replay.test.ts +149 -0
  189. package/src/storage.ts +345 -358
  190. package/src/temporal-supersession.test.ts +431 -2
  191. package/src/temporal-supersession.ts +567 -107
  192. package/src/testing/subjects/dependency-propagation.test.ts +179 -38
  193. package/dist/chunk-ENTCVTQR.js.map +0 -1
  194. package/dist/chunk-EWMTOVMY.js +0 -484
  195. package/dist/chunk-EWMTOVMY.js.map +0 -1
  196. package/dist/chunk-OYESWJ43.js.map +0 -1
  197. package/dist/chunk-VBHPOWBJ.js.map +0 -1
  198. package/dist/chunk-ZHY5TMJG.js.map +0 -1
  199. /package/dist/{capsule-merge-XMFBXVDP.js.map → capsule-merge-HCJJMLPS.js.map} +0 -0
  200. /package/dist/{chunk-PWN32TAG.js.map → chunk-2ASAS3CQ.js.map} +0 -0
  201. /package/dist/{chunk-WQM326GS.js.map → chunk-2LY6GIIE.js.map} +0 -0
  202. /package/dist/{chunk-JL43BVDE.js.map → chunk-2OIQDTDC.js.map} +0 -0
  203. /package/dist/{chunk-33SV3E43.js.map → chunk-3OY7KXHM.js.map} +0 -0
  204. /package/dist/{chunk-AIUBBHSF.js.map → chunk-5UZ6UCOZ.js.map} +0 -0
  205. /package/dist/{chunk-UT5BV5OG.js.map → chunk-7HXZJAID.js.map} +0 -0
  206. /package/dist/{chunk-M7BXDKPR.js.map → chunk-7M6NYQWB.js.map} +0 -0
  207. /package/dist/{chunk-TPTECFWR.js.map → chunk-B6LZ4IBH.js.map} +0 -0
  208. /package/dist/{chunk-J5LLX6IL.js.map → chunk-BF6SXNQJ.js.map} +0 -0
  209. /package/dist/{chunk-4PC4PRJE.js.map → chunk-BKIFTXHR.js.map} +0 -0
  210. /package/dist/{chunk-VVG2BOKA.js.map → chunk-BMEYMKWX.js.map} +0 -0
  211. /package/dist/{chunk-DCZBSE4F.js.map → chunk-D5YOZ66N.js.map} +0 -0
  212. /package/dist/{chunk-BHXYAVUM.js.map → chunk-DLG5V6ND.js.map} +0 -0
  213. /package/dist/{chunk-52UKFTUK.js.map → chunk-F4EU6VUC.js.map} +0 -0
  214. /package/dist/{chunk-M4A44LUA.js.map → chunk-GSTIBXBW.js.map} +0 -0
  215. /package/dist/{chunk-W5XVQFLM.js.map → chunk-GXFSQ6SD.js.map} +0 -0
  216. /package/dist/{chunk-XADV5IZ3.js.map → chunk-HDIUYMSB.js.map} +0 -0
  217. /package/dist/{chunk-FXTYIA46.js.map → chunk-J272WKX2.js.map} +0 -0
  218. /package/dist/{chunk-JYL3BVVZ.js.map → chunk-JSEJH2YQ.js.map} +0 -0
  219. /package/dist/{chunk-5UDMFE3Q.js.map → chunk-MLXZD4WM.js.map} +0 -0
  220. /package/dist/{chunk-PUSLMZBQ.js.map → chunk-NOEMGE56.js.map} +0 -0
  221. /package/dist/{chunk-C5QLI33R.js.map → chunk-QINJITDS.js.map} +0 -0
  222. /package/dist/{chunk-45BRYON6.js.map → chunk-QPOHMO6M.js.map} +0 -0
  223. /package/dist/{chunk-ZQOCRIGA.js.map → chunk-QYN5PXDZ.js.map} +0 -0
  224. /package/dist/{chunk-PA45XCUD.js.map → chunk-RANKJI5T.js.map} +0 -0
  225. /package/dist/{chunk-37ZO6LLJ.js.map → chunk-RYLEDKQ2.js.map} +0 -0
  226. /package/dist/{chunk-CV6BBVYV.js.map → chunk-TEMSWHQD.js.map} +0 -0
  227. /package/dist/{chunk-QGR6OVGT.js.map → chunk-VFSAWRMZ.js.map} +0 -0
  228. /package/dist/{chunk-NLK2VZYZ.js.map → chunk-VXCNQYTC.js.map} +0 -0
  229. /package/dist/{chunk-AZ2QBGYB.js.map → chunk-W7Y5JDVB.js.map} +0 -0
  230. /package/dist/{chunk-4F6X3RXW.js.map → chunk-X57M2Q4I.js.map} +0 -0
  231. /package/dist/{chunk-6MTSFX7A.js.map → chunk-X7XEDOSL.js.map} +0 -0
  232. /package/dist/{chunk-UEVLYVNW.js.map → chunk-YXKVLYXU.js.map} +0 -0
  233. /package/dist/{chunk-OQAL6SIR.js.map → chunk-ZMSZVISA.js.map} +0 -0
  234. /package/dist/{forget-UJDJHG6R.js.map → forget-HDS4N6CG.js.map} +0 -0
@@ -1,10 +1,20 @@
1
1
  import assert from "node:assert/strict";
2
- import { mkdtemp, rm } from "node:fs/promises";
2
+ import { mkdtemp, rm, stat } from "node:fs/promises";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  import test from "node:test";
6
6
  import { parseConfig } from "./config.js";
7
7
  import { Orchestrator } from "./orchestrator.js";
8
+ type ShutdownTestSurface = {
9
+ extractionQueueCoordinator: { pauseAndDrain(): Promise<boolean> };
10
+ dependencyPropagationDelivery: { shutdown(): Promise<void> };
11
+ };
12
+ function stubOrchestratorInit(orchestrator: Orchestrator): void {
13
+ const initCoordinator = (orchestrator as unknown as {
14
+ orchestratorInitCoordinator: { initialize: () => Promise<void> };
15
+ }).orchestratorInitCoordinator;
16
+ initCoordinator.initialize = async () => {};
17
+ }
8
18
 
9
19
  test("destroy cancels maintenance and waits for tracked writes before disposing search backends", async () => {
10
20
  const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-background-writes-"));
@@ -53,6 +63,260 @@ test("destroy cancels maintenance and waits for tracked writes before disposing
53
63
  }
54
64
  });
55
65
 
66
+ test("disabled propagation does not construct the lazy delivery during initialize", async () => {
67
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-disabled-propagation-"));
68
+ const orchestrator = new Orchestrator(
69
+ parseConfig({
70
+ openaiApiKey: "sk-test",
71
+ memoryDir,
72
+ workspaceDir: path.join(memoryDir, "workspace"),
73
+ qmdEnabled: false,
74
+ knowledgeIndexEnabled: false,
75
+ identityContinuityEnabled: false,
76
+ transcriptEnabled: false,
77
+ hourlySummariesEnabled: false,
78
+ compoundingEnabled: false,
79
+ }),
80
+ );
81
+ stubOrchestratorInit(orchestrator);
82
+ const internals = orchestrator as unknown as {
83
+ _dependencyPropagationDelivery: unknown;
84
+ extractionPersistCoordinator: { dependencyPropagationDelivery: unknown };
85
+ };
86
+ try {
87
+ assert.equal(internals._dependencyPropagationDelivery, undefined);
88
+ assert.equal(internals.extractionPersistCoordinator.dependencyPropagationDelivery, undefined);
89
+ await orchestrator.initialize();
90
+ assert.equal(internals._dependencyPropagationDelivery, undefined);
91
+ assert.equal(internals.extractionPersistCoordinator.dependencyPropagationDelivery, undefined);
92
+ } finally {
93
+ await orchestrator.destroy();
94
+ await rm(memoryDir, { recursive: true, force: true });
95
+ }
96
+ });
97
+
98
+ test("destroy waits for startup propagation recovery before shutdown", async () => {
99
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-recovery-shutdown-"));
100
+ const orchestrator = new Orchestrator(
101
+ parseConfig({
102
+ openaiApiKey: "sk-test",
103
+ memoryDir,
104
+ workspaceDir: path.join(memoryDir, "workspace"),
105
+ qmdEnabled: false,
106
+ dependencyPropagation: {
107
+ enabled: true,
108
+ linkTypes: ["supports"],
109
+ maxDependents: 10,
110
+ timeoutMs: 100,
111
+ dryRun: false,
112
+ },
113
+ }),
114
+ );
115
+ stubOrchestratorInit(orchestrator);
116
+ const recovery = Promise.withResolvers<void>();
117
+ let shutdown = false;
118
+ Reflect.set(orchestrator, "_dependencyPropagationDelivery", {
119
+ recover: () => recovery.promise,
120
+ shutdown: async () => {
121
+ shutdown = true;
122
+ },
123
+ });
124
+
125
+ let destroyPromise: Promise<void> | undefined;
126
+ try {
127
+ await orchestrator.initialize();
128
+ let destroySettled = false;
129
+ destroyPromise = orchestrator.destroy().then(() => {
130
+ destroySettled = true;
131
+ });
132
+ await new Promise<void>((resolve) => setImmediate(resolve));
133
+ assert.equal(destroySettled, false, "destroy must await startup recovery");
134
+ assert.equal(shutdown, false, "delivery must remain live during recovery");
135
+
136
+ recovery.resolve();
137
+ await destroyPromise;
138
+ assert.equal(shutdown, true);
139
+ } finally {
140
+ recovery.resolve();
141
+ await (destroyPromise ?? orchestrator.destroy());
142
+ await rm(memoryDir, { recursive: true, force: true });
143
+ }
144
+ });
145
+
146
+ test("enabled propagation recovery rejection does not block initialize", async () => {
147
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-recovery-rejection-"));
148
+ const orchestrator = new Orchestrator(
149
+ parseConfig({
150
+ openaiApiKey: "sk-test",
151
+ memoryDir,
152
+ workspaceDir: path.join(memoryDir, "workspace"),
153
+ qmdEnabled: false,
154
+ dependencyPropagation: {
155
+ enabled: true,
156
+ linkTypes: ["supports"],
157
+ maxDependents: 10,
158
+ timeoutMs: 100,
159
+ dryRun: false,
160
+ },
161
+ }),
162
+ );
163
+ stubOrchestratorInit(orchestrator);
164
+ let recoverCalls = 0;
165
+ Reflect.set(orchestrator, "_dependencyPropagationDelivery", {
166
+ recover: async () => {
167
+ recoverCalls += 1;
168
+ throw new Error("synthetic recovery failure");
169
+ },
170
+ shutdown: async () => {},
171
+ });
172
+ try {
173
+ await orchestrator.initialize();
174
+ assert.equal(recoverCalls, 1);
175
+ } finally {
176
+ await orchestrator.destroy();
177
+ await rm(memoryDir, { recursive: true, force: true });
178
+ }
179
+ });
180
+
181
+ test("enabled propagation recovery tolerates a missing queue directory", async () => {
182
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-recovery-no-queue-"));
183
+ const orchestrator = new Orchestrator(
184
+ parseConfig({
185
+ openaiApiKey: "sk-test",
186
+ memoryDir,
187
+ workspaceDir: path.join(memoryDir, "workspace"),
188
+ qmdEnabled: false,
189
+ dependencyPropagation: {
190
+ enabled: true,
191
+ linkTypes: ["supports"],
192
+ maxDependents: 10,
193
+ timeoutMs: 100,
194
+ dryRun: false,
195
+ },
196
+ }),
197
+ );
198
+ stubOrchestratorInit(orchestrator);
199
+ const queueRoot = path.join(memoryDir, "state", "dependency-propagation");
200
+ try {
201
+ await orchestrator.initialize();
202
+ const recovery = (orchestrator as unknown as {
203
+ _dependencyPropagationRecovery: Promise<void> | null;
204
+ })._dependencyPropagationRecovery;
205
+ assert.ok(recovery, "enabled initialization must retain its recovery promise");
206
+ await recovery;
207
+ await assert.rejects(() => stat(queueRoot), /ENOENT/);
208
+ } finally {
209
+ await orchestrator.destroy();
210
+ await rm(memoryDir, { recursive: true, force: true });
211
+ }
212
+ });
213
+
214
+ test("destroy drains consolidation producers before propagation delivery", async () => {
215
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-destroy-propagation-order-"));
216
+ const orchestrator = new Orchestrator(
217
+ parseConfig({
218
+ openaiApiKey: "sk-test",
219
+ memoryDir,
220
+ workspaceDir: path.join(memoryDir, "workspace"),
221
+ qmdEnabled: false,
222
+ knowledgeIndexEnabled: false,
223
+ identityContinuityEnabled: false,
224
+ transcriptEnabled: false,
225
+ hourlySummariesEnabled: false,
226
+ compoundingEnabled: false,
227
+ }),
228
+ );
229
+ const order: string[] = [];
230
+ const scheduler = (orchestrator as unknown as { maintenanceScheduler: { dispose(): Promise<void> } })
231
+ .maintenanceScheduler;
232
+ const extractionQueue = (orchestrator as unknown as { extractionQueueCoordinator: { waitForIdle(): Promise<boolean> } })
233
+ .extractionQueueCoordinator;
234
+ const delivery = (orchestrator as unknown as { dependencyPropagationDelivery: { shutdown(): Promise<void> } })
235
+ .dependencyPropagationDelivery;
236
+ const originalSchedulerDispose = scheduler.dispose.bind(scheduler);
237
+ const originalExtractionWait = extractionQueue.waitForIdle.bind(extractionQueue);
238
+ const originalDeliveryShutdown = delivery.shutdown.bind(delivery);
239
+ scheduler.dispose = async () => {
240
+ order.push("scheduler");
241
+ await originalSchedulerDispose();
242
+ };
243
+ extractionQueue.waitForIdle = async () => {
244
+ order.push("extraction");
245
+ return originalExtractionWait();
246
+ };
247
+ delivery.shutdown = async () => {
248
+ order.push("delivery");
249
+ await originalDeliveryShutdown();
250
+ };
251
+
252
+ try {
253
+ await orchestrator.destroy();
254
+ assert.deepEqual(order.slice(0, 3), ["scheduler", "extraction", "delivery"]);
255
+ const internals = orchestrator as unknown as {
256
+ _dependencyPropagationDelivery: unknown;
257
+ dependencyPropagationDelivery: unknown;
258
+ };
259
+ assert.equal(internals._dependencyPropagationDelivery, undefined);
260
+ assert.throws(
261
+ () => internals.dependencyPropagationDelivery,
262
+ /orchestrator has been destroyed/,
263
+ "destroyed orchestrators must not recreate delivery",
264
+ );
265
+ } finally {
266
+ await rm(memoryDir, { recursive: true, force: true });
267
+ }
268
+ });
269
+ test("destroy continues cleanup when extraction drain times out", async () => {
270
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-destroy-propagation-timeout-"));
271
+ const orchestrator = new Orchestrator(
272
+ parseConfig({
273
+ openaiApiKey: "sk-test",
274
+ memoryDir,
275
+ workspaceDir: path.join(memoryDir, "workspace"),
276
+ qmdEnabled: false,
277
+ knowledgeIndexEnabled: false,
278
+ identityContinuityEnabled: false,
279
+ transcriptEnabled: false,
280
+ hourlySummariesEnabled: false,
281
+ compoundingEnabled: false,
282
+ }),
283
+ );
284
+ const internals = orchestrator as unknown as ShutdownTestSurface & {
285
+ _dependencyPropagationDelivery: unknown;
286
+ qmd: { dispose?: () => void | Promise<void> };
287
+ };
288
+ const extractionQueue = internals.extractionQueueCoordinator;
289
+ const delivery = internals.dependencyPropagationDelivery;
290
+ let shutdownCalls = 0;
291
+ let qmdDisposed = false;
292
+ const originalShutdown = delivery.shutdown.bind(delivery);
293
+ const originalQmdDispose = internals.qmd.dispose?.bind(internals.qmd);
294
+ delivery.shutdown = async () => {
295
+ shutdownCalls += 1;
296
+ await originalShutdown();
297
+ };
298
+ internals.qmd.dispose = async () => {
299
+ qmdDisposed = true;
300
+ await originalQmdDispose?.();
301
+ };
302
+ extractionQueue.pauseAndDrain = async () => false;
303
+
304
+ try {
305
+ await assert.rejects(
306
+ orchestrator.destroy(),
307
+ /extraction queue did not drain before teardown/,
308
+ );
309
+ assert.equal(shutdownCalls, 1, "delivery shutdown must run after a drain timeout");
310
+ assert.equal(internals._dependencyPropagationDelivery, undefined);
311
+ assert.equal(qmdDisposed, true, "later cleanup must run before the timeout is rethrown");
312
+ } finally {
313
+ extractionQueue.pauseAndDrain = async () => true;
314
+ await orchestrator.destroy();
315
+ await rm(memoryDir, { recursive: true, force: true });
316
+ }
317
+ });
318
+
319
+
56
320
  test("destroy waits for a recall-initiated last-recall write", async () => {
57
321
  const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-recall-background-write-"));
58
322
  const orchestrator = new Orchestrator(
@@ -183,3 +183,42 @@ test("logExtractionQueueFailure names the right layer (task vs processor)", asyn
183
183
  ),
184
184
  );
185
185
  });
186
+
187
+
188
+ test("pauseAndDrain waits for accepted work after its observation timeout", async () => {
189
+ const coordinator = createCoordinator();
190
+ const started = Promise.withResolvers<void>();
191
+ const release = Promise.withResolvers<void>();
192
+ const waitForIdle = coordinator.waitForIdle.bind(coordinator);
193
+ let waitCalls = 0;
194
+ let drainSettled = false;
195
+ coordinator.waitForIdle = async (timeoutMs?: number): Promise<boolean> => {
196
+ waitCalls += 1;
197
+ if (waitCalls === 1) return false;
198
+ return waitForIdle(timeoutMs);
199
+ };
200
+ coordinator.enqueue(async () => {
201
+ started.resolve();
202
+ await release.promise;
203
+ });
204
+ await started.promise;
205
+ const drain = coordinator.pauseAndDrain().then((result) => {
206
+ drainSettled = true;
207
+ return result;
208
+ });
209
+ await Promise.resolve();
210
+ assert.equal(drainSettled, false);
211
+ release.resolve();
212
+ assert.equal(await drain, true);
213
+ assert.equal(waitCalls, 2);
214
+ assert.equal(coordinator.isProcessing, false);
215
+ });
216
+
217
+ test("stopAccepting rejects new producers until resume", async () => {
218
+ const coordinator = createCoordinator();
219
+ coordinator.stopAccepting();
220
+ assert.equal(coordinator.enqueue(async () => {}), false);
221
+ coordinator.resumeAccepting();
222
+ assert.equal(coordinator.enqueue(async () => {}), true);
223
+ assert.equal(await coordinator.pauseAndDrain(), true);
224
+ });
@@ -122,6 +122,13 @@ import {
122
122
  } from "./orchestration/extraction-run.js";
123
123
  import { ConsolidationRunCoordinator } from "./orchestration/consolidation-run.js";
124
124
  import { ExtractionPersistCoordinator } from "./orchestration/extraction-persist.js";
125
+ import { drainExtractionAndShutdownDependencyPropagation } from "./orchestration/extraction-shutdown.js";
126
+ import {
127
+ DependencyPropagationDelivery,
128
+ type DependencyPropagationDeliveryPort,
129
+ } from "./orchestration/dependency-propagation-delivery.js";
130
+ import { isDependencyPropagationEnabled } from "./orchestration/dependency-propagation.js";
131
+ import { createDependencyPropagationReplayReconciliation } from "./orchestration/dependency-propagation-replay-reconciliation.js";
125
132
  import { RecallInternalCoordinator } from "./orchestration/recall-internal.js";
126
133
  import { RecallSearchPipelineCoordinator } from "./orchestration/recall-search-pipeline.js";
127
134
  import type { GraphRecallExpansionOptions, GraphRecallExpansionResult } from "./orchestration/graph-recall-seam.js";
@@ -371,7 +378,6 @@ import { resolveNamespaceCapabilities,
371
378
  resolveCapabilities,
372
379
  resolveGraphConstructionCapabilities,
373
380
  resolveMemoryLifecycleCapabilities,
374
- resolveIndexingCapabilities,
375
381
  resolveCreationMemoryCapabilities,
376
382
  type CapabilitySet,
377
383
  type GraphConstructionCapabilitySet,
@@ -733,26 +739,30 @@ export class Orchestrator {
733
739
  suppressedReasonCounts: Record<string, number>;
734
740
  } = { detected: 0, queued: 0, autoApplied: 0, suppressedReasonCounts: {} };
735
741
 
736
- /**
737
- * Background serial extraction queue coordinator (issue #1526 — moved
738
- * from inline `extractionQueue`/`queueProcessing` fields). Owns queue
739
- * state + scheduling + the serial drain + failure classification; the
740
- * orchestrator builds each task closure in `queueBufferedExtraction` and
741
- * hands it to `enqueue`.
742
- */
742
+ private _dependencyPropagationDelivery: DependencyPropagationDelivery | undefined;
743
+ private _dependencyPropagationRecovery: Promise<void> | null = null;
744
+ private destroyed = false;
745
+
746
+ private get dependencyPropagationDelivery(): DependencyPropagationDelivery {
747
+ if (this.destroyed) throw new Error("orchestrator has been destroyed");
748
+ if (!this._dependencyPropagationDelivery) {
749
+ this._dependencyPropagationDelivery = new DependencyPropagationDelivery({
750
+ queueRoot: path.join(this.config.memoryDir, "state", "dependency-propagation"),
751
+ config: this.config,
752
+ extraction: this.extraction,
753
+ getStorage: (namespace) => this.getStorage(namespace),
754
+ readQueueFile: (filePath) => this.storage.readDependencyPropagationQueueFile(filePath),
755
+ writeQueueFile: (filePath, content) => this.storage.writeDependencyPropagationQueueFile(filePath, content),
756
+ autoStart: true,
757
+ replayReconciliation: createDependencyPropagationReplayReconciliation({ getStorage: (namespace) => this.getStorage(namespace), indexPersistedMemory: (storage, memoryId) => this.indexPersistedMemory(storage, memoryId), removeFromIndex: (memoryId) => this.embeddingFallback.removeFromIndex(memoryId), config: this.config }),
758
+ });
759
+ }
760
+ return this._dependencyPropagationDelivery;
761
+ }
762
+
743
763
  readonly extractionQueueCoordinator: ExtractionQueueCoordinator;
744
- /**
745
- * Extraction-run pipeline coordinator (issue #1526 seam 15). Owns
746
- * `runExtraction` + dedupe fingerprint helpers + processed-fingerprint
747
- * recording. The orchestrator delegates and injects its own methods.
748
- */
749
764
  private _extractionRunCoordinator: ExtractionRunCoordinator | undefined;
750
765
 
751
- /**
752
- * Lazy getter: creates the ExtractionRunCoordinator on first access using
753
- * the orchestrator's live field references. Supports Object.create(prototype)
754
- * tests that set fields post-construction without invoking the constructor.
755
- */
756
766
  private get extractionRunCoordinator(): ExtractionRunCoordinator {
757
767
  if (!this._extractionRunCoordinator) {
758
768
  this._extractionRunCoordinator = new ExtractionRunCoordinator({
@@ -779,15 +789,6 @@ export class Orchestrator {
779
789
  }
780
790
  return this._extractionRunCoordinator;
781
791
  }
782
- /**
783
- * Consolidation-run coordinator (issue #1526 seam 17). Owns
784
- * the full consolidation maintenance pass (LLM merge/invalidate/update,
785
- * entity merge, commitment/TTL cleanup, lifecycle policy, compression
786
- * guideline learning, tier migration, fact archival, semantic consolidation,
787
- * identity consolidation, profile consolidation, summarization, topic
788
- * extraction, TMT rebuild). The orchestrator delegates and injects
789
- * its own methods + coordinators.
790
- */
791
792
  private _consolidationRunCoordinator: ConsolidationRunCoordinator | undefined;
792
793
  private get consolidationRunCoordinator(): ConsolidationRunCoordinator {
793
794
  if (!this._consolidationRunCoordinator) {
@@ -797,6 +798,8 @@ export class Orchestrator {
797
798
  getStorageRouter: () => this.storageRouter,
798
799
  getNamespaceCatalog: () => this.namespaceCatalog,
799
800
  getExtraction: () => this.extraction,
801
+ storageDirNamespace: (storageDir) => this.storageDirNamespace(storageDir),
802
+ getDependencyPropagationDelivery: () => this.dependencyPropagationDelivery,
800
803
  embeddingFallback: this.embeddingFallback,
801
804
  tmtBuilder: this.tmtBuilder,
802
805
  consolidationObservers: this.consolidationObservers,
@@ -816,11 +819,6 @@ export class Orchestrator {
816
819
  return this._consolidationRunCoordinator;
817
820
  }
818
821
 
819
- /**
820
- * Extraction-persist coordinator (issue #1526 seam 16). Owns the
821
- * `persistExtraction` pipeline. Lazy: created on first access so
822
- * Object.create(prototype) tests that set fields post-construction work.
823
- */
824
822
  private _extractionPersistCoordinator: ExtractionPersistCoordinator | undefined;
825
823
 
826
824
  private get extractionPersistCoordinator(): ExtractionPersistCoordinator {
@@ -829,8 +827,8 @@ export class Orchestrator {
829
827
  config: this.config,
830
828
  getStorageRouter: () => this.storageRouter,
831
829
  getThreading: () => this.threading,
830
+ getDependencyPropagationDelivery: () => this.dependencyPropagationDelivery,
832
831
  getLocalLlm: () => this.localLlm,
833
- getExtraction: () => this.extraction,
834
832
  getQmd: () => this.qmd,
835
833
  getJudgeVerdictCache: () => this.judgeVerdictCache,
836
834
  getJudgeDeferCounts: () => this.judgeDeferCounts,
@@ -960,36 +958,26 @@ export class Orchestrator {
960
958
  */
961
959
  async destroy(): Promise<void> {
962
960
  this.abortDeferredInit();
961
+ this.extractionQueueCoordinator.stopAccepting();
963
962
  if (this.wearablesAutoSyncHandle) {
964
- // Aborts in-flight provider fetches and waits for the tick to
965
- // settle, so nothing is writing or reindexing past destroy().
966
963
  await this.wearablesAutoSyncHandle.stop();
967
964
  this.wearablesAutoSyncHandle = null;
968
965
  }
969
966
  for (const svc of this.meetingsServiceByNamespace.values()) svc.dispose();
970
967
  await this.maintenanceScheduler.dispose();
968
+ await this._dependencyPropagationRecovery;
969
+ this._dependencyPropagationRecovery = null;
970
+ const extractionDrainError = await drainExtractionAndShutdownDependencyPropagation(
971
+ this.extractionQueueCoordinator,
972
+ this._dependencyPropagationDelivery,
973
+ );
974
+ this.destroyed = true;
975
+ this._dependencyPropagationDelivery = undefined;
971
976
  await drainRecallWrites(this);
972
- // PR #2016 finding 3: drain any deferred lock-timeout hash-index retries so a
973
- // short-lived writer's durable fact hash reaches disk before the process
974
- // exits. The per-index background retry is unref'd (it never keeps a
975
- // long-lived host alive), so a one-shot CLI could exit before it fires; this
976
- // drives it to completion inline at the shutdown boundary. Best-effort.
977
977
  await this.persistenceIndexCoordinator
978
978
  .drainContentHashReconcileRetries()
979
979
  .catch((err) => log.warn(`content-hash reconcile drain failed during destroy: ${err}`));
980
- // Issue #1909: persist any turns buffered within the debounce window BEFORE
981
- // flushing catalog touches (review round 11 finding 2). The buffer save fires
982
- // a coalesced namespace-catalog touch on an unref'd timer; flushing touches
983
- // first would let that shutdown-time touch queue after the flush and be lost.
984
- // Ordering it before flushPendingTouches folds the buffer-save's touch into
985
- // the flush below so both settle before destroy() returns.
986
- //
987
- // Graceful-shutdown durability contract (review round 14): the flush runs
988
- // with throwOnFailure so a failed buffer write is NOT silently swallowed.
989
- // flushPendingSave keeps the save pending on failure (in-memory turns are
990
- // retained), so we finish the rest of teardown in a finally block and then
991
- // rethrow — the host learns buffered turns did not reach disk instead of
992
- // destroy() reporting a clean shutdown and losing them on exit.
980
+ // Save buffered turns before catalog flush; rethrow save failures after teardown.
993
981
  let bufferFlushError: unknown;
994
982
  try {
995
983
  await this.buffer.flushPendingSave({ throwOnFailure: true });
@@ -997,8 +985,7 @@ export class Orchestrator {
997
985
  bufferFlushError = err;
998
986
  }
999
987
  try {
1000
- // Issue #1903: flush any coalesced namespace-catalog touches before teardown
1001
- // so a long-lived host does not drop buffered read/write timestamps.
988
+ // Flush catalog touches before backend disposal.
1002
989
  await this.namespaceCatalog.flushPendingTouches().catch(() => undefined);
1003
990
  this.prioritizedEmbedding?.dispose();
1004
991
  await this.namespaceSearchRouter.dispose();
@@ -1013,6 +1000,12 @@ export class Orchestrator {
1013
1000
  );
1014
1001
  throw bufferFlushError;
1015
1002
  }
1003
+ if (extractionDrainError !== undefined) {
1004
+ log.warn(
1005
+ `orchestrator.destroy: extraction queue did not drain before teardown: ${String(extractionDrainError)}`,
1006
+ );
1007
+ throw extractionDrainError;
1008
+ }
1016
1009
  }
1017
1010
  }
1018
1011
 
@@ -1297,7 +1290,6 @@ export class Orchestrator {
1297
1290
  );
1298
1291
  }
1299
1292
 
1300
-
1301
1293
  invalidateLiveContentHashIndex(): void {
1302
1294
  this.contentHashIndex = null;
1303
1295
  this.contentHashIndexesByStorageDir.clear();
@@ -1551,6 +1543,8 @@ export class Orchestrator {
1551
1543
  namespaceFromPath: (p) => this.namespaceFromPath(p),
1552
1544
  storageForNamespace: (namespace) => this.storageRouter.storageFor(namespace),
1553
1545
  getExtraction: () => this.extraction,
1546
+ storageDirNamespace: (storageDir) => this.storageDirNamespace(storageDir),
1547
+ getDependencyPropagationDelivery: () => this.dependencyPropagationDelivery,
1554
1548
  });
1555
1549
  this.modelRegistry = new ModelRegistry(config.memoryDir);
1556
1550
  this.graphRecallCoordinator = new GraphRecallCoordinator({
@@ -1957,8 +1951,15 @@ export class Orchestrator {
1957
1951
  }
1958
1952
 
1959
1953
  async initialize(): Promise<void> {
1960
- return this.orchestratorInitCoordinator.initialize(
1961
- );
1954
+ this.extractionQueueCoordinator.resumeAccepting();
1955
+ await this.orchestratorInitCoordinator.initialize();
1956
+ if (isDependencyPropagationEnabled(this.config)) {
1957
+ this._dependencyPropagationRecovery = this.dependencyPropagationDelivery
1958
+ .recover()
1959
+ .catch((err) => {
1960
+ log.warn(`dependency propagation startup recovery failed: ${err}`);
1961
+ });
1962
+ }
1962
1963
  }
1963
1964
 
1964
1965
  private async deferredInitialize(signal: AbortSignal): Promise<void> {
@@ -0,0 +1,34 @@
1
+ import { log } from "../logger.js";
2
+
3
+ export interface CommittedInvalidationCallbacks {
4
+ memoryId: string;
5
+ recordProof: (() => Promise<void>) | undefined;
6
+ clearProof: (() => Promise<void>) | undefined;
7
+ deleteMemory: () => Promise<boolean>;
8
+ afterDelete: () => Promise<void>;
9
+ }
10
+
11
+ async function clearProofFailOpen(callbacks: CommittedInvalidationCallbacks): Promise<void> {
12
+ await callbacks.clearProof?.().catch((error) => {
13
+ log.warn(`failed to clear invalidation proof for ${callbacks.memoryId}: ${error}`);
14
+ });
15
+ }
16
+
17
+ export async function runCommittedInvalidation(
18
+ callbacks: CommittedInvalidationCallbacks,
19
+ ): Promise<boolean> {
20
+ let deletionCompleted = false;
21
+ try {
22
+ await callbacks.recordProof?.();
23
+ if (!await callbacks.deleteMemory()) {
24
+ await clearProofFailOpen(callbacks);
25
+ return false;
26
+ }
27
+ deletionCompleted = true;
28
+ await callbacks.afterDelete();
29
+ return true;
30
+ } catch (error) {
31
+ if (!deletionCompleted) await clearProofFailOpen(callbacks);
32
+ throw error;
33
+ }
34
+ }