@remnic/core 1.1.1 → 1.1.3

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 (497) hide show
  1. package/dist/abort-error.js +1 -0
  2. package/dist/abstraction-nodes.js +1 -0
  3. package/dist/access-audit.js +1 -0
  4. package/dist/access-cli.js +76 -51
  5. package/dist/access-cli.js.map +1 -1
  6. package/dist/access-http.d.ts +50 -5
  7. package/dist/access-http.js +38 -16
  8. package/dist/access-idempotency.js +1 -0
  9. package/dist/access-mcp.d.ts +10 -5
  10. package/dist/access-mcp.js +37 -14
  11. package/dist/access-schema.d.ts +133 -13
  12. package/dist/access-schema.js +20 -1
  13. package/dist/access-service-_AEUMVyX.d.ts +1981 -0
  14. package/dist/access-service.d.ts +11 -6
  15. package/dist/access-service.js +39 -14
  16. package/dist/active-memory-bridge.js +1 -0
  17. package/dist/active-recall.js +1 -0
  18. package/dist/active-recall.js.map +1 -1
  19. package/dist/behavior-learner.js +1 -0
  20. package/dist/behavior-learner.js.map +1 -1
  21. package/dist/behavior-signals.js +1 -0
  22. package/dist/bootstrap.d.ts +6 -4
  23. package/dist/bootstrap.js +1 -0
  24. package/dist/boxes.js +1 -0
  25. package/dist/briefing.d.ts +9 -5
  26. package/dist/briefing.js +10 -6
  27. package/dist/buffer-surprise-report.js +1 -0
  28. package/dist/buffer-surprise.js +1 -0
  29. package/dist/buffer.d.ts +1 -1
  30. package/dist/buffer.js +1 -0
  31. package/dist/calibration.d.ts +8 -1
  32. package/dist/calibration.js +10 -2
  33. package/dist/calibration.js.map +1 -1
  34. package/dist/capsule-cli.d.ts +137 -0
  35. package/dist/capsule-cli.js +34 -0
  36. package/dist/capsule-crypto-5CYAGVC5.js +18 -0
  37. package/dist/capsule-export-NZQPOTQ4.js +17 -0
  38. package/dist/capsule-export-NZQPOTQ4.js.map +1 -0
  39. package/dist/capsule-import-SDCUXLEV.js +16 -0
  40. package/dist/capsule-import-SDCUXLEV.js.map +1 -0
  41. package/dist/capsule-merge-DI7PNQ2H.js +189 -0
  42. package/dist/capsule-merge-DI7PNQ2H.js.map +1 -0
  43. package/dist/causal-behavior.js +1 -0
  44. package/dist/causal-behavior.js.map +1 -1
  45. package/dist/causal-chain.js +1 -0
  46. package/dist/causal-consolidation.js +12 -9
  47. package/dist/causal-consolidation.js.map +1 -1
  48. package/dist/causal-retrieval.js +2 -1
  49. package/dist/causal-retrieval.js.map +1 -1
  50. package/dist/causal-trajectory-graph.js +4 -1
  51. package/dist/causal-trajectory-graph.js.map +1 -1
  52. package/dist/causal-trajectory.js +2 -1
  53. package/dist/chunk-2LSZVONP.js +67 -0
  54. package/dist/chunk-2LSZVONP.js.map +1 -0
  55. package/dist/chunk-32KD5IHZ.js +245 -0
  56. package/dist/chunk-32KD5IHZ.js.map +1 -0
  57. package/dist/{chunk-VDX363PS.js → chunk-34F3PLWZ.js} +10 -3
  58. package/dist/chunk-34F3PLWZ.js.map +1 -0
  59. package/dist/chunk-3KIS4VGT.js +228 -0
  60. package/dist/chunk-3KIS4VGT.js.map +1 -0
  61. package/dist/chunk-3LCWFNVS.js +350 -0
  62. package/dist/chunk-3LCWFNVS.js.map +1 -0
  63. package/dist/chunk-43EKP2UK.js +26 -0
  64. package/dist/chunk-43EKP2UK.js.map +1 -0
  65. package/dist/chunk-457A4P3L.js +119 -0
  66. package/dist/chunk-457A4P3L.js.map +1 -0
  67. package/dist/{chunk-KUB6JU6H.js → chunk-47WOM4YW.js} +2 -2
  68. package/dist/{chunk-HK3FGIEW.js → chunk-4PLGJRBV.js} +656 -20
  69. package/dist/chunk-4PLGJRBV.js.map +1 -0
  70. package/dist/{chunk-BGJGXLZ7.js → chunk-55FXRRSJ.js} +11 -8
  71. package/dist/chunk-55FXRRSJ.js.map +1 -0
  72. package/dist/{chunk-ULYOGL6R.js → chunk-5HRY2WRF.js} +7 -3
  73. package/dist/chunk-5HRY2WRF.js.map +1 -0
  74. package/dist/chunk-6TBWYBJ3.js +236 -0
  75. package/dist/chunk-6TBWYBJ3.js.map +1 -0
  76. package/dist/chunk-74EMIVE4.js +329 -0
  77. package/dist/chunk-74EMIVE4.js.map +1 -0
  78. package/dist/chunk-74WWN7ZW.js +82 -0
  79. package/dist/chunk-74WWN7ZW.js.map +1 -0
  80. package/dist/{chunk-B5WXLVDY.js → chunk-7GCMLT7J.js} +245 -25
  81. package/dist/chunk-7GCMLT7J.js.map +1 -0
  82. package/dist/chunk-A6XUJE5D.js +126 -0
  83. package/dist/chunk-A6XUJE5D.js.map +1 -0
  84. package/dist/chunk-AJA46VX5.js +393 -0
  85. package/dist/chunk-AJA46VX5.js.map +1 -0
  86. package/dist/{chunk-DFTTJYSO.js → chunk-AKUCB2OG.js} +525 -24
  87. package/dist/chunk-AKUCB2OG.js.map +1 -0
  88. package/dist/chunk-ASIQZXYO.js +277 -0
  89. package/dist/chunk-ASIQZXYO.js.map +1 -0
  90. package/dist/{chunk-ZEM3OK2K.js → chunk-B2TL6GA2.js} +3 -3
  91. package/dist/chunk-BJMBJZ2Y.js +290 -0
  92. package/dist/chunk-BJMBJZ2Y.js.map +1 -0
  93. package/dist/chunk-BT7NVCML.js +79 -0
  94. package/dist/chunk-BT7NVCML.js.map +1 -0
  95. package/dist/chunk-CK5NTM2S.js +454 -0
  96. package/dist/chunk-CK5NTM2S.js.map +1 -0
  97. package/dist/{chunk-3GXCSUXR.js → chunk-CRU27Q4J.js} +2 -2
  98. package/dist/{chunk-F5VP6YCB.js → chunk-DCE6SQLA.js} +572 -155
  99. package/dist/chunk-DCE6SQLA.js.map +1 -0
  100. package/dist/{chunk-CUPFXL3J.js → chunk-DHRQHX36.js} +4 -4
  101. package/dist/chunk-DHRQHX36.js.map +1 -0
  102. package/dist/{chunk-GKFXUTJ2.js → chunk-DR7MCMPS.js} +981 -61
  103. package/dist/chunk-DR7MCMPS.js.map +1 -0
  104. package/dist/chunk-FP2373TW.js +149 -0
  105. package/dist/chunk-FP2373TW.js.map +1 -0
  106. package/dist/{chunk-RBBWYEFJ.js → chunk-G2WADRQ3.js} +1 -1
  107. package/dist/chunk-G7D6GZ5J.js +48 -0
  108. package/dist/chunk-G7D6GZ5J.js.map +1 -0
  109. package/dist/chunk-H7XKCNR6.js +60 -0
  110. package/dist/chunk-H7XKCNR6.js.map +1 -0
  111. package/dist/{chunk-VYM3VWOF.js → chunk-IM3JSE73.js} +966 -329
  112. package/dist/chunk-IM3JSE73.js.map +1 -0
  113. package/dist/chunk-IXEJRKCZ.js +18 -0
  114. package/dist/chunk-IXEJRKCZ.js.map +1 -0
  115. package/dist/chunk-IYY4MCPG.js +275 -0
  116. package/dist/chunk-IYY4MCPG.js.map +1 -0
  117. package/dist/{chunk-BK2EFTE2.js → chunk-JWSENLQI.js} +508 -28
  118. package/dist/chunk-JWSENLQI.js.map +1 -0
  119. package/dist/chunk-KNKUID7G.js +183 -0
  120. package/dist/chunk-KNKUID7G.js.map +1 -0
  121. package/dist/chunk-L2IO2QPY.js +2036 -0
  122. package/dist/chunk-L2IO2QPY.js.map +1 -0
  123. package/dist/{chunk-SPI27QT6.js → chunk-L5IIGA5V.js} +9 -4
  124. package/dist/chunk-L5IIGA5V.js.map +1 -0
  125. package/dist/{chunk-RGLL5SPU.js → chunk-LVYGDT5V.js} +56 -82
  126. package/dist/chunk-LVYGDT5V.js.map +1 -0
  127. package/dist/{chunk-ZAIM4TUE.js → chunk-LW2NMHDW.js} +46 -1
  128. package/dist/chunk-LW2NMHDW.js.map +1 -0
  129. package/dist/{chunk-3OGMS3PE.js → chunk-LZRYQK6L.js} +3 -2
  130. package/dist/chunk-LZRYQK6L.js.map +1 -0
  131. package/dist/chunk-MDYG7VI7.js +48 -0
  132. package/dist/chunk-MDYG7VI7.js.map +1 -0
  133. package/dist/chunk-MXC3AP5I.js +74 -0
  134. package/dist/chunk-MXC3AP5I.js.map +1 -0
  135. package/dist/{chunk-S3EEFKNY.js → chunk-N7X62G74.js} +26 -11
  136. package/dist/chunk-N7X62G74.js.map +1 -0
  137. package/dist/chunk-NN3TS5BM.js +147 -0
  138. package/dist/chunk-NN3TS5BM.js.map +1 -0
  139. package/dist/chunk-OA3L7BFR.js +183 -0
  140. package/dist/chunk-OA3L7BFR.js.map +1 -0
  141. package/dist/{chunk-LK6SGL53.js → chunk-OR64ZGRZ.js} +3 -2
  142. package/dist/chunk-OR64ZGRZ.js.map +1 -0
  143. package/dist/chunk-OZHRDTDX.js +240 -0
  144. package/dist/chunk-OZHRDTDX.js.map +1 -0
  145. package/dist/chunk-PCUKNJAZ.js +165 -0
  146. package/dist/chunk-PCUKNJAZ.js.map +1 -0
  147. package/dist/{chunk-6PFRXT4K.js → chunk-PFV5C235.js} +11 -6
  148. package/dist/chunk-PFV5C235.js.map +1 -0
  149. package/dist/chunk-PZ5AY32C.js +10 -0
  150. package/dist/chunk-PZ5AY32C.js.map +1 -0
  151. package/dist/{chunk-XZ2TIKGC.js → chunk-Q7FJ5ZHM.js} +30 -10
  152. package/dist/chunk-Q7FJ5ZHM.js.map +1 -0
  153. package/dist/{chunk-7I7FKFZH.js → chunk-R2L7SUX2.js} +6 -6
  154. package/dist/{chunk-JL2PU6AI.js → chunk-R2XRID2N.js} +2 -2
  155. package/dist/{chunk-WCLICCGB.js → chunk-RILIVK4O.js} +91 -4
  156. package/dist/chunk-RILIVK4O.js.map +1 -0
  157. package/dist/{chunk-C2EFFULQ.js → chunk-RK2Y4XOM.js} +163 -20
  158. package/dist/chunk-RK2Y4XOM.js.map +1 -0
  159. package/dist/{chunk-TP4FZJIZ.js → chunk-RULE4VG5.js} +5 -1
  160. package/dist/chunk-RULE4VG5.js.map +1 -0
  161. package/dist/{chunk-PVPWZSSI.js → chunk-SMA4IMHV.js} +19 -3
  162. package/dist/chunk-SMA4IMHV.js.map +1 -0
  163. package/dist/{chunk-WVVA7F5A.js → chunk-SS253RXF.js} +30 -16
  164. package/dist/chunk-SS253RXF.js.map +1 -0
  165. package/dist/chunk-TUFG6VXY.js +875 -0
  166. package/dist/chunk-TUFG6VXY.js.map +1 -0
  167. package/dist/chunk-TYEOAFH3.js +251 -0
  168. package/dist/chunk-TYEOAFH3.js.map +1 -0
  169. package/dist/chunk-UKJAGEXH.js +260 -0
  170. package/dist/chunk-UKJAGEXH.js.map +1 -0
  171. package/dist/{chunk-KVBLZUKV.js → chunk-USFPPRAF.js} +93 -3
  172. package/dist/chunk-USFPPRAF.js.map +1 -0
  173. package/dist/{chunk-EPQJM2GC.js → chunk-VTJVUHRK.js} +22 -36
  174. package/dist/chunk-VTJVUHRK.js.map +1 -0
  175. package/dist/{chunk-O5ETUNBT.js → chunk-VTU2B4VF.js} +7 -3
  176. package/dist/chunk-VTU2B4VF.js.map +1 -0
  177. package/dist/chunk-WIICJPET.js +45 -0
  178. package/dist/chunk-WIICJPET.js.map +1 -0
  179. package/dist/{chunk-VBVG2M5G.js → chunk-WPGJYVUH.js} +6 -2
  180. package/dist/chunk-WPGJYVUH.js.map +1 -0
  181. package/dist/{chunk-YNQKWQT4.js → chunk-WSZIHQBK.js} +31 -11
  182. package/dist/{chunk-YNQKWQT4.js.map → chunk-WSZIHQBK.js.map} +1 -1
  183. package/dist/{chunk-NZLQTHS5.js → chunk-WW3QQF4H.js} +4 -1
  184. package/dist/chunk-WW3QQF4H.js.map +1 -0
  185. package/dist/{chunk-FVA6TGI3.js → chunk-Y3WQ4ZWK.js} +42 -2
  186. package/dist/chunk-Y3WQ4ZWK.js.map +1 -0
  187. package/dist/chunk-YNJHCGDT.js +309 -0
  188. package/dist/chunk-YNJHCGDT.js.map +1 -0
  189. package/dist/{chunk-ALXMCZEU.js → chunk-Z2E7VW55.js} +6 -3
  190. package/dist/chunk-Z2E7VW55.js.map +1 -0
  191. package/dist/{chunk-INXV5JBT.js → chunk-ZGXSCMQN.js} +1992 -410
  192. package/dist/chunk-ZGXSCMQN.js.map +1 -0
  193. package/dist/{chunk-W6SL7OFG.js → chunk-ZTSE2ZJ6.js} +12 -2
  194. package/dist/{chunk-W6SL7OFG.js.map → chunk-ZTSE2ZJ6.js.map} +1 -1
  195. package/dist/chunking.js +1 -0
  196. package/dist/cipher-GVE2GQ5H.js +28 -0
  197. package/dist/cipher-GVE2GQ5H.js.map +1 -0
  198. package/dist/citations.js +1 -0
  199. package/dist/{cli-BkeRaYfk.d.ts → cli-x2APT9a6.d.ts} +26 -7
  200. package/dist/cli.d.ts +11 -6
  201. package/dist/cli.js +68 -34
  202. package/dist/codex-thread-key.js +1 -0
  203. package/dist/commitment-ledger.js +1 -0
  204. package/dist/compression-optimizer.js +1 -0
  205. package/dist/config.d.ts +2 -1
  206. package/dist/config.js +5 -2
  207. package/dist/connectors-cli-DFGtY2DB.d.ts +257 -0
  208. package/dist/connectors-cli.d.ts +2 -0
  209. package/dist/connectors-cli.js +22 -0
  210. package/dist/connectors-cli.js.map +1 -0
  211. package/dist/consolidation-operator.d.ts +65 -5
  212. package/dist/consolidation-operator.js +6 -1
  213. package/dist/consolidation-provenance-check.d.ts +1 -1
  214. package/dist/consolidation-provenance-check.js +3 -2
  215. package/dist/consolidation-undo.d.ts +1 -1
  216. package/dist/consolidation-undo.js +1 -0
  217. package/dist/consolidation-undo.js.map +1 -1
  218. package/dist/{contradiction-review-WIUBAR52.js → contradiction-review-5LTTVDQV.js} +2 -1
  219. package/dist/contradiction-review-5LTTVDQV.js.map +1 -0
  220. package/dist/{contradiction-scan-E3GJTI4F.js → contradiction-scan-3Z6YW7YA.js} +2 -1
  221. package/dist/{contradiction-scan-E3GJTI4F.js.map → contradiction-scan-3Z6YW7YA.js.map} +1 -1
  222. package/dist/cross-namespace-budget.js +1 -0
  223. package/dist/cue-anchors.js +1 -0
  224. package/dist/dashboard-runtime.js +1 -0
  225. package/dist/day-summary.js +1 -0
  226. package/dist/delinearize.js +1 -0
  227. package/dist/direct-answer-wiring.js +1 -0
  228. package/dist/direct-answer.js +1 -0
  229. package/dist/dreams-ledger-LR2NBAZE.js +286 -0
  230. package/dist/dreams-ledger-LR2NBAZE.js.map +1 -0
  231. package/dist/embedding-fallback.js +3 -1
  232. package/dist/{engine-F3GOXGE5.js → engine-ICC2DSQF.js} +10 -7
  233. package/dist/engine-ICC2DSQF.js.map +1 -0
  234. package/dist/entity-retrieval.d.ts +1 -1
  235. package/dist/entity-retrieval.js +9 -6
  236. package/dist/entity-schema.js +1 -0
  237. package/dist/evals.js +1 -0
  238. package/dist/evidence-pack.d.ts +16 -0
  239. package/dist/evidence-pack.js +8 -0
  240. package/dist/evidence-pack.js.map +1 -0
  241. package/dist/explicit-capture.d.ts +6 -4
  242. package/dist/explicit-capture.js +1 -0
  243. package/dist/extraction-judge-telemetry.js +1 -0
  244. package/dist/extraction-judge-training.js +1 -0
  245. package/dist/extraction-judge.js +1 -0
  246. package/dist/extraction.js +9 -8
  247. package/dist/fallback-llm.js +3 -2
  248. package/dist/first-start-migration-4MHQEOSD.js +263 -0
  249. package/dist/first-start-migration-4MHQEOSD.js.map +1 -0
  250. package/dist/forget-PLR6J5DN.js +69 -0
  251. package/dist/forget-PLR6J5DN.js.map +1 -0
  252. package/dist/framework-CyHYDcri.d.ts +153 -0
  253. package/dist/fs-utils-IRVUFB6G.js +30 -0
  254. package/dist/fs-utils-IRVUFB6G.js.map +1 -0
  255. package/dist/graph-dashboard-diff.js +1 -0
  256. package/dist/graph-dashboard-key.js +1 -0
  257. package/dist/graph-dashboard-parser.js +1 -0
  258. package/dist/graph-edge-decay-PWB63GRE.js +207 -0
  259. package/dist/graph-edge-decay-PWB63GRE.js.map +1 -0
  260. package/dist/graph-edge-reinforcement.d.ts +81 -0
  261. package/dist/graph-edge-reinforcement.js +24 -0
  262. package/dist/graph-edge-reinforcement.js.map +1 -0
  263. package/dist/graph-events.d.ts +87 -0
  264. package/dist/graph-events.js +14 -0
  265. package/dist/graph-events.js.map +1 -0
  266. package/dist/graph-recall.js +1 -0
  267. package/dist/graph-retrieval.js +1 -0
  268. package/dist/graph-snapshot.d.ts +112 -0
  269. package/dist/graph-snapshot.js +19 -0
  270. package/dist/graph-snapshot.js.map +1 -0
  271. package/dist/graph.d.ts +105 -7
  272. package/dist/graph.js +20 -3
  273. package/dist/harmonic-retrieval.js +1 -0
  274. package/dist/himem.js +1 -0
  275. package/dist/hygiene.js +1 -0
  276. package/dist/identity-continuity.js +1 -0
  277. package/dist/importance.js +1 -0
  278. package/dist/index.d.ts +562 -13
  279. package/dist/index.js +365 -96
  280. package/dist/index.js.map +1 -1
  281. package/dist/intent.js +1 -0
  282. package/dist/json-extract.js +1 -0
  283. package/dist/json-store.js +1 -0
  284. package/dist/kdf-7S6RWKLZ.js +26 -0
  285. package/dist/kdf-7S6RWKLZ.js.map +1 -0
  286. package/dist/legacy-hook-compat.js +1 -0
  287. package/dist/legacy-hook-compat.js.map +1 -1
  288. package/dist/lifecycle.js +1 -0
  289. package/dist/live-connectors-runner.d.ts +48 -0
  290. package/dist/live-connectors-runner.js +17 -0
  291. package/dist/live-connectors-runner.js.map +1 -0
  292. package/dist/local-llm.js +3 -2
  293. package/dist/logger.js +1 -0
  294. package/dist/memory-action-policy.js +1 -0
  295. package/dist/memory-cache.d.ts +2 -1
  296. package/dist/memory-cache.js +4 -1
  297. package/dist/memory-governance-KG52RITE.js +37 -0
  298. package/dist/memory-governance-KG52RITE.js.map +1 -0
  299. package/dist/memory-lifecycle-ledger-utils.d.ts +2 -1
  300. package/dist/memory-lifecycle-ledger-utils.js +4 -1
  301. package/dist/memory-projection-format.js +1 -0
  302. package/dist/{memory-projection-store-DeSXPh1j.d.ts → memory-projection-store-D3vBHS4J.d.ts} +1 -0
  303. package/dist/memory-projection-store.d.ts +1 -1
  304. package/dist/memory-projection-store.js +1 -0
  305. package/dist/memory-worth-bench.js +1 -0
  306. package/dist/memory-worth-bench.js.map +1 -1
  307. package/dist/memory-worth-filter.js +1 -0
  308. package/dist/memory-worth-outcomes.d.ts +1 -1
  309. package/dist/memory-worth-outcomes.js +1 -0
  310. package/dist/memory-worth.js +1 -0
  311. package/dist/metadata-FC3XPDRQ.js +21 -0
  312. package/dist/metadata-FC3XPDRQ.js.map +1 -0
  313. package/dist/migrate-from-identity-anchor-TTEDEJGX.js +8 -0
  314. package/dist/migrate-from-identity-anchor-TTEDEJGX.js.map +1 -0
  315. package/dist/model-registry.js +1 -0
  316. package/dist/models-json.js +1 -0
  317. package/dist/native-knowledge.js +1 -0
  318. package/dist/negative.js +1 -0
  319. package/dist/objective-state-writers.js +1 -0
  320. package/dist/objective-state-writers.js.map +1 -1
  321. package/dist/objective-state.js +1 -0
  322. package/dist/openai-chat-compat.js +1 -0
  323. package/dist/operator-toolkit.d.ts +46 -2
  324. package/dist/operator-toolkit.js +29 -17
  325. package/dist/opik-exporter.js +1 -0
  326. package/dist/opik-exporter.js.map +1 -1
  327. package/dist/{orchestrator-CmJ-NTdJ.d.ts → orchestrator-ChkesB8U.d.ts} +177 -13
  328. package/dist/orchestrator.d.ts +6 -4
  329. package/dist/orchestrator.js +58 -42
  330. package/dist/page-versioning.js +1 -0
  331. package/dist/path-RMTY5Y5A.js +9 -0
  332. package/dist/path-RMTY5Y5A.js.map +1 -0
  333. package/dist/patterns-cli.d.ts +160 -0
  334. package/dist/patterns-cli.js +29 -0
  335. package/dist/patterns-cli.js.map +1 -0
  336. package/dist/peers-6OSQ3NK6.js +44 -0
  337. package/dist/peers-6OSQ3NK6.js.map +1 -0
  338. package/dist/plugin-id.js +1 -0
  339. package/dist/policy-runtime.js +1 -0
  340. package/dist/{port-BADbLZU5.d.ts → port-hqGnoStS.d.ts} +6 -0
  341. package/dist/profiling.js +1 -0
  342. package/dist/purge-6ATBGT77.js +205 -0
  343. package/dist/purge-6ATBGT77.js.map +1 -0
  344. package/dist/qmd-recall-cache.d.ts +1 -1
  345. package/dist/qmd-recall-cache.js +1 -0
  346. package/dist/qmd.d.ts +2 -1
  347. package/dist/qmd.js +4 -3
  348. package/dist/reasoning-trace-recall.js +1 -0
  349. package/dist/reasoning-trace-types.js +1 -0
  350. package/dist/recall-audit-anomaly.js +1 -0
  351. package/dist/recall-audit.js +1 -0
  352. package/dist/recall-disclosure-escalation.d.ts +84 -0
  353. package/dist/recall-disclosure-escalation.js +14 -0
  354. package/dist/recall-disclosure-escalation.js.map +1 -0
  355. package/dist/recall-explain-renderer.js +4 -1
  356. package/dist/recall-mmr.js +1 -0
  357. package/dist/recall-qos.js +1 -0
  358. package/dist/recall-query-policy.js +1 -0
  359. package/dist/recall-state.d.ts +7 -0
  360. package/dist/recall-state.js +2 -1
  361. package/dist/recall-tag-filter.d.ts +56 -0
  362. package/dist/recall-tag-filter.js +14 -0
  363. package/dist/recall-tag-filter.js.map +1 -0
  364. package/dist/recall-tokenization.js +1 -0
  365. package/dist/recall-xray-cli.d.ts +9 -2
  366. package/dist/recall-xray-cli.js +9 -4
  367. package/dist/recall-xray-renderer.js +4 -1
  368. package/dist/recall-xray.d.ts +116 -2
  369. package/dist/recall-xray.js +9 -3
  370. package/dist/reconstruct.js +1 -0
  371. package/dist/release-changelog.js +2 -0
  372. package/dist/release-changelog.js.map +1 -1
  373. package/dist/relevance.js +1 -0
  374. package/dist/rerank.js +1 -0
  375. package/dist/{resolution-QBTDHTG7.js → resolution-YGIBORXI.js} +2 -1
  376. package/dist/{resolution-QBTDHTG7.js.map → resolution-YGIBORXI.js.map} +1 -1
  377. package/dist/resolve-auth-token.d.ts +51 -0
  378. package/dist/resolve-auth-token.js +12 -0
  379. package/dist/resolve-auth-token.js.map +1 -0
  380. package/dist/resolve-provider-secret.d.ts +13 -1
  381. package/dist/resolve-provider-secret.js +6 -1
  382. package/dist/resume-bundles.js +5 -4
  383. package/dist/retrieval-agents.d.ts +1 -1
  384. package/dist/retrieval-agents.js +1 -0
  385. package/dist/retrieval-tiers.js +1 -0
  386. package/dist/retrieval.js +1 -0
  387. package/dist/sanitize.js +1 -0
  388. package/dist/schemas.d.ts +15 -2
  389. package/dist/schemas.js +2 -1
  390. package/dist/sdk-compat.js +1 -0
  391. package/dist/sdk-compat.js.map +1 -1
  392. package/dist/secure-store-4R2GSO7S.js +156 -0
  393. package/dist/secure-store-4R2GSO7S.js.map +1 -0
  394. package/dist/semantic-chunking.js +1 -0
  395. package/dist/{semantic-consolidation-CxJU6MJk.d.ts → semantic-consolidation-ByBXb-sf.d.ts} +3 -3
  396. package/dist/semantic-consolidation.d.ts +2 -2
  397. package/dist/semantic-consolidation.js +12 -6
  398. package/dist/semantic-rule-promotion.d.ts +1 -1
  399. package/dist/semantic-rule-promotion.js +9 -6
  400. package/dist/semantic-rule-verifier.d.ts +1 -1
  401. package/dist/semantic-rule-verifier.js +9 -6
  402. package/dist/session-integrity.js +1 -0
  403. package/dist/session-observer-bands.js +1 -0
  404. package/dist/session-observer-state.js +1 -0
  405. package/dist/session-toggles.js +2 -0
  406. package/dist/session-toggles.js.map +1 -1
  407. package/dist/signal.js +1 -0
  408. package/dist/skills-registry.js +2 -0
  409. package/dist/skills-registry.js.map +1 -1
  410. package/dist/source-attribution.js +1 -0
  411. package/dist/state-NCHQ4TRG.js +8 -0
  412. package/dist/state-NCHQ4TRG.js.map +1 -0
  413. package/dist/state-store-3EH7HYIN.js +16 -0
  414. package/dist/state-store-3EH7HYIN.js.map +1 -0
  415. package/dist/storage.d.ts +76 -2
  416. package/dist/storage.js +8 -5
  417. package/dist/store-contract.js +1 -0
  418. package/dist/summarizer.js +6 -5
  419. package/dist/summary-snapshot.js +1 -0
  420. package/dist/temporal-index.js +1 -0
  421. package/dist/temporal-supersession.d.ts +1 -1
  422. package/dist/temporal-supersession.js +2 -1
  423. package/dist/temporal-validity.d.ts +52 -0
  424. package/dist/temporal-validity.js +14 -0
  425. package/dist/temporal-validity.js.map +1 -0
  426. package/dist/threading.js +1 -0
  427. package/dist/tier-migration.d.ts +2 -2
  428. package/dist/tier-migration.js +1 -0
  429. package/dist/tier-routing.js +1 -0
  430. package/dist/tier-stats-62ZVDFKS.js +152 -0
  431. package/dist/tier-stats-62ZVDFKS.js.map +1 -0
  432. package/dist/tmt.js +1 -0
  433. package/dist/tokens.js +3 -1
  434. package/dist/topics.js +1 -0
  435. package/dist/trace-C5ETWBEF.js +290 -0
  436. package/dist/trace-C5ETWBEF.js.map +1 -0
  437. package/dist/transcript.js +1 -0
  438. package/dist/trust-zones.js +1 -0
  439. package/dist/tui-RI7P6PBS.js +13 -0
  440. package/dist/tui-RI7P6PBS.js.map +1 -0
  441. package/dist/types-V3FJ26TF.js +30 -0
  442. package/dist/types-V3FJ26TF.js.map +1 -0
  443. package/dist/types.d.ts +634 -9
  444. package/dist/types.js +10 -3
  445. package/dist/utility-learner.js +1 -0
  446. package/dist/utility-runtime.js +1 -0
  447. package/dist/utility-telemetry.js +1 -0
  448. package/dist/verified-recall.js +9 -6
  449. package/dist/version-utils.js +1 -0
  450. package/dist/whitespace.js +1 -0
  451. package/dist/work-product-ledger.js +1 -0
  452. package/package.json +2 -1
  453. package/dist/access-service-Br8ZydTK.d.ts +0 -827
  454. package/dist/chunk-3OGMS3PE.js.map +0 -1
  455. package/dist/chunk-6PFRXT4K.js.map +0 -1
  456. package/dist/chunk-ALXMCZEU.js.map +0 -1
  457. package/dist/chunk-B5WXLVDY.js.map +0 -1
  458. package/dist/chunk-BGJGXLZ7.js.map +0 -1
  459. package/dist/chunk-BK2EFTE2.js.map +0 -1
  460. package/dist/chunk-C2EFFULQ.js.map +0 -1
  461. package/dist/chunk-CUPFXL3J.js.map +0 -1
  462. package/dist/chunk-DFTTJYSO.js.map +0 -1
  463. package/dist/chunk-EPQJM2GC.js.map +0 -1
  464. package/dist/chunk-F5VP6YCB.js.map +0 -1
  465. package/dist/chunk-FVA6TGI3.js.map +0 -1
  466. package/dist/chunk-GKFXUTJ2.js.map +0 -1
  467. package/dist/chunk-HK3FGIEW.js.map +0 -1
  468. package/dist/chunk-INXV5JBT.js.map +0 -1
  469. package/dist/chunk-KVBLZUKV.js.map +0 -1
  470. package/dist/chunk-LK6SGL53.js.map +0 -1
  471. package/dist/chunk-LTCGGW2D.js +0 -14
  472. package/dist/chunk-LTCGGW2D.js.map +0 -1
  473. package/dist/chunk-NZLQTHS5.js.map +0 -1
  474. package/dist/chunk-O5ETUNBT.js.map +0 -1
  475. package/dist/chunk-PVPWZSSI.js.map +0 -1
  476. package/dist/chunk-RGLL5SPU.js.map +0 -1
  477. package/dist/chunk-S3EEFKNY.js.map +0 -1
  478. package/dist/chunk-SPI27QT6.js.map +0 -1
  479. package/dist/chunk-TP4FZJIZ.js.map +0 -1
  480. package/dist/chunk-ULYOGL6R.js.map +0 -1
  481. package/dist/chunk-VBVG2M5G.js.map +0 -1
  482. package/dist/chunk-VDX363PS.js.map +0 -1
  483. package/dist/chunk-VYM3VWOF.js.map +0 -1
  484. package/dist/chunk-WCLICCGB.js.map +0 -1
  485. package/dist/chunk-WVVA7F5A.js.map +0 -1
  486. package/dist/chunk-X6GF3FX2.js +0 -26
  487. package/dist/chunk-X6GF3FX2.js.map +0 -1
  488. package/dist/chunk-XZ2TIKGC.js.map +0 -1
  489. package/dist/chunk-ZAIM4TUE.js.map +0 -1
  490. /package/dist/{contradiction-review-WIUBAR52.js.map → capsule-cli.js.map} +0 -0
  491. /package/dist/{engine-F3GOXGE5.js.map → capsule-crypto-5CYAGVC5.js.map} +0 -0
  492. /package/dist/{chunk-KUB6JU6H.js.map → chunk-47WOM4YW.js.map} +0 -0
  493. /package/dist/{chunk-ZEM3OK2K.js.map → chunk-B2TL6GA2.js.map} +0 -0
  494. /package/dist/{chunk-3GXCSUXR.js.map → chunk-CRU27Q4J.js.map} +0 -0
  495. /package/dist/{chunk-RBBWYEFJ.js.map → chunk-G2WADRQ3.js.map} +0 -0
  496. /package/dist/{chunk-7I7FKFZH.js.map → chunk-R2L7SUX2.js.map} +0 -0
  497. /package/dist/{chunk-JL2PU6AI.js.map → chunk-R2XRID2N.js.map} +0 -0
@@ -0,0 +1,257 @@
1
+ import { b as ConnectorCursor, a as ConnectorDocument } from './framework-CyHYDcri.js';
2
+
3
+ /**
4
+ * @remnic/core — Live Connectors State Store (issue #683 PR 1/N)
5
+ *
6
+ * Persists per-connector cursor + sync metadata to
7
+ * `<memoryDir>/state/connectors/<id>.json`
8
+ *
9
+ * Reasons this lives next to memory data, not in user config:
10
+ * - cursors are *operational* state that should travel with the memory
11
+ * directory when a user moves it across machines;
12
+ * - it keeps memory + ingest provenance co-located so tooling that backs up
13
+ * the memory directory captures cursor state too.
14
+ *
15
+ * Atomic-write contract (CLAUDE.md gotcha #54):
16
+ * - We NEVER `rmSync(target)` before `renameSync(tmp, target)`.
17
+ * - Writes go to a sibling tmp file and `rename()` swaps it in.
18
+ * - On error, the tmp file is best-effort cleaned up; the previous good
19
+ * state file is left untouched.
20
+ *
21
+ * Privacy: cursors are opaque connector-defined strings. We do not log them
22
+ * and do not surface them through user-visible APIs. Document content NEVER
23
+ * touches this module.
24
+ */
25
+
26
+ /**
27
+ * Status of the most recent sync attempt for a connector.
28
+ *
29
+ * `"never"` is distinct from `"success"` so callers can detect
30
+ * "registered but never run" without inspecting timestamps. Per CLAUDE.md
31
+ * gotcha #34, we deliberately distinguish empty/unknown from failure states.
32
+ */
33
+ type ConnectorSyncStatus = "success" | "error" | "never";
34
+ /**
35
+ * Persisted per-connector state.
36
+ *
37
+ * Stored as pretty-printed JSON for human inspection — the file is small
38
+ * (one record per connector) and operators may need to debug stuck cursors
39
+ * by hand.
40
+ */
41
+ interface ConnectorState {
42
+ /** Connector id. Matches the filename stem. */
43
+ readonly id: string;
44
+ /** Last persisted cursor, or `null` if the connector has never synced. */
45
+ readonly cursor: ConnectorCursor | null;
46
+ /** ISO 8601 timestamp of the last completed sync attempt, or `null`. */
47
+ readonly lastSyncAt: string | null;
48
+ /** Status of the last completed sync attempt. */
49
+ readonly lastSyncStatus: ConnectorSyncStatus;
50
+ /** Optional error message from the last failed sync. Truncated to 1 KB. */
51
+ readonly lastSyncError?: string;
52
+ /** Cumulative count of documents successfully imported across all syncs. */
53
+ readonly totalDocsImported: number;
54
+ /** ISO 8601 timestamp of when this state record was last written. */
55
+ readonly updatedAt: string;
56
+ }
57
+ /**
58
+ * Read the persisted state for a single connector.
59
+ *
60
+ * Returns `null` if the file does not exist (ENOENT). Throws on any other
61
+ * I/O error or on shape mismatch — operators should see corruption loudly.
62
+ *
63
+ * Rejects symlinks anywhere on the path so a planted symlink can't redirect
64
+ * reads outside the memory root. (PR #724 review.)
65
+ */
66
+ declare function readConnectorState(memoryDir: string, id: string): Promise<ConnectorState | null>;
67
+ /**
68
+ * Write state atomically: create-tmp + rename. Never destroys the previous
69
+ * file before the new one is in place — see CLAUDE.md gotcha #54.
70
+ *
71
+ * We accept `Omit<ConnectorState, "updatedAt">` and stamp `updatedAt`
72
+ * ourselves so callers can't accidentally persist a stale timestamp.
73
+ */
74
+ declare function writeConnectorState(memoryDir: string, id: string, state: Omit<ConnectorState, "updatedAt">): Promise<ConnectorState>;
75
+ /**
76
+ * Enumerate every persisted connector state. Returns an empty array when
77
+ * the directory does not exist yet (clean install, no syncs ever run).
78
+ *
79
+ * Files that do not match the `<id>.json` naming rule are skipped — this
80
+ * keeps stray editor backups (`.json~`, `.swp`) from breaking enumeration.
81
+ *
82
+ * Corruption (unparseable JSON, shape mismatch, id mismatch) is also
83
+ * skipped so one bad file doesn't take down the listing. Operators
84
+ * inspecting `state/connectors/` can still see the offending file by hand.
85
+ *
86
+ * **Genuine I/O failures (`EACCES`, `EIO`, etc.) are NOT swallowed** —
87
+ * silently returning an incomplete state set would make active connectors
88
+ * appear missing and trigger duplicate ingestion on the next scheduler tick.
89
+ * (PR #724 review.)
90
+ */
91
+ declare function listConnectorStates(memoryDir: string): Promise<ConnectorState[]>;
92
+
93
+ /**
94
+ * `remnic connectors` CLI helpers (issue #683 PR 6/N).
95
+ *
96
+ * Three subcommands:
97
+ *
98
+ * remnic connectors list
99
+ * Lists all configured live connectors, their enabled state, last poll
100
+ * time, and last error. Output formats: text (default), markdown, json.
101
+ *
102
+ * remnic connectors status
103
+ * Identical data to `list` but defaults to JSON output so scripts can
104
+ * reliably parse it. Accepts `--format` to override.
105
+ *
106
+ * remnic connectors run <name>
107
+ * Manually triggers a single `syncIncremental()` pass for the named
108
+ * connector. Operator debug surface — useful when you want to test
109
+ * credentials without waiting for the scheduler tick. Prints the
110
+ * number of new documents imported plus any error.
111
+ *
112
+ * Design decisions:
113
+ *
114
+ * - Pure functions for list / status / run option parsing so they can be
115
+ * unit-tested without booting an orchestrator (CLAUDE.md rules 14 + 51).
116
+ * - Rendering lives here (not in cli.ts) so HTTP/MCP surfaces can reuse the
117
+ * same output without forking formatting (CLAUDE.md rule 22).
118
+ * - The `run` command requires the caller to pass a `pollFn` callback
119
+ * (wrapping the actual connector's `syncIncremental`). This keeps the
120
+ * helper module free of direct orchestrator / live-connector imports while
121
+ * still being testable (CLAUDE.md rule 33 — mock signatures must match
122
+ * production).
123
+ * - CLAUDE.md rule 51: invalid `--format` throws with listed options; unknown
124
+ * connector name in `run` throws a descriptive error; `--format` without a
125
+ * value is caught by Commander's built-in argument check.
126
+ * - `runConnectorPollOnce` encapsulates the persist-before-cursor-advance
127
+ * contract (CLAUDE.md gotcha #25, #43) so it can be unit-tested in
128
+ * isolation without an orchestrator.
129
+ */
130
+
131
+ /**
132
+ * A lightweight descriptor for one live connector, assembled from the parsed
133
+ * config and any persisted state. The CLI handler builds this from
134
+ * `orchestrator.config.connectors` + `listConnectorStates(memoryDir)`.
135
+ */
136
+ interface ConnectorRow {
137
+ /** Stable connector id (e.g. `"google-drive"`, `"notion"`). */
138
+ id: string;
139
+ /** Human-readable display name. */
140
+ displayName: string;
141
+ /** Whether the operator has enabled this connector in config. */
142
+ enabled: boolean;
143
+ /** Persisted sync state, or `null` if no sync has ever run. */
144
+ state: ConnectorState | null;
145
+ }
146
+ /**
147
+ * Result returned by the `run` command's poll function.
148
+ */
149
+ interface ConnectorRunResult {
150
+ /** Number of new documents imported in this pass. */
151
+ docsImported: number;
152
+ /**
153
+ * Error message if the sync or ingest failed, undefined on full success.
154
+ * When only the cursor write failed after a successful ingest, this field is
155
+ * undefined and `stateWriteError` carries the failure. When persisting error
156
+ * state fails after a sync/ingest error, both fields are set so callers can
157
+ * preserve the primary failure while also reporting stale persisted state.
158
+ */
159
+ error?: string;
160
+ /**
161
+ * Set when cursor/error-state persistence fails. On the success path,
162
+ * `docsImported` reflects the actual count of successfully ingested docs so
163
+ * the operator knows data was persisted even though the cursor did not
164
+ * advance. On the error path, `error` preserves the primary sync/ingest
165
+ * failure and this field reports the secondary persistence failure.
166
+ */
167
+ stateWriteError?: string;
168
+ }
169
+ declare const CONNECTORS_OUTPUT_FORMATS: readonly ["text", "markdown", "json"];
170
+ type ConnectorsOutputFormat = (typeof CONNECTORS_OUTPUT_FORMATS)[number];
171
+ /**
172
+ * Validate `--format <fmt>`. Throws a listed-options error for any value not
173
+ * in `CONNECTORS_OUTPUT_FORMATS`. Returns the given default when the value is
174
+ * `undefined` (no flag supplied).
175
+ */
176
+ declare function parseConnectorsFormat(value: unknown, defaultFormat?: ConnectorsOutputFormat): ConnectorsOutputFormat;
177
+ interface ParsedConnectorsListOptions {
178
+ format: ConnectorsOutputFormat;
179
+ }
180
+ interface ParsedConnectorsStatusOptions {
181
+ /** `status` defaults to JSON for scripting; `--format` can override. */
182
+ format: ConnectorsOutputFormat;
183
+ }
184
+ /**
185
+ * Validate the option bag for `remnic connectors list`.
186
+ */
187
+ declare function parseConnectorsListOptions(options: Record<string, unknown>): ParsedConnectorsListOptions;
188
+ /**
189
+ * Validate the option bag for `remnic connectors status`.
190
+ * Defaults to `json` (machine-readable) unless `--format` overrides.
191
+ */
192
+ declare function parseConnectorsStatusOptions(options: Record<string, unknown>): ParsedConnectorsStatusOptions;
193
+ /**
194
+ * Validate the positional `<name>` argument for `remnic connectors run`.
195
+ */
196
+ declare function parseConnectorsRunName(rawName: unknown): string;
197
+ /**
198
+ * Render connector rows for `remnic connectors list` / `remnic connectors status`.
199
+ */
200
+ declare function renderConnectorsList(rows: readonly ConnectorRow[], format: ConnectorsOutputFormat): string;
201
+ /**
202
+ * Render the result of a manual `remnic connectors run <name>` invocation.
203
+ */
204
+ declare function renderConnectorsRunResult(connectorId: string, result: ConnectorRunResult, format: ConnectorsOutputFormat): string;
205
+ /**
206
+ * Arguments for a single connector poll pass.
207
+ *
208
+ * All I/O is injectable so callers (cli.ts) can supply real implementations
209
+ * and tests can supply lightweight stubs without booting an orchestrator.
210
+ */
211
+ interface RunConnectorPollOnceArgs {
212
+ /** Connector identifier (used in error/success state writes). */
213
+ connectorId: string;
214
+ /** Prior persisted state, or `null` on the very first sync. */
215
+ priorState: ConnectorState | null;
216
+ /**
217
+ * Perform a single incremental sync. Returns newly-fetched documents and
218
+ * the cursor that should be persisted on success.
219
+ */
220
+ syncFn: (cursor: ConnectorCursor | null) => Promise<{
221
+ newDocs: ConnectorDocument[];
222
+ nextCursor: ConnectorCursor;
223
+ }>;
224
+ /**
225
+ * Ingest fetched documents into the memory layer. Called BEFORE the cursor
226
+ * is advanced. If this throws the cursor is NOT advanced (CLAUDE.md gotcha
227
+ * #25 — don't destroy old state before confirming new state succeeds).
228
+ */
229
+ ingestFn: (docs: ConnectorDocument[]) => Promise<void>;
230
+ /**
231
+ * Persist connector state (cursor + metadata). Called after `ingestFn`
232
+ * succeeds (success path) or when `syncFn` / `ingestFn` throws (error path,
233
+ * with old cursor retained).
234
+ */
235
+ writeCursorFn: (state: {
236
+ cursor: ConnectorCursor | null;
237
+ lastSyncStatus: ConnectorSyncStatus;
238
+ lastSyncError?: string;
239
+ totalDocsImported: number;
240
+ }) => Promise<void>;
241
+ }
242
+ /**
243
+ * Execute one `syncIncremental` pass for a live connector, enforcing the
244
+ * persist-before-advance-cursor contract.
245
+ *
246
+ * Invariant (CLAUDE.md gotcha #25 + #43):
247
+ * 1. `syncFn` fetches new docs and a next cursor.
248
+ * 2. `ingestFn` persists the docs into the memory layer.
249
+ * 3. Only if (2) succeeds does `writeCursorFn` advance the cursor.
250
+ * 4. If (1) or (2) throws, `writeCursorFn` is still called but retains the
251
+ * **prior** cursor so the next poll re-fetches the same window.
252
+ *
253
+ * Returns the `ConnectorRunResult` that `cli.ts` uses for output rendering.
254
+ */
255
+ declare function runConnectorPollOnce(args: RunConnectorPollOnceArgs): Promise<ConnectorRunResult>;
256
+
257
+ export { CONNECTORS_OUTPUT_FORMATS as C, type ParsedConnectorsListOptions as P, type RunConnectorPollOnceArgs as R, type ConnectorRow as a, type ConnectorRunResult as b, type ConnectorState as c, type ConnectorSyncStatus as d, type ConnectorsOutputFormat as e, type ParsedConnectorsStatusOptions as f, parseConnectorsListOptions as g, parseConnectorsRunName as h, parseConnectorsStatusOptions as i, renderConnectorsList as j, renderConnectorsRunResult as k, listConnectorStates as l, runConnectorPollOnce as m, parseConnectorsFormat as p, readConnectorState as r, writeConnectorState as w };
@@ -0,0 +1,2 @@
1
+ import './framework-CyHYDcri.js';
2
+ export { C as CONNECTORS_OUTPUT_FORMATS, a as ConnectorRow, b as ConnectorRunResult, e as ConnectorsOutputFormat, P as ParsedConnectorsListOptions, f as ParsedConnectorsStatusOptions, R as RunConnectorPollOnceArgs, p as parseConnectorsFormat, g as parseConnectorsListOptions, h as parseConnectorsRunName, i as parseConnectorsStatusOptions, j as renderConnectorsList, k as renderConnectorsRunResult, m as runConnectorPollOnce } from './connectors-cli-DFGtY2DB.js';
@@ -0,0 +1,22 @@
1
+ import {
2
+ CONNECTORS_OUTPUT_FORMATS,
3
+ parseConnectorsFormat,
4
+ parseConnectorsListOptions,
5
+ parseConnectorsRunName,
6
+ parseConnectorsStatusOptions,
7
+ renderConnectorsList,
8
+ renderConnectorsRunResult,
9
+ runConnectorPollOnce
10
+ } from "./chunk-OZHRDTDX.js";
11
+ import "./chunk-PZ5AY32C.js";
12
+ export {
13
+ CONNECTORS_OUTPUT_FORMATS,
14
+ parseConnectorsFormat,
15
+ parseConnectorsListOptions,
16
+ parseConnectorsRunName,
17
+ parseConnectorsStatusOptions,
18
+ renderConnectorsList,
19
+ renderConnectorsRunResult,
20
+ runConnectorPollOnce
21
+ };
22
+ //# sourceMappingURL=connectors-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -20,22 +20,82 @@
20
20
  * memory.
21
21
  * - `update` — a newer value supersedes an older value within the same
22
22
  * logical fact.
23
+ * - `pattern-reinforcement` (issue #687 PR 2/4) — emitted by the
24
+ * pattern-reinforcement maintenance job when it clusters duplicate
25
+ * non-procedural memories and promotes the most recent member to
26
+ * canonical. Unlike the other operators, the job does not produce
27
+ * page-versioning snapshots — it just stamps reinforcement metadata
28
+ * on the canonical and points superseded duplicates at it. See
29
+ * `maintenance/pattern-reinforcement.ts`.
23
30
  */
24
- type ConsolidationOperator = "split" | "merge" | "update";
31
+ type ConsolidationOperator = "split" | "merge" | "update" | "pattern-reinforcement";
25
32
  /**
26
33
  * Allowed values for the `derived_via` frontmatter field. Used by storage
27
34
  * validation to reject unknown operator values on write.
28
35
  */
29
36
  declare const CONSOLIDATION_OPERATORS: readonly ConsolidationOperator[];
30
37
  /**
31
- * Validate a `derived_from` entry string. Returns `true` if the entry
32
- * parses as `<non-empty path>:<integer >= 0>`. Kept pure so storage and
33
- * future CLI/doctor paths can share the same validator.
38
+ * Regular expression for validating a memory-id `derived_from` entry
39
+ * (issue #687 PR 2/4). Pattern reinforcement records source memory IDs
40
+ * directly rather than page-versioning snapshots, so we also need to
41
+ * accept that shape.
42
+ *
43
+ * Memory IDs are alphanumeric with hyphens, underscores, or COLONS —
44
+ * the namespace-prefixed form (e.g. `global:fact-abc-123`,
45
+ * `entity:person-alice`) is a legitimate ID format already used
46
+ * throughout the graph-retrieval code path (`stripDerivedFromVersion`).
47
+ * The disambiguation against `<path>:<version>` is *not* "no colons
48
+ * allowed" — it's "the segment after the last colon must NOT be a
49
+ * non-negative integer" (PR #730 review feedback, Codex P1).
50
+ *
51
+ * Slashes and dots remain forbidden so paths cannot accidentally pass
52
+ * as memory IDs.
53
+ *
54
+ * Exported so the consolidation-provenance integrity scanner (PR
55
+ * #730 review feedback) can recognize bare memory IDs instead of
56
+ * flagging them as malformed snapshot references.
57
+ */
58
+ declare const DERIVED_FROM_MEMORY_ID_RE: RegExp;
59
+ /**
60
+ * Validate a `derived_from` entry string. Returns `true` for either
61
+ * - the snapshot format `<non-empty path>:<integer >= 0>` (issue #561), or
62
+ * - a memory-id of the form `[A-Za-z0-9][A-Za-z0-9_:-]*` (issue #687
63
+ * PR 2/4 — used by pattern-reinforcement provenance). Memory IDs
64
+ * may include `:` for namespace-prefixed forms like
65
+ * `global:fact-abc-123` (PR #730 review feedback, Codex P1).
66
+ *
67
+ * Disambiguation rule: only treat the entry as `<path>:<version>` when
68
+ * the trailing segment after the last `:` is a non-negative integer
69
+ * AND the left side looks like a path (contains `/` or `.`).
70
+ * Otherwise treat the entire string as a memory ID. This is the same
71
+ * heuristic the graph retrieval path uses when splitting derived_from
72
+ * references back into a memory id.
73
+ *
74
+ * Kept pure so storage and future CLI/doctor paths can share the same
75
+ * validator.
34
76
  */
35
77
  declare function isValidDerivedFromEntry(entry: unknown): entry is string;
36
78
  /**
37
79
  * Type guard for `ConsolidationOperator`.
38
80
  */
39
81
  declare function isConsolidationOperator(value: unknown): value is ConsolidationOperator;
82
+ /**
83
+ * Narrow operator vocabulary for the LLM-driven semantic-consolidation
84
+ * pass (issue #561 PR 3). This explicitly excludes
85
+ * `"pattern-reinforcement"` (issue #687 PR 2/4), which is reserved for
86
+ * the maintenance job and must NEVER be emitted by the consolidation
87
+ * LLM. Without this narrow gate, a hallucinated
88
+ * `{"operator":"pattern-reinforcement"}` response from the LLM would
89
+ * write misleading provenance on a semantic-consolidation memory
90
+ * (Cursor Bugbot review, PR #730 head `aa1c2a8`).
91
+ */
92
+ type SemanticConsolidationLlmOperator = "split" | "merge" | "update";
93
+ /**
94
+ * Type guard restricted to the operator subset the
95
+ * semantic-consolidation LLM is allowed to emit. Use this in any
96
+ * code path that validates LLM output — `isConsolidationOperator` is
97
+ * for validating values that came from disk / internal callers.
98
+ */
99
+ declare function isSemanticConsolidationLlmOperator(value: unknown): value is SemanticConsolidationLlmOperator;
40
100
 
41
- export { CONSOLIDATION_OPERATORS, type ConsolidationOperator, isConsolidationOperator, isValidDerivedFromEntry };
101
+ export { CONSOLIDATION_OPERATORS, type ConsolidationOperator, DERIVED_FROM_MEMORY_ID_RE, type SemanticConsolidationLlmOperator, isConsolidationOperator, isSemanticConsolidationLlmOperator, isValidDerivedFromEntry };
@@ -1,11 +1,16 @@
1
1
  import {
2
2
  CONSOLIDATION_OPERATORS,
3
+ DERIVED_FROM_MEMORY_ID_RE,
3
4
  isConsolidationOperator,
5
+ isSemanticConsolidationLlmOperator,
4
6
  isValidDerivedFromEntry
5
- } from "./chunk-X6GF3FX2.js";
7
+ } from "./chunk-G7D6GZ5J.js";
8
+ import "./chunk-PZ5AY32C.js";
6
9
  export {
7
10
  CONSOLIDATION_OPERATORS,
11
+ DERIVED_FROM_MEMORY_ID_RE,
8
12
  isConsolidationOperator,
13
+ isSemanticConsolidationLlmOperator,
9
14
  isValidDerivedFromEntry
10
15
  };
11
16
  //# sourceMappingURL=consolidation-operator.js.map
@@ -2,7 +2,7 @@ import { StorageManager } from './storage.js';
2
2
  import './page-versioning.js';
3
3
  import './consolidation-operator.js';
4
4
  import './types.js';
5
- import './memory-projection-store-DeSXPh1j.js';
5
+ import './memory-projection-store-D3vBHS4J.js';
6
6
  import 'better-sqlite3';
7
7
 
8
8
  /**
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  runConsolidationProvenanceCheck
3
- } from "./chunk-ULYOGL6R.js";
4
- import "./chunk-X6GF3FX2.js";
3
+ } from "./chunk-5HRY2WRF.js";
4
+ import "./chunk-G7D6GZ5J.js";
5
5
  import "./chunk-FAAFWE4G.js";
6
+ import "./chunk-PZ5AY32C.js";
6
7
  export {
7
8
  runConsolidationProvenanceCheck
8
9
  };
@@ -2,7 +2,7 @@ import { StorageManager } from './storage.js';
2
2
  import { VersioningConfig } from './page-versioning.js';
3
3
  import './consolidation-operator.js';
4
4
  import './types.js';
5
- import './memory-projection-store-DeSXPh1j.js';
5
+ import './memory-projection-store-D3vBHS4J.js';
6
6
  import 'better-sqlite3';
7
7
 
8
8
  /**
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  getVersion
3
3
  } from "./chunk-FAAFWE4G.js";
4
+ import "./chunk-PZ5AY32C.js";
4
5
 
5
6
  // src/consolidation-undo.ts
6
7
  import path from "path";
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/consolidation-undo.ts"],"sourcesContent":["/**\n * Consolidation undo (issue #561 PR 5).\n *\n * Reverts a consolidated memory by restoring each source memory from its\n * `derived_from` snapshot and archiving the target.\n *\n * Contract:\n * - Load the target memory markdown file via its absolute path.\n * - For every `\"<rel>:<version>\"` entry in `derived_from`, fetch the\n * snapshot content via `page-versioning.getVersion` and restore it\n * to the original relative path. If the restore target file\n * already exists, we skip overwriting it (the source was never\n * archived, or was re-created since) and record the skip.\n * - Archive the target with reason code `\"consolidation-undo\"` so the\n * lifecycle ledger records the undo.\n * - Dry-run mode produces the same plan without touching disk.\n *\n * The helper is kept pure over `StorageManager` so the CLI can reuse it\n * without additional wiring, and tests can exercise the plan logic\n * directly.\n */\n\nimport path from \"node:path\";\nimport { mkdir, writeFile, access, realpath, lstat } from \"node:fs/promises\";\nimport { constants as fsConstants } from \"node:fs\";\nimport type { StorageManager } from \"./storage.js\";\nimport type { VersioningConfig } from \"./page-versioning.js\";\nimport { getVersion } from \"./page-versioning.js\";\n\n/**\n * Outcome of restoring a single `derived_from` source.\n */\nexport interface ConsolidationUndoRestore {\n /** The raw `\"<relpath>:<version>\"` entry from `derived_from`. */\n entry: string;\n /** Absolute path where the source would be / was restored. */\n sourcePath: string;\n /** What actually happened. */\n outcome:\n | \"restored\"\n | \"skipped_file_exists\"\n | \"skipped_non_regular_file\"\n | \"skipped_snapshot_missing\"\n | \"skipped_malformed_entry\"\n | \"skipped_outside_memory_dir\"\n | \"skipped_non_active_path\"\n | \"skipped_self_referential\"\n | \"skipped_write_failed\"\n | \"skipped_blocked_by_other_failures\"\n | \"skipped_dry_run\";\n /** Human-readable detail. */\n detail?: string;\n}\n\n/**\n * Plan + result of a `remnic consolidate undo` invocation.\n */\nexport interface ConsolidationUndoResult {\n /** Absolute path to the target memory. */\n targetPath: string;\n /** True when the target was archived successfully. */\n targetArchived: boolean;\n /** Per-source restore outcomes. */\n restores: ConsolidationUndoRestore[];\n /** Whether the run was a dry-run plan only. */\n dryRun: boolean;\n /** Fatal error, if any — the run bails early. */\n error?: string;\n}\n\nconst DERIVED_FROM_ENTRY_RE = /^(.+):(\\d+)$/;\n\nfunction parseEntry(entry: unknown): { pagePath: string; versionId: string } | null {\n // Non-string entries (PR #637 round-3 review, cursor Low) can arrive\n // from hostile on-disk frontmatter — guard against a .match() crash.\n if (typeof entry !== \"string\") return null;\n const match = entry.match(DERIVED_FROM_ENTRY_RE);\n if (!match) return null;\n return { pagePath: match[1], versionId: match[2] };\n}\n\n/**\n * Verify that `candidate` resolves inside `root` (defense against\n * path-traversal in `derived_from` entries and user-facing target\n * paths). Path-string normalization only; for symlink-aware checks\n * use `isInsideDirectoryRealpath`. Both paths are resolved to\n * absolute form before comparison so `..` segments, symlinks-as-\n * strings, and relative prefixes are normalized.\n */\nexport function isInsideDirectory(candidate: string, root: string): boolean {\n const normRoot = path.resolve(root);\n const normCandidate = path.resolve(candidate);\n const rel = path.relative(normRoot, normCandidate);\n if (rel.length === 0) return true;\n if (rel.startsWith(\"..\")) return false;\n if (path.isAbsolute(rel)) return false;\n return true;\n}\n\n/**\n * Symlink-aware containment check (PR #637 round-2 review, codex P1).\n *\n * `isInsideDirectory` only normalizes path strings — if a `derived_from`\n * entry resolves through a symlink inside `memoryDir` that points\n * outside, the string check passes but the subsequent `writeFile` would\n * land outside the memory tree. Use this guard for any path that is\n * about to be written.\n *\n * Walks every parent directory between `candidate` and `root`,\n * `realpath`-ing each segment that exists and rejecting when any\n * segment escapes `root`. Non-existent parents are resolved as the\n * canonicalized deepest-existing ancestor plus the trailing segments,\n * so a not-yet-created target file still gets the symlink check on its\n * existing parent directories.\n */\nexport async function isInsideDirectoryRealpath(\n candidate: string,\n root: string,\n): Promise<boolean> {\n if (!isInsideDirectory(candidate, root)) return false;\n // Reject raw `..` segments before canonicalization so that symlinks\n // cannot be hidden behind intermediate dot-dot components (PR #637\n // round-14 review, codex P1).\n const rawSegments = candidate.replace(/\\\\/g, \"/\").split(\"/\");\n if (rawSegments.some((s) => s === \"..\")) return false;\n let resolvedRoot: string;\n try {\n resolvedRoot = await realpath(path.resolve(root));\n } catch {\n return false;\n }\n const normCandidate = path.resolve(candidate);\n\n // Reject dangling symlinks (PR #637 round-3 review, codex P1).\n // If the candidate itself is a symlink (even if its target doesn't\n // exist), Node will follow it when we later call `writeFile`.\n // `lstat` inspects the link itself without dereferencing; if it\n // succeeds and reports a symlink, we treat the candidate as\n // unsafe. We must check every non-root ancestor too — a symlink\n // anywhere along the path lets an attacker redirect writes.\n const normRoot = path.resolve(root);\n const relFromRoot = path.relative(normRoot, normCandidate);\n const segments = relFromRoot.length > 0 ? relFromRoot.split(path.sep) : [];\n for (let i = 0; i <= segments.length; i++) {\n const probe = i === 0 ? normRoot : path.join(normRoot, ...segments.slice(0, i));\n try {\n const st = await lstat(probe);\n if (st.isSymbolicLink() && probe !== normRoot) {\n // A symlink on the path — resolve THIS segment and bail out\n // if the resolved target escapes `resolvedRoot`.\n let target: string;\n try {\n target = await realpath(probe);\n } catch {\n // Dangling symlink inside memoryDir — always unsafe.\n return false;\n }\n const rel = path.relative(resolvedRoot, target);\n if (rel.length === 0) continue;\n if (rel.startsWith(\"..\") || path.isAbsolute(rel)) return false;\n }\n } catch {\n // Segment doesn't exist yet — that's fine, fall through to the\n // textual containment verification below.\n }\n }\n\n // Walk up from the candidate until we hit a path that exists, then\n // realpath THAT and re-apply the trailing segments textually.\n const parts = normCandidate.split(path.sep);\n for (let i = parts.length; i > 0; i--) {\n const probe = parts.slice(0, i).join(path.sep) || path.sep;\n try {\n const resolved = await realpath(probe);\n // Re-join any trailing segments that didn't exist yet.\n const trailing = parts.slice(i).join(path.sep);\n const final = trailing.length > 0 ? path.join(resolved, trailing) : resolved;\n // Now apply the textual containment check against the canonical\n // `resolvedRoot`.\n const rel = path.relative(resolvedRoot, final);\n if (rel.length === 0) return true;\n if (rel.startsWith(\"..\")) return false;\n if (path.isAbsolute(rel)) return false;\n return true;\n } catch {\n continue;\n }\n }\n // Nothing along the path resolvable — treat as outside by default.\n return false;\n}\n\n/**\n * Directories under memoryDir that are NOT active memory locations.\n * A `derived_from` entry pointing into one of these should not be\n * counted as \"recovered_existing\" (PR #637 round-7 review, codex P2).\n * The versioning sidecar directory is included dynamically via the\n * `sidecarDir` parameter (PR #637 round-8 review, codex P2).\n */\nconst NON_ACTIVE_PREFIXES = [\"archive/\", \"state/\"];\n\n/**\n * Normalize a relative path by collapsing `.` and `..` segments so\n * that crafted entries like `\"facts/../archive/x.md\"` are reduced to\n * `\"archive/x.md\"` before the non-active-prefix check.\n */\nfunction normalizeRelativePath(p: string): string {\n // Normalize separators, split into segments, then resolve.\n const parts = p.replace(/\\\\/g, \"/\").split(\"/\");\n const resolved: string[] = [];\n for (const seg of parts) {\n if (seg === \"\" || seg === \".\") continue;\n if (seg === \"..\") {\n if (resolved.length > 0) resolved.pop();\n // If \"..\" pops past the root, we let the caller's containment\n // check catch it — don't silently drop.\n } else {\n resolved.push(seg);\n }\n }\n return resolved.join(\"/\");\n}\n\n/**\n * Check that a relative path (relative to memoryDir) points to an\n * active memory location rather than an internal/archive directory.\n * Returns `true` when the normalised `pagePath` does NOT start with\n * a known non-active prefix.\n *\n * @param pagePath Relative path from `derived_from` entry.\n * @param sidecarDir Optional versioning sidecar directory name\n * (e.g. `\".versions\"`). When provided, paths\n * under this directory are also rejected as\n * non-active.\n */\nexport function isActiveMemoryRelativePath(\n pagePath: string,\n sidecarDir?: string,\n): boolean {\n const normalized = normalizeRelativePath(pagePath);\n const prefixes = [...NON_ACTIVE_PREFIXES];\n if (sidecarDir) {\n const normSidecar = normalizeRelativePath(sidecarDir);\n prefixes.push(normSidecar + \"/\");\n }\n for (const prefix of prefixes) {\n if (normalized === prefix.slice(0, -1) || normalized.startsWith(prefix)) {\n return false;\n }\n }\n return true;\n}\n\nasync function isRegularFile(p: string): Promise<boolean> {\n try {\n const st = await lstat(p);\n return st.isFile();\n } catch {\n return false;\n }\n}\n\nasync function fileExists(p: string): Promise<boolean> {\n try {\n await access(p, fsConstants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Perform a consolidation-undo operation.\n *\n * @param options.storage Storage manager for the memory directory.\n * @param options.memoryDir Absolute memory directory root.\n * @param options.targetPath Absolute path to the consolidated memory.\n * @param options.versioning Page-versioning config (sidecarDir must\n * match the sidecar layout used when the\n * snapshots were created).\n * @param options.dryRun When true, compute the plan but do not\n * write or archive.\n */\nexport async function runConsolidationUndo(options: {\n storage: StorageManager;\n memoryDir: string;\n targetPath: string;\n versioning: VersioningConfig;\n dryRun?: boolean;\n}): Promise<ConsolidationUndoResult> {\n const { storage, memoryDir, targetPath, versioning } = options;\n const dryRun = options.dryRun === true;\n\n const result: ConsolidationUndoResult = {\n targetPath,\n targetArchived: false,\n restores: [],\n dryRun,\n };\n\n // Defense against path-traversal (PR #637 review, codex P1): refuse\n // to operate on a target outside the configured memory directory.\n // Archive moves and eventual unlink would otherwise let an operator\n // accidentally destroy an unrelated file with memory-like\n // frontmatter. Uses the realpath-aware check so a symlinked\n // directory inside `memoryDir` can't tunnel a target past the guard.\n if (!(await isInsideDirectoryRealpath(targetPath, memoryDir))) {\n result.error = `target path ${targetPath} is outside memory directory ${memoryDir}`;\n return result;\n }\n\n // Reject targets in non-active directories (archive/, state/,\n // versioning sidecar). A target inside `.versions/...` would be\n // a sidecar snapshot, not a real consolidated memory; archiving\n // it would silently delete version history (PR #637 round-8\n // review, codex P2).\n const targetRel = path.relative(memoryDir, targetPath);\n if (!isActiveMemoryRelativePath(targetRel, versioning.sidecarDir)) {\n result.error = `target path \"${targetRel}\" is inside a non-active directory — refusing to operate`;\n return result;\n }\n\n // Load the target memory. readMemoryByPath returns null when the file\n // is absent or unparseable — surface that as a fatal error because the\n // caller cannot continue without a derived_from list.\n const target = await storage.readMemoryByPath(targetPath);\n if (!target) {\n result.error = `could not load target memory at ${targetPath}`;\n return result;\n }\n\n const derivedFrom = target.frontmatter.derived_from;\n if (!Array.isArray(derivedFrom) || derivedFrom.length === 0) {\n result.error = \"target memory has no derived_from entries — nothing to undo\";\n return result;\n }\n\n // Two-pass plan + execute (PR #637 round-4 review, cursor Medium):\n // the undo is \"all-or-nothing\" both for the archive decision AND\n // for the per-source writes. First pass validates + loads every\n // snapshot into memory; second pass writes only if every source\n // would succeed. This prevents the previous eager-write behaviour\n // where a later-failing source would leave earlier sources already\n // written to disk alongside an unarchived consolidated target.\n type RestorePlan =\n | { kind: \"skip\"; restore: ConsolidationUndoRestore }\n | { kind: \"write\"; entry: string; sourcePath: string; content: string }\n | { kind: \"recovered_existing\"; entry: string; sourcePath: string };\n\n const plans: RestorePlan[] = [];\n for (const rawEntry of derivedFrom) {\n const entry = typeof rawEntry === \"string\" ? rawEntry : String(rawEntry);\n const parsed = parseEntry(rawEntry);\n if (!parsed) {\n plans.push({\n kind: \"skip\",\n restore: {\n entry,\n sourcePath: \"\",\n outcome: \"skipped_malformed_entry\",\n detail: `expected \"<path>:<version>\" shape`,\n },\n });\n continue;\n }\n\n // Reject absolute paths in derived_from entries (PR #637 round-10\n // review, codex P1). An absolute pagePath would cause path.join to\n // ignore memoryDir, bypassing the active-directory guard downstream.\n if (path.isAbsolute(parsed.pagePath)) {\n plans.push({\n kind: \"skip\",\n restore: {\n entry,\n sourcePath: parsed.pagePath,\n outcome: \"skipped_malformed_entry\",\n detail: `derived_from path must be relative, got absolute: \"${parsed.pagePath}\"`,\n },\n });\n continue;\n }\n\n const sourcePath = path.join(memoryDir, parsed.pagePath);\n\n if (!(await isInsideDirectoryRealpath(sourcePath, memoryDir))) {\n plans.push({\n kind: \"skip\",\n restore: {\n entry,\n sourcePath,\n outcome: \"skipped_outside_memory_dir\",\n detail: `resolved path escapes memory directory ${memoryDir}`,\n },\n });\n continue;\n }\n\n // Reject source paths inside non-active directories (archive/,\n // state/, versioning sidecar). A crafted or corrupted derived_from\n // entry like \"archive/2024-01-01/x.md:1\" would otherwise be counted\n // as \"recovered_existing\" even though no active memory was restored.\n // Also resolve symlinks before checking — a derived_from entry like\n // \"facts/link/stale.md:1\" where `facts/link` points to `archive/…`\n // must be caught (PR #637 round-8 review, cursor+codex).\n let resolvedRelative = parsed.pagePath;\n try {\n const realBase = await realpath(memoryDir);\n try {\n const realSource = await realpath(sourcePath);\n const rel = path.relative(realBase, realSource);\n if (!rel.startsWith(\"..\") && !path.isAbsolute(rel)) {\n resolvedRelative = rel.replace(/\\\\/g, \"/\");\n }\n } catch {\n // realpath on the leaf failed (file doesn't exist yet). Try\n // resolving the parent directory instead — if the parent is a\n // symlink into archive/state, the leaf would be written there\n // too (PR #637 round-12 review, codex P1).\n const parentDir = path.dirname(sourcePath);\n try {\n const realParent = await realpath(parentDir);\n const parentRel = path.relative(realBase, realParent);\n if (!parentRel.startsWith(\"..\") && !path.isAbsolute(parentRel)) {\n const leafName = path.basename(sourcePath);\n resolvedRelative = path.join(parentRel, leafName).replace(/\\\\/g, \"/\");\n }\n } catch {\n // Parent also doesn't exist — fall through to text path check\n }\n }\n } catch {\n // memoryDir realpath failed — use the text path\n }\n if (!isActiveMemoryRelativePath(parsed.pagePath, versioning.sidecarDir) ||\n !isActiveMemoryRelativePath(resolvedRelative, versioning.sidecarDir)) {\n plans.push({\n kind: \"skip\",\n restore: {\n entry,\n sourcePath,\n outcome: \"skipped_non_active_path\",\n detail: `source path \"${parsed.pagePath}\" is inside a non-active directory (archive/state/versions)`,\n },\n });\n continue;\n }\n\n // Reject self-referential derived_from entries (PR #637 round-9 review,\n // codex P1). If the source resolves to the same file as the target,\n // counting it as \"recovered\" would let undo archive the target without\n // restoring any independent source — leaving no active copy. This\n // guards against corrupted or manually-edited derived_from lists.\n if (path.resolve(sourcePath) === path.resolve(targetPath)) {\n plans.push({\n kind: \"skip\",\n restore: {\n entry,\n sourcePath,\n outcome: \"skipped_self_referential\",\n detail: `derived_from entry \"${entry}\" resolves to the same file as the target — refusing to count as recovered`,\n },\n });\n continue;\n }\n\n if (await isRegularFile(sourcePath)) {\n // Source is still active (regular file present) — nothing to\n // restore but this counts as \"recovered\" for the archive\n // decision. We require a regular file specifically (PR #637\n // round-5 review, codex P2): a directory, device node, or\n // symlink at the source path should not count as \"recovered\"\n // because a later read won't find the expected memory content.\n plans.push({ kind: \"recovered_existing\", entry, sourcePath });\n continue;\n }\n if (await fileExists(sourcePath)) {\n // Something other than a regular file is at the source path\n // (directory, device node, symlink). Refuse to overwrite AND\n // refuse to count as recovered (PR #637 round-5 review, codex\n // P2) — the operator needs to clean up manually. This is a\n // blocking skip: no source writes happen, target stays active.\n plans.push({\n kind: \"skip\",\n restore: {\n entry,\n sourcePath,\n outcome: \"skipped_non_regular_file\",\n detail: \"source path is occupied by a non-regular-file; refusing to proceed\",\n },\n });\n continue;\n }\n\n let snapshotContent: string;\n try {\n snapshotContent = await getVersion(\n sourcePath,\n parsed.versionId,\n versioning,\n memoryDir,\n );\n } catch {\n plans.push({\n kind: \"skip\",\n restore: {\n entry,\n sourcePath,\n outcome: \"skipped_snapshot_missing\",\n detail: `no snapshot for version ${parsed.versionId}`,\n },\n });\n continue;\n }\n\n plans.push({ kind: \"write\", entry, sourcePath, content: snapshotContent });\n }\n\n // If any plan is a skip (anything other than \"write\" or\n // \"recovered_existing\"), the undo is over before it starts — no\n // writes happen. Reveal every per-source skip reason in the\n // result so operators can diagnose what went wrong.\n const skipped = plans.filter((p) => p.kind === \"skip\");\n if (skipped.length > 0) {\n for (const p of plans) {\n if (p.kind === \"skip\") {\n result.restores.push(p.restore);\n } else if (p.kind === \"write\") {\n // Announced-but-not-executed write — still record it so the\n // operator sees what would have been restored if the failed\n // sources had been recoverable.\n result.restores.push({\n entry: p.entry,\n sourcePath: p.sourcePath,\n outcome: dryRun ? \"skipped_dry_run\" : \"skipped_blocked_by_other_failures\",\n detail: dryRun\n ? \"would restore from snapshot (blocked by other failures)\"\n : \"snapshot available but undo aborted due to other failures\",\n });\n } else {\n result.restores.push({\n entry: p.entry,\n sourcePath: p.sourcePath,\n outcome: \"skipped_file_exists\",\n detail: \"source file already exists; no restore needed\",\n });\n }\n }\n const recovered = result.restores.filter(\n (r) => r.outcome === \"restored\" || r.outcome === \"skipped_file_exists\",\n ).length;\n if (recovered === 0) {\n result.error =\n \"no sources could be recovered (all snapshots missing or paths unsafe); target not archived to preserve data\";\n } else {\n result.error = `${skipped.length} of ${plans.length} sources could not be recovered; target not archived (undo is all-or-nothing)`;\n }\n return result;\n }\n\n // Deduplicate plans by sourcePath: duplicate derived_from entries for\n // the same source would cause the second wx-flagged write to fail with\n // EEXIST after the first succeeds. The first plan for each source wins;\n // subsequent duplicates are recorded as skipped. Applied before dry-run\n // so the preview accurately reflects what execution would do.\n const seenSourcePaths = new Set<string>();\n const dedupedPlans: RestorePlan[] = [];\n for (const p of plans) {\n if (p.kind === \"write\" || p.kind === \"recovered_existing\") {\n if (seenSourcePaths.has(p.sourcePath)) {\n dedupedPlans.push({\n kind: \"skip\",\n restore: {\n entry: p.kind === \"write\" ? p.entry : p.entry,\n sourcePath: p.sourcePath,\n outcome: \"skipped_file_exists\",\n detail: \"duplicate derived_from entry — source already processed\",\n },\n });\n continue;\n }\n seenSourcePaths.add(p.sourcePath);\n }\n dedupedPlans.push(p);\n }\n\n // Dry-run: report what each plan would do.\n if (dryRun) {\n for (const p of dedupedPlans) {\n if (p.kind === \"write\") {\n result.restores.push({\n entry: p.entry,\n sourcePath: p.sourcePath,\n outcome: \"skipped_dry_run\",\n detail: \"would restore from snapshot\",\n });\n } else if (p.kind === \"recovered_existing\") {\n result.restores.push({\n entry: p.entry,\n sourcePath: p.sourcePath,\n outcome: \"skipped_file_exists\",\n detail: \"source file already exists; no restore needed\",\n });\n } else if (p.kind === \"skip\" && p.restore) {\n result.restores.push(p.restore);\n }\n }\n return result;\n }\n\n // All validations passed — execute writes. A write failure here\n // is a filesystem problem rather than a provenance problem, but\n // any failure still aborts the archive.\n\n let writeFailed = false;\n for (const p of dedupedPlans) {\n if (p.kind === \"skip\") {\n // Dedup-generated skip entries and other pre-write skips.\n if (p.restore) result.restores.push(p.restore);\n continue;\n }\n if (p.kind === \"recovered_existing\") {\n result.restores.push({\n entry: p.entry,\n sourcePath: p.sourcePath,\n outcome: \"skipped_file_exists\",\n detail: \"source file already exists; no restore needed\",\n });\n continue;\n }\n if (p.kind === \"write\") {\n if (writeFailed) {\n // All-or-nothing: once a write fails, skip all remaining writes\n // so the target is not archived with partial source coverage.\n result.restores.push({\n entry: p.entry,\n sourcePath: p.sourcePath,\n outcome: \"skipped_blocked_by_other_failures\",\n detail: \"a prior source write failed; skipping remaining writes to honor all-or-nothing contract\",\n });\n continue;\n }\n try {\n await mkdir(path.dirname(p.sourcePath), { recursive: true });\n // Use exclusive create (wx / O_EXCL) so that if another process\n // recreates the source file between planning and execution, this\n // write fails with EEXIST instead of silently overwriting the new\n // file (PR #637 round-11 review, codex P1).\n await writeFile(p.sourcePath, p.content, { encoding: \"utf-8\", flag: \"wx\" });\n result.restores.push({\n entry: p.entry,\n sourcePath: p.sourcePath,\n outcome: \"restored\",\n });\n } catch (err) {\n writeFailed = true;\n result.restores.push({\n entry: p.entry,\n sourcePath: p.sourcePath,\n outcome: \"skipped_write_failed\",\n detail: `write failed: ${err instanceof Error ? err.message : String(err)}`,\n });\n }\n }\n }\n\n if (writeFailed) {\n result.error =\n \"one or more source writes failed mid-restore; target not archived to preserve data\";\n return result;\n }\n\n // Archive the target memory. archiveMemory returns null on\n // failure — surface that as a fatal error (PR #637 round-5 review,\n // codex P2) so automation doesn't mistake a half-undo for a clean\n // run. The already-completed restores still roll forward; the\n // result.restores list records what was written.\n const archivedAt = await storage.archiveMemory(target, {\n actor: \"consolidate-undo\",\n reasonCode: \"consolidation-undo\",\n });\n result.targetArchived = archivedAt !== null;\n if (!result.targetArchived) {\n result.error =\n \"sources restored successfully but archiving the consolidated target failed; inspect storage for manual cleanup\";\n }\n return result;\n}\n\n/**\n * Render a consolidation-undo result as a human-readable multi-line\n * string for the CLI. Extracted so tests can snapshot the formatting\n * without parsing stdout.\n */\nexport function formatConsolidationUndoResult(result: ConsolidationUndoResult): string {\n const lines: string[] = [];\n lines.push(`consolidate undo ${result.dryRun ? \"(dry run) \" : \"\"}→ ${result.targetPath}`);\n // Emit per-restore details BEFORE the error (PR #637 review, cursor\n // Medium): the \"no sources could be recovered\" error is set after\n // the restore loop ran, so operators need the per-source skip\n // reasons to diagnose which snapshots were missing / outside\n // memoryDir / malformed. Early-bail errors (unloadable target,\n // target outside memoryDir, no derived_from) run before the loop,\n // so `result.restores` is empty in those cases and this block is a\n // no-op.\n for (const r of result.restores) {\n lines.push(` - ${r.entry} → ${r.outcome}${r.detail ? ` (${r.detail})` : \"\"}`);\n }\n if (result.error) {\n lines.push(` ERROR: ${result.error}`);\n return lines.join(\"\\n\");\n }\n lines.push(\n result.dryRun\n ? \" (dry run — no files were modified, target not archived)\"\n : ` target archived: ${result.targetArchived ? \"yes\" : \"no\"}`,\n );\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/consolidation-undo.ts"],"sourcesContent":["/**\n * Consolidation undo (issue #561 PR 5).\n *\n * Reverts a consolidated memory by restoring each source memory from its\n * `derived_from` snapshot and archiving the target.\n *\n * Contract:\n * - Load the target memory markdown file via its absolute path.\n * - For every `\"<rel>:<version>\"` entry in `derived_from`, fetch the\n * snapshot content via `page-versioning.getVersion` and restore it\n * to the original relative path. If the restore target file\n * already exists, we skip overwriting it (the source was never\n * archived, or was re-created since) and record the skip.\n * - Archive the target with reason code `\"consolidation-undo\"` so the\n * lifecycle ledger records the undo.\n * - Dry-run mode produces the same plan without touching disk.\n *\n * The helper is kept pure over `StorageManager` so the CLI can reuse it\n * without additional wiring, and tests can exercise the plan logic\n * directly.\n */\n\nimport path from \"node:path\";\nimport { mkdir, writeFile, access, realpath, lstat } from \"node:fs/promises\";\nimport { constants as fsConstants } from \"node:fs\";\nimport type { StorageManager } from \"./storage.js\";\nimport type { VersioningConfig } from \"./page-versioning.js\";\nimport { getVersion } from \"./page-versioning.js\";\n\n/**\n * Outcome of restoring a single `derived_from` source.\n */\nexport interface ConsolidationUndoRestore {\n /** The raw `\"<relpath>:<version>\"` entry from `derived_from`. */\n entry: string;\n /** Absolute path where the source would be / was restored. */\n sourcePath: string;\n /** What actually happened. */\n outcome:\n | \"restored\"\n | \"skipped_file_exists\"\n | \"skipped_non_regular_file\"\n | \"skipped_snapshot_missing\"\n | \"skipped_malformed_entry\"\n | \"skipped_outside_memory_dir\"\n | \"skipped_non_active_path\"\n | \"skipped_self_referential\"\n | \"skipped_write_failed\"\n | \"skipped_blocked_by_other_failures\"\n | \"skipped_dry_run\";\n /** Human-readable detail. */\n detail?: string;\n}\n\n/**\n * Plan + result of a `remnic consolidate undo` invocation.\n */\nexport interface ConsolidationUndoResult {\n /** Absolute path to the target memory. */\n targetPath: string;\n /** True when the target was archived successfully. */\n targetArchived: boolean;\n /** Per-source restore outcomes. */\n restores: ConsolidationUndoRestore[];\n /** Whether the run was a dry-run plan only. */\n dryRun: boolean;\n /** Fatal error, if any — the run bails early. */\n error?: string;\n}\n\nconst DERIVED_FROM_ENTRY_RE = /^(.+):(\\d+)$/;\n\nfunction parseEntry(entry: unknown): { pagePath: string; versionId: string } | null {\n // Non-string entries (PR #637 round-3 review, cursor Low) can arrive\n // from hostile on-disk frontmatter — guard against a .match() crash.\n if (typeof entry !== \"string\") return null;\n const match = entry.match(DERIVED_FROM_ENTRY_RE);\n if (!match) return null;\n return { pagePath: match[1], versionId: match[2] };\n}\n\n/**\n * Verify that `candidate` resolves inside `root` (defense against\n * path-traversal in `derived_from` entries and user-facing target\n * paths). Path-string normalization only; for symlink-aware checks\n * use `isInsideDirectoryRealpath`. Both paths are resolved to\n * absolute form before comparison so `..` segments, symlinks-as-\n * strings, and relative prefixes are normalized.\n */\nexport function isInsideDirectory(candidate: string, root: string): boolean {\n const normRoot = path.resolve(root);\n const normCandidate = path.resolve(candidate);\n const rel = path.relative(normRoot, normCandidate);\n if (rel.length === 0) return true;\n if (rel.startsWith(\"..\")) return false;\n if (path.isAbsolute(rel)) return false;\n return true;\n}\n\n/**\n * Symlink-aware containment check (PR #637 round-2 review, codex P1).\n *\n * `isInsideDirectory` only normalizes path strings — if a `derived_from`\n * entry resolves through a symlink inside `memoryDir` that points\n * outside, the string check passes but the subsequent `writeFile` would\n * land outside the memory tree. Use this guard for any path that is\n * about to be written.\n *\n * Walks every parent directory between `candidate` and `root`,\n * `realpath`-ing each segment that exists and rejecting when any\n * segment escapes `root`. Non-existent parents are resolved as the\n * canonicalized deepest-existing ancestor plus the trailing segments,\n * so a not-yet-created target file still gets the symlink check on its\n * existing parent directories.\n */\nexport async function isInsideDirectoryRealpath(\n candidate: string,\n root: string,\n): Promise<boolean> {\n if (!isInsideDirectory(candidate, root)) return false;\n // Reject raw `..` segments before canonicalization so that symlinks\n // cannot be hidden behind intermediate dot-dot components (PR #637\n // round-14 review, codex P1).\n const rawSegments = candidate.replace(/\\\\/g, \"/\").split(\"/\");\n if (rawSegments.some((s) => s === \"..\")) return false;\n let resolvedRoot: string;\n try {\n resolvedRoot = await realpath(path.resolve(root));\n } catch {\n return false;\n }\n const normCandidate = path.resolve(candidate);\n\n // Reject dangling symlinks (PR #637 round-3 review, codex P1).\n // If the candidate itself is a symlink (even if its target doesn't\n // exist), Node will follow it when we later call `writeFile`.\n // `lstat` inspects the link itself without dereferencing; if it\n // succeeds and reports a symlink, we treat the candidate as\n // unsafe. We must check every non-root ancestor too — a symlink\n // anywhere along the path lets an attacker redirect writes.\n const normRoot = path.resolve(root);\n const relFromRoot = path.relative(normRoot, normCandidate);\n const segments = relFromRoot.length > 0 ? relFromRoot.split(path.sep) : [];\n for (let i = 0; i <= segments.length; i++) {\n const probe = i === 0 ? normRoot : path.join(normRoot, ...segments.slice(0, i));\n try {\n const st = await lstat(probe);\n if (st.isSymbolicLink() && probe !== normRoot) {\n // A symlink on the path — resolve THIS segment and bail out\n // if the resolved target escapes `resolvedRoot`.\n let target: string;\n try {\n target = await realpath(probe);\n } catch {\n // Dangling symlink inside memoryDir — always unsafe.\n return false;\n }\n const rel = path.relative(resolvedRoot, target);\n if (rel.length === 0) continue;\n if (rel.startsWith(\"..\") || path.isAbsolute(rel)) return false;\n }\n } catch {\n // Segment doesn't exist yet — that's fine, fall through to the\n // textual containment verification below.\n }\n }\n\n // Walk up from the candidate until we hit a path that exists, then\n // realpath THAT and re-apply the trailing segments textually.\n const parts = normCandidate.split(path.sep);\n for (let i = parts.length; i > 0; i--) {\n const probe = parts.slice(0, i).join(path.sep) || path.sep;\n try {\n const resolved = await realpath(probe);\n // Re-join any trailing segments that didn't exist yet.\n const trailing = parts.slice(i).join(path.sep);\n const final = trailing.length > 0 ? path.join(resolved, trailing) : resolved;\n // Now apply the textual containment check against the canonical\n // `resolvedRoot`.\n const rel = path.relative(resolvedRoot, final);\n if (rel.length === 0) return true;\n if (rel.startsWith(\"..\")) return false;\n if (path.isAbsolute(rel)) return false;\n return true;\n } catch {\n continue;\n }\n }\n // Nothing along the path resolvable — treat as outside by default.\n return false;\n}\n\n/**\n * Directories under memoryDir that are NOT active memory locations.\n * A `derived_from` entry pointing into one of these should not be\n * counted as \"recovered_existing\" (PR #637 round-7 review, codex P2).\n * The versioning sidecar directory is included dynamically via the\n * `sidecarDir` parameter (PR #637 round-8 review, codex P2).\n */\nconst NON_ACTIVE_PREFIXES = [\"archive/\", \"state/\"];\n\n/**\n * Normalize a relative path by collapsing `.` and `..` segments so\n * that crafted entries like `\"facts/../archive/x.md\"` are reduced to\n * `\"archive/x.md\"` before the non-active-prefix check.\n */\nfunction normalizeRelativePath(p: string): string {\n // Normalize separators, split into segments, then resolve.\n const parts = p.replace(/\\\\/g, \"/\").split(\"/\");\n const resolved: string[] = [];\n for (const seg of parts) {\n if (seg === \"\" || seg === \".\") continue;\n if (seg === \"..\") {\n if (resolved.length > 0) resolved.pop();\n // If \"..\" pops past the root, we let the caller's containment\n // check catch it — don't silently drop.\n } else {\n resolved.push(seg);\n }\n }\n return resolved.join(\"/\");\n}\n\n/**\n * Check that a relative path (relative to memoryDir) points to an\n * active memory location rather than an internal/archive directory.\n * Returns `true` when the normalised `pagePath` does NOT start with\n * a known non-active prefix.\n *\n * @param pagePath Relative path from `derived_from` entry.\n * @param sidecarDir Optional versioning sidecar directory name\n * (e.g. `\".versions\"`). When provided, paths\n * under this directory are also rejected as\n * non-active.\n */\nexport function isActiveMemoryRelativePath(\n pagePath: string,\n sidecarDir?: string,\n): boolean {\n const normalized = normalizeRelativePath(pagePath);\n const prefixes = [...NON_ACTIVE_PREFIXES];\n if (sidecarDir) {\n const normSidecar = normalizeRelativePath(sidecarDir);\n prefixes.push(normSidecar + \"/\");\n }\n for (const prefix of prefixes) {\n if (normalized === prefix.slice(0, -1) || normalized.startsWith(prefix)) {\n return false;\n }\n }\n return true;\n}\n\nasync function isRegularFile(p: string): Promise<boolean> {\n try {\n const st = await lstat(p);\n return st.isFile();\n } catch {\n return false;\n }\n}\n\nasync function fileExists(p: string): Promise<boolean> {\n try {\n await access(p, fsConstants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Perform a consolidation-undo operation.\n *\n * @param options.storage Storage manager for the memory directory.\n * @param options.memoryDir Absolute memory directory root.\n * @param options.targetPath Absolute path to the consolidated memory.\n * @param options.versioning Page-versioning config (sidecarDir must\n * match the sidecar layout used when the\n * snapshots were created).\n * @param options.dryRun When true, compute the plan but do not\n * write or archive.\n */\nexport async function runConsolidationUndo(options: {\n storage: StorageManager;\n memoryDir: string;\n targetPath: string;\n versioning: VersioningConfig;\n dryRun?: boolean;\n}): Promise<ConsolidationUndoResult> {\n const { storage, memoryDir, targetPath, versioning } = options;\n const dryRun = options.dryRun === true;\n\n const result: ConsolidationUndoResult = {\n targetPath,\n targetArchived: false,\n restores: [],\n dryRun,\n };\n\n // Defense against path-traversal (PR #637 review, codex P1): refuse\n // to operate on a target outside the configured memory directory.\n // Archive moves and eventual unlink would otherwise let an operator\n // accidentally destroy an unrelated file with memory-like\n // frontmatter. Uses the realpath-aware check so a symlinked\n // directory inside `memoryDir` can't tunnel a target past the guard.\n if (!(await isInsideDirectoryRealpath(targetPath, memoryDir))) {\n result.error = `target path ${targetPath} is outside memory directory ${memoryDir}`;\n return result;\n }\n\n // Reject targets in non-active directories (archive/, state/,\n // versioning sidecar). A target inside `.versions/...` would be\n // a sidecar snapshot, not a real consolidated memory; archiving\n // it would silently delete version history (PR #637 round-8\n // review, codex P2).\n const targetRel = path.relative(memoryDir, targetPath);\n if (!isActiveMemoryRelativePath(targetRel, versioning.sidecarDir)) {\n result.error = `target path \"${targetRel}\" is inside a non-active directory — refusing to operate`;\n return result;\n }\n\n // Load the target memory. readMemoryByPath returns null when the file\n // is absent or unparseable — surface that as a fatal error because the\n // caller cannot continue without a derived_from list.\n const target = await storage.readMemoryByPath(targetPath);\n if (!target) {\n result.error = `could not load target memory at ${targetPath}`;\n return result;\n }\n\n const derivedFrom = target.frontmatter.derived_from;\n if (!Array.isArray(derivedFrom) || derivedFrom.length === 0) {\n result.error = \"target memory has no derived_from entries — nothing to undo\";\n return result;\n }\n\n // Two-pass plan + execute (PR #637 round-4 review, cursor Medium):\n // the undo is \"all-or-nothing\" both for the archive decision AND\n // for the per-source writes. First pass validates + loads every\n // snapshot into memory; second pass writes only if every source\n // would succeed. This prevents the previous eager-write behaviour\n // where a later-failing source would leave earlier sources already\n // written to disk alongside an unarchived consolidated target.\n type RestorePlan =\n | { kind: \"skip\"; restore: ConsolidationUndoRestore }\n | { kind: \"write\"; entry: string; sourcePath: string; content: string }\n | { kind: \"recovered_existing\"; entry: string; sourcePath: string };\n\n const plans: RestorePlan[] = [];\n for (const rawEntry of derivedFrom) {\n const entry = typeof rawEntry === \"string\" ? rawEntry : String(rawEntry);\n const parsed = parseEntry(rawEntry);\n if (!parsed) {\n plans.push({\n kind: \"skip\",\n restore: {\n entry,\n sourcePath: \"\",\n outcome: \"skipped_malformed_entry\",\n detail: `expected \"<path>:<version>\" shape`,\n },\n });\n continue;\n }\n\n // Reject absolute paths in derived_from entries (PR #637 round-10\n // review, codex P1). An absolute pagePath would cause path.join to\n // ignore memoryDir, bypassing the active-directory guard downstream.\n if (path.isAbsolute(parsed.pagePath)) {\n plans.push({\n kind: \"skip\",\n restore: {\n entry,\n sourcePath: parsed.pagePath,\n outcome: \"skipped_malformed_entry\",\n detail: `derived_from path must be relative, got absolute: \"${parsed.pagePath}\"`,\n },\n });\n continue;\n }\n\n const sourcePath = path.join(memoryDir, parsed.pagePath);\n\n if (!(await isInsideDirectoryRealpath(sourcePath, memoryDir))) {\n plans.push({\n kind: \"skip\",\n restore: {\n entry,\n sourcePath,\n outcome: \"skipped_outside_memory_dir\",\n detail: `resolved path escapes memory directory ${memoryDir}`,\n },\n });\n continue;\n }\n\n // Reject source paths inside non-active directories (archive/,\n // state/, versioning sidecar). A crafted or corrupted derived_from\n // entry like \"archive/2024-01-01/x.md:1\" would otherwise be counted\n // as \"recovered_existing\" even though no active memory was restored.\n // Also resolve symlinks before checking — a derived_from entry like\n // \"facts/link/stale.md:1\" where `facts/link` points to `archive/…`\n // must be caught (PR #637 round-8 review, cursor+codex).\n let resolvedRelative = parsed.pagePath;\n try {\n const realBase = await realpath(memoryDir);\n try {\n const realSource = await realpath(sourcePath);\n const rel = path.relative(realBase, realSource);\n if (!rel.startsWith(\"..\") && !path.isAbsolute(rel)) {\n resolvedRelative = rel.replace(/\\\\/g, \"/\");\n }\n } catch {\n // realpath on the leaf failed (file doesn't exist yet). Try\n // resolving the parent directory instead — if the parent is a\n // symlink into archive/state, the leaf would be written there\n // too (PR #637 round-12 review, codex P1).\n const parentDir = path.dirname(sourcePath);\n try {\n const realParent = await realpath(parentDir);\n const parentRel = path.relative(realBase, realParent);\n if (!parentRel.startsWith(\"..\") && !path.isAbsolute(parentRel)) {\n const leafName = path.basename(sourcePath);\n resolvedRelative = path.join(parentRel, leafName).replace(/\\\\/g, \"/\");\n }\n } catch {\n // Parent also doesn't exist — fall through to text path check\n }\n }\n } catch {\n // memoryDir realpath failed — use the text path\n }\n if (!isActiveMemoryRelativePath(parsed.pagePath, versioning.sidecarDir) ||\n !isActiveMemoryRelativePath(resolvedRelative, versioning.sidecarDir)) {\n plans.push({\n kind: \"skip\",\n restore: {\n entry,\n sourcePath,\n outcome: \"skipped_non_active_path\",\n detail: `source path \"${parsed.pagePath}\" is inside a non-active directory (archive/state/versions)`,\n },\n });\n continue;\n }\n\n // Reject self-referential derived_from entries (PR #637 round-9 review,\n // codex P1). If the source resolves to the same file as the target,\n // counting it as \"recovered\" would let undo archive the target without\n // restoring any independent source — leaving no active copy. This\n // guards against corrupted or manually-edited derived_from lists.\n if (path.resolve(sourcePath) === path.resolve(targetPath)) {\n plans.push({\n kind: \"skip\",\n restore: {\n entry,\n sourcePath,\n outcome: \"skipped_self_referential\",\n detail: `derived_from entry \"${entry}\" resolves to the same file as the target — refusing to count as recovered`,\n },\n });\n continue;\n }\n\n if (await isRegularFile(sourcePath)) {\n // Source is still active (regular file present) — nothing to\n // restore but this counts as \"recovered\" for the archive\n // decision. We require a regular file specifically (PR #637\n // round-5 review, codex P2): a directory, device node, or\n // symlink at the source path should not count as \"recovered\"\n // because a later read won't find the expected memory content.\n plans.push({ kind: \"recovered_existing\", entry, sourcePath });\n continue;\n }\n if (await fileExists(sourcePath)) {\n // Something other than a regular file is at the source path\n // (directory, device node, symlink). Refuse to overwrite AND\n // refuse to count as recovered (PR #637 round-5 review, codex\n // P2) — the operator needs to clean up manually. This is a\n // blocking skip: no source writes happen, target stays active.\n plans.push({\n kind: \"skip\",\n restore: {\n entry,\n sourcePath,\n outcome: \"skipped_non_regular_file\",\n detail: \"source path is occupied by a non-regular-file; refusing to proceed\",\n },\n });\n continue;\n }\n\n let snapshotContent: string;\n try {\n snapshotContent = await getVersion(\n sourcePath,\n parsed.versionId,\n versioning,\n memoryDir,\n );\n } catch {\n plans.push({\n kind: \"skip\",\n restore: {\n entry,\n sourcePath,\n outcome: \"skipped_snapshot_missing\",\n detail: `no snapshot for version ${parsed.versionId}`,\n },\n });\n continue;\n }\n\n plans.push({ kind: \"write\", entry, sourcePath, content: snapshotContent });\n }\n\n // If any plan is a skip (anything other than \"write\" or\n // \"recovered_existing\"), the undo is over before it starts — no\n // writes happen. Reveal every per-source skip reason in the\n // result so operators can diagnose what went wrong.\n const skipped = plans.filter((p) => p.kind === \"skip\");\n if (skipped.length > 0) {\n for (const p of plans) {\n if (p.kind === \"skip\") {\n result.restores.push(p.restore);\n } else if (p.kind === \"write\") {\n // Announced-but-not-executed write — still record it so the\n // operator sees what would have been restored if the failed\n // sources had been recoverable.\n result.restores.push({\n entry: p.entry,\n sourcePath: p.sourcePath,\n outcome: dryRun ? \"skipped_dry_run\" : \"skipped_blocked_by_other_failures\",\n detail: dryRun\n ? \"would restore from snapshot (blocked by other failures)\"\n : \"snapshot available but undo aborted due to other failures\",\n });\n } else {\n result.restores.push({\n entry: p.entry,\n sourcePath: p.sourcePath,\n outcome: \"skipped_file_exists\",\n detail: \"source file already exists; no restore needed\",\n });\n }\n }\n const recovered = result.restores.filter(\n (r) => r.outcome === \"restored\" || r.outcome === \"skipped_file_exists\",\n ).length;\n if (recovered === 0) {\n result.error =\n \"no sources could be recovered (all snapshots missing or paths unsafe); target not archived to preserve data\";\n } else {\n result.error = `${skipped.length} of ${plans.length} sources could not be recovered; target not archived (undo is all-or-nothing)`;\n }\n return result;\n }\n\n // Deduplicate plans by sourcePath: duplicate derived_from entries for\n // the same source would cause the second wx-flagged write to fail with\n // EEXIST after the first succeeds. The first plan for each source wins;\n // subsequent duplicates are recorded as skipped. Applied before dry-run\n // so the preview accurately reflects what execution would do.\n const seenSourcePaths = new Set<string>();\n const dedupedPlans: RestorePlan[] = [];\n for (const p of plans) {\n if (p.kind === \"write\" || p.kind === \"recovered_existing\") {\n if (seenSourcePaths.has(p.sourcePath)) {\n dedupedPlans.push({\n kind: \"skip\",\n restore: {\n entry: p.kind === \"write\" ? p.entry : p.entry,\n sourcePath: p.sourcePath,\n outcome: \"skipped_file_exists\",\n detail: \"duplicate derived_from entry — source already processed\",\n },\n });\n continue;\n }\n seenSourcePaths.add(p.sourcePath);\n }\n dedupedPlans.push(p);\n }\n\n // Dry-run: report what each plan would do.\n if (dryRun) {\n for (const p of dedupedPlans) {\n if (p.kind === \"write\") {\n result.restores.push({\n entry: p.entry,\n sourcePath: p.sourcePath,\n outcome: \"skipped_dry_run\",\n detail: \"would restore from snapshot\",\n });\n } else if (p.kind === \"recovered_existing\") {\n result.restores.push({\n entry: p.entry,\n sourcePath: p.sourcePath,\n outcome: \"skipped_file_exists\",\n detail: \"source file already exists; no restore needed\",\n });\n } else if (p.kind === \"skip\" && p.restore) {\n result.restores.push(p.restore);\n }\n }\n return result;\n }\n\n // All validations passed — execute writes. A write failure here\n // is a filesystem problem rather than a provenance problem, but\n // any failure still aborts the archive.\n\n let writeFailed = false;\n for (const p of dedupedPlans) {\n if (p.kind === \"skip\") {\n // Dedup-generated skip entries and other pre-write skips.\n if (p.restore) result.restores.push(p.restore);\n continue;\n }\n if (p.kind === \"recovered_existing\") {\n result.restores.push({\n entry: p.entry,\n sourcePath: p.sourcePath,\n outcome: \"skipped_file_exists\",\n detail: \"source file already exists; no restore needed\",\n });\n continue;\n }\n if (p.kind === \"write\") {\n if (writeFailed) {\n // All-or-nothing: once a write fails, skip all remaining writes\n // so the target is not archived with partial source coverage.\n result.restores.push({\n entry: p.entry,\n sourcePath: p.sourcePath,\n outcome: \"skipped_blocked_by_other_failures\",\n detail: \"a prior source write failed; skipping remaining writes to honor all-or-nothing contract\",\n });\n continue;\n }\n try {\n await mkdir(path.dirname(p.sourcePath), { recursive: true });\n // Use exclusive create (wx / O_EXCL) so that if another process\n // recreates the source file between planning and execution, this\n // write fails with EEXIST instead of silently overwriting the new\n // file (PR #637 round-11 review, codex P1).\n await writeFile(p.sourcePath, p.content, { encoding: \"utf-8\", flag: \"wx\" });\n result.restores.push({\n entry: p.entry,\n sourcePath: p.sourcePath,\n outcome: \"restored\",\n });\n } catch (err) {\n writeFailed = true;\n result.restores.push({\n entry: p.entry,\n sourcePath: p.sourcePath,\n outcome: \"skipped_write_failed\",\n detail: `write failed: ${err instanceof Error ? err.message : String(err)}`,\n });\n }\n }\n }\n\n if (writeFailed) {\n result.error =\n \"one or more source writes failed mid-restore; target not archived to preserve data\";\n return result;\n }\n\n // Archive the target memory. archiveMemory returns null on\n // failure — surface that as a fatal error (PR #637 round-5 review,\n // codex P2) so automation doesn't mistake a half-undo for a clean\n // run. The already-completed restores still roll forward; the\n // result.restores list records what was written.\n const archivedAt = await storage.archiveMemory(target, {\n actor: \"consolidate-undo\",\n reasonCode: \"consolidation-undo\",\n });\n result.targetArchived = archivedAt !== null;\n if (!result.targetArchived) {\n result.error =\n \"sources restored successfully but archiving the consolidated target failed; inspect storage for manual cleanup\";\n }\n return result;\n}\n\n/**\n * Render a consolidation-undo result as a human-readable multi-line\n * string for the CLI. Extracted so tests can snapshot the formatting\n * without parsing stdout.\n */\nexport function formatConsolidationUndoResult(result: ConsolidationUndoResult): string {\n const lines: string[] = [];\n lines.push(`consolidate undo ${result.dryRun ? \"(dry run) \" : \"\"}→ ${result.targetPath}`);\n // Emit per-restore details BEFORE the error (PR #637 review, cursor\n // Medium): the \"no sources could be recovered\" error is set after\n // the restore loop ran, so operators need the per-source skip\n // reasons to diagnose which snapshots were missing / outside\n // memoryDir / malformed. Early-bail errors (unloadable target,\n // target outside memoryDir, no derived_from) run before the loop,\n // so `result.restores` is empty in those cases and this block is a\n // no-op.\n for (const r of result.restores) {\n lines.push(` - ${r.entry} → ${r.outcome}${r.detail ? ` (${r.detail})` : \"\"}`);\n }\n if (result.error) {\n lines.push(` ERROR: ${result.error}`);\n return lines.join(\"\\n\");\n }\n lines.push(\n result.dryRun\n ? \" (dry run — no files were modified, target not archived)\"\n : ` target archived: ${result.targetArchived ? \"yes\" : \"no\"}`,\n );\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":[]}
@@ -8,6 +8,7 @@ import {
8
8
  writePair,
9
9
  writePairs
10
10
  } from "./chunk-YNB73F22.js";
11
+ import "./chunk-PZ5AY32C.js";
11
12
  export {
12
13
  computePairId,
13
14
  isCoolingDown,
@@ -18,4 +19,4 @@ export {
18
19
  writePair,
19
20
  writePairs
20
21
  };
21
- //# sourceMappingURL=contradiction-review-WIUBAR52.js.map
22
+ //# sourceMappingURL=contradiction-review-5LTTVDQV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}