@remnic/core 9.3.698 → 9.3.700

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 (271) hide show
  1. package/dist/access-boundary.d.ts +5 -5
  2. package/dist/access-boundary.js +14 -10
  3. package/dist/access-cli.js +28 -26
  4. package/dist/access-cli.js.map +1 -1
  5. package/dist/access-http.d.ts +5 -5
  6. package/dist/access-http.js +18 -14
  7. package/dist/access-mcp.d.ts +5 -5
  8. package/dist/access-mcp.js +17 -13
  9. package/dist/access-operations.d.ts +5 -5
  10. package/dist/access-operations.js +16 -12
  11. package/dist/access-schema.js +3 -3
  12. package/dist/{access-service-DftqtNUy.d.ts → access-service-Cte3ol0W.d.ts} +2 -2
  13. package/dist/access-service.d.ts +5 -5
  14. package/dist/access-service.js +13 -9
  15. package/dist/access-surface-catalog.d.ts +5 -5
  16. package/dist/action-confidence.d.ts +1 -1
  17. package/dist/action-confidence.js +2 -2
  18. package/dist/active-memory-bridge.d.ts +1 -1
  19. package/dist/active-recall.d.ts +1 -1
  20. package/dist/active-recall.js +1 -1
  21. package/dist/behavior-learner.d.ts +1 -1
  22. package/dist/behavior-signals.d.ts +1 -1
  23. package/dist/bootstrap.d.ts +4 -4
  24. package/dist/briefing.d.ts +1 -1
  25. package/dist/briefing.js +7 -3
  26. package/dist/buffer-surprise-report.d.ts +1 -1
  27. package/dist/buffer.d.ts +1 -1
  28. package/dist/calibration.d.ts +1 -1
  29. package/dist/capabilities.d.ts +1 -1
  30. package/dist/{catalog-D7YDNNF7.d.ts → catalog-DN1PzThs.d.ts} +1 -1
  31. package/dist/causal-behavior.d.ts +1 -1
  32. package/dist/causal-consolidation.d.ts +1 -1
  33. package/dist/causal-consolidation.js +8 -4
  34. package/dist/causal-consolidation.js.map +1 -1
  35. package/dist/{chunk-SJQ4HY3E.js → chunk-2NWHLAXX.js} +2 -2
  36. package/dist/{chunk-UDDSC6PO.js → chunk-33L6XHU2.js} +54 -6
  37. package/dist/chunk-33L6XHU2.js.map +1 -0
  38. package/dist/{chunk-XFG3PVZE.js → chunk-3E5WRQNQ.js} +8 -8
  39. package/dist/{chunk-EZWQZYLK.js → chunk-6JDGADXK.js} +2 -2
  40. package/dist/{chunk-IZ5E6ZTK.js → chunk-CCOXIDRM.js} +6 -6
  41. package/dist/{chunk-COEZR6F5.js → chunk-CHM274U6.js} +2 -2
  42. package/dist/{chunk-TIVZ4MCG.js → chunk-D75JXBV4.js} +3 -3
  43. package/dist/{chunk-M4DQWUKX.js → chunk-DR2JTSLZ.js} +2 -2
  44. package/dist/{chunk-XL5RSHZP.js → chunk-EOBJRBLC.js} +2 -2
  45. package/dist/{chunk-YY2HGKWR.js → chunk-G5PKTQ5J.js} +2 -2
  46. package/dist/{chunk-JYVD2XZ4.js → chunk-GA5A6MJH.js} +57 -24
  47. package/dist/chunk-GA5A6MJH.js.map +1 -0
  48. package/dist/{chunk-E6GOVHHJ.js → chunk-GYVVQYA3.js} +3 -3
  49. package/dist/{chunk-2KJG6ZZK.js → chunk-HDLC75NX.js} +2 -2
  50. package/dist/{chunk-AC5LO7IU.js → chunk-I4WBNM3M.js} +7 -1
  51. package/dist/chunk-I4WBNM3M.js.map +1 -0
  52. package/dist/{chunk-QVTSLRKT.js → chunk-IJEZMWKA.js} +3 -3
  53. package/dist/{chunk-ZXUOAFUG.js → chunk-IKNQAGBV.js} +1 -1
  54. package/dist/chunk-IKNQAGBV.js.map +1 -0
  55. package/dist/{chunk-N55RJT4N.js → chunk-JKW5XSWC.js} +2 -2
  56. package/dist/{chunk-CQ4PGFMC.js → chunk-JX3YZVII.js} +4 -4
  57. package/dist/{chunk-3LSSFCB4.js → chunk-K4DWSPMW.js} +11 -1
  58. package/dist/chunk-K4DWSPMW.js.map +1 -0
  59. package/dist/{chunk-HZ5DRA4Q.js → chunk-NHBEO3F3.js} +20 -20
  60. package/dist/{chunk-FSEQXHEZ.js → chunk-O54DY26V.js} +2 -2
  61. package/dist/{chunk-MRFVMSTR.js → chunk-OMNTQPZO.js} +20 -1
  62. package/dist/{chunk-MRFVMSTR.js.map → chunk-OMNTQPZO.js.map} +1 -1
  63. package/dist/chunk-PCZR32VL.js +446 -0
  64. package/dist/chunk-PCZR32VL.js.map +1 -0
  65. package/dist/{chunk-2K64VH66.js → chunk-PQG4T5V3.js} +22 -2
  66. package/dist/chunk-PQG4T5V3.js.map +1 -0
  67. package/dist/{chunk-JI6HWBYL.js → chunk-Q4O3ET6F.js} +2 -2
  68. package/dist/{chunk-ANZLT74L.js → chunk-RJ2THZ4H.js} +2 -2
  69. package/dist/{chunk-LPIWEKZ3.js → chunk-SDPDU2PM.js} +3 -3
  70. package/dist/{chunk-A62RAIBN.js → chunk-SEWF2O74.js} +2 -2
  71. package/dist/{chunk-2ULWWAQH.js → chunk-SMIVW7XC.js} +2 -2
  72. package/dist/{chunk-RTN2BLZM.js → chunk-T5QAZIBO.js} +2 -2
  73. package/dist/{chunk-RIC5U67B.js → chunk-X74FJSW7.js} +232 -4
  74. package/dist/chunk-X74FJSW7.js.map +1 -0
  75. package/dist/{chunk-SIDSEXUG.js → chunk-XY4WJTEX.js} +2 -2
  76. package/dist/{chunk-LIWU4QI6.js → chunk-YMTGXDN6.js} +5 -5
  77. package/dist/chunk-YPR7DOPD.js +286 -0
  78. package/dist/chunk-YPR7DOPD.js.map +1 -0
  79. package/dist/{chunk-MRX6S22R.js → chunk-YXIFA36P.js} +2 -2
  80. package/dist/{chunk-AH2JUU6X.js → chunk-Z2YFLXHU.js} +2 -2
  81. package/dist/{chunk-PJIHCOGV.js → chunk-ZDK2IW5F.js} +2 -2
  82. package/dist/{cli-DUMkkdLl.d.ts → cli--yVN9yEV.d.ts} +3 -3
  83. package/dist/cli.d.ts +6 -6
  84. package/dist/cli.js +32 -28
  85. package/dist/compounding/engine.d.ts +1 -1
  86. package/dist/compounding/engine.js +7 -3
  87. package/dist/compounding/preference-consolidator.d.ts +1 -1
  88. package/dist/compression-optimizer.d.ts +1 -1
  89. package/dist/config.d.ts +1 -1
  90. package/dist/config.js +1 -1
  91. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  92. package/dist/connectors/codex-materialize-runner.js +7 -3
  93. package/dist/connectors/codex-materialize.d.ts +1 -1
  94. package/dist/connectors/index.d.ts +1 -1
  95. package/dist/connectors/index.js +7 -3
  96. package/dist/consolidation-provenance-check.d.ts +1 -1
  97. package/dist/consolidation-undo.d.ts +1 -1
  98. package/dist/contradiction/index.d.ts +1 -1
  99. package/dist/conversation-index/backend.d.ts +1 -1
  100. package/dist/conversation-index/chunker.d.ts +1 -1
  101. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  102. package/dist/conversation-index/indexer.d.ts +1 -1
  103. package/dist/conversation-index/search.d.ts +1 -1
  104. package/dist/day-summary.d.ts +1 -1
  105. package/dist/delinearize.d.ts +1 -1
  106. package/dist/direct-answer-wiring.d.ts +1 -1
  107. package/dist/direct-answer.d.ts +1 -1
  108. package/dist/embedding-fallback.d.ts +1 -1
  109. package/dist/enrichment/index.d.ts +1 -1
  110. package/dist/entity-retrieval.d.ts +1 -1
  111. package/dist/entity-retrieval.js +7 -3
  112. package/dist/entity-schema.d.ts +1 -1
  113. package/dist/explicit-capture.d.ts +6 -6
  114. package/dist/extraction-faithfulness.d.ts +1 -1
  115. package/dist/extraction-judge-telemetry.d.ts +1 -1
  116. package/dist/extraction-judge-training.d.ts +1 -1
  117. package/dist/extraction-judge.d.ts +1 -1
  118. package/dist/extraction.d.ts +1 -1
  119. package/dist/fallback-llm.d.ts +1 -1
  120. package/dist/{forget-PLR6J5DN.js → forget-BEXG5PQC.js} +32 -1
  121. package/dist/forget-BEXG5PQC.js.map +1 -0
  122. package/dist/identity-continuity.d.ts +1 -1
  123. package/dist/importance.d.ts +1 -1
  124. package/dist/index.d.ts +21 -10
  125. package/dist/index.js +63 -41
  126. package/dist/index.js.map +1 -1
  127. package/dist/intent.d.ts +1 -1
  128. package/dist/lcm/engine.d.ts +1 -1
  129. package/dist/lcm/index.d.ts +1 -1
  130. package/dist/lcm/tools.d.ts +1 -1
  131. package/dist/lifecycle.d.ts +1 -1
  132. package/dist/live-connectors-runner.d.ts +1 -1
  133. package/dist/local-llm.d.ts +1 -1
  134. package/dist/maintenance/memory-governance.d.ts +1 -1
  135. package/dist/maintenance/memory-governance.js +7 -3
  136. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -3
  137. package/dist/maintenance/rebuild-memory-projection.js +8 -4
  138. package/dist/mcp-memory-inspector-app.d.ts +5 -5
  139. package/dist/memory-action-policy.d.ts +1 -1
  140. package/dist/memory-cache.d.ts +1 -1
  141. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  142. package/dist/memory-projection-store.d.ts +1 -1
  143. package/dist/memory-provenance.d.ts +1 -1
  144. package/dist/memory-provenance.js +1 -1
  145. package/dist/memory-worth-outcomes.d.ts +1 -1
  146. package/dist/models-json.d.ts +1 -1
  147. package/dist/namespaces/migrate.d.ts +2 -2
  148. package/dist/namespaces/migrate.js +8 -4
  149. package/dist/namespaces/principal.d.ts +1 -1
  150. package/dist/namespaces/search.d.ts +1 -1
  151. package/dist/namespaces/storage.d.ts +20 -2
  152. package/dist/namespaces/storage.js +7 -3
  153. package/dist/native-knowledge.d.ts +1 -1
  154. package/dist/operator-toolkit.d.ts +19 -2
  155. package/dist/operator-toolkit.js +17 -11
  156. package/dist/orchestration/maintenance.d.ts +2 -2
  157. package/dist/orchestration/maintenance.js +9 -5
  158. package/dist/{orchestrator-Dv8JQu3-.d.ts → orchestrator-CJI4xdqV.d.ts} +3 -3
  159. package/dist/orchestrator.d.ts +4 -4
  160. package/dist/orchestrator.js +20 -18
  161. package/dist/patterns-cli.d.ts +1 -1
  162. package/dist/policy-runtime.d.ts +1 -1
  163. package/dist/provenance.d.ts +1 -1
  164. package/dist/qmd-recall-cache.d.ts +1 -1
  165. package/dist/qmd.d.ts +1 -1
  166. package/dist/recall-disclosure-escalation.d.ts +1 -1
  167. package/dist/recall-explain-renderer.d.ts +1 -1
  168. package/dist/recall-explain-renderer.js +4 -4
  169. package/dist/recall-planner-llm.d.ts +1 -1
  170. package/dist/recall-state.d.ts +1 -1
  171. package/dist/recall-tag-filter.d.ts +1 -1
  172. package/dist/recall-xray-cli.d.ts +1 -1
  173. package/dist/recall-xray-cli.js +5 -5
  174. package/dist/recall-xray-renderer.d.ts +1 -1
  175. package/dist/recall-xray-renderer.js +4 -4
  176. package/dist/recall-xray.d.ts +1 -1
  177. package/dist/recall-xray.js +3 -3
  178. package/dist/resolve-auth-token.d.ts +1 -1
  179. package/dist/resume-bundles.js +2 -2
  180. package/dist/retrieval-agents.d.ts +1 -1
  181. package/dist/retrieval-tiers.d.ts +1 -1
  182. package/dist/routing/engine.d.ts +1 -1
  183. package/dist/routing/store.d.ts +1 -1
  184. package/dist/schemas.d.ts +6 -6
  185. package/dist/search/embed-helper.d.ts +1 -1
  186. package/dist/search/factory.d.ts +1 -1
  187. package/dist/search/index.d.ts +1 -1
  188. package/dist/search/lancedb-backend.d.ts +1 -1
  189. package/dist/search/meilisearch-backend.d.ts +1 -1
  190. package/dist/search/noop-backend.d.ts +1 -1
  191. package/dist/search/orama-backend.d.ts +1 -1
  192. package/dist/search/port.d.ts +1 -1
  193. package/dist/search/remote-backend.d.ts +1 -1
  194. package/dist/{semantic-consolidation-CgREi9E0.d.ts → semantic-consolidation-BtUfv-AL.d.ts} +1 -1
  195. package/dist/semantic-consolidation.d.ts +2 -2
  196. package/dist/semantic-consolidation.js +8 -4
  197. package/dist/semantic-rule-promotion.js +7 -3
  198. package/dist/semantic-rule-verifier.d.ts +1 -1
  199. package/dist/semantic-rule-verifier.js +7 -3
  200. package/dist/session-observer-bands.d.ts +1 -1
  201. package/dist/session-observer-state.d.ts +1 -1
  202. package/dist/shared-context/manager.d.ts +1 -1
  203. package/dist/signal.d.ts +1 -1
  204. package/dist/storage.d.ts +306 -2
  205. package/dist/storage.js +6 -2
  206. package/dist/summarizer.d.ts +1 -1
  207. package/dist/summary-snapshot.d.ts +1 -1
  208. package/dist/temporal-supersession.d.ts +1 -1
  209. package/dist/temporal-supersession.js +1 -1
  210. package/dist/temporal-validity.d.ts +1 -1
  211. package/dist/threading.d.ts +1 -1
  212. package/dist/tier-migration.d.ts +1 -1
  213. package/dist/tier-routing.d.ts +1 -1
  214. package/dist/topics.d.ts +1 -1
  215. package/dist/transcript.d.ts +1 -1
  216. package/dist/{types-CeZYSC-E.d.ts → types-PuiPZ9iE.d.ts} +34 -1
  217. package/dist/types.d.ts +1 -1
  218. package/dist/types.js +1 -1
  219. package/dist/utility-runtime.d.ts +1 -1
  220. package/dist/utils/serialize-mutations.js +5 -280
  221. package/dist/utils/serialize-mutations.js.map +1 -1
  222. package/dist/verified-recall.js +7 -3
  223. package/package.json +2 -2
  224. package/src/config.ts +10 -0
  225. package/src/lifecycle/tombstones.test.ts +355 -0
  226. package/src/lifecycle/tombstones.ts +807 -0
  227. package/src/maintenance/forget.ts +41 -0
  228. package/src/maintenance/pattern-reinforcement.ts +47 -0
  229. package/src/memory-provenance.ts +12 -0
  230. package/src/namespaces/storage.ts +33 -0
  231. package/src/operator-toolkit.ts +77 -0
  232. package/src/orchestrator.ts +16 -0
  233. package/src/review/index.ts +48 -3
  234. package/src/storage.ts +329 -2
  235. package/src/temporal-supersession.ts +38 -0
  236. package/src/types.ts +34 -0
  237. package/dist/chunk-2K64VH66.js.map +0 -1
  238. package/dist/chunk-3LSSFCB4.js.map +0 -1
  239. package/dist/chunk-AC5LO7IU.js.map +0 -1
  240. package/dist/chunk-JYVD2XZ4.js.map +0 -1
  241. package/dist/chunk-RIC5U67B.js.map +0 -1
  242. package/dist/chunk-UDDSC6PO.js.map +0 -1
  243. package/dist/chunk-ZXUOAFUG.js.map +0 -1
  244. package/dist/forget-PLR6J5DN.js.map +0 -1
  245. /package/dist/{chunk-SJQ4HY3E.js.map → chunk-2NWHLAXX.js.map} +0 -0
  246. /package/dist/{chunk-XFG3PVZE.js.map → chunk-3E5WRQNQ.js.map} +0 -0
  247. /package/dist/{chunk-EZWQZYLK.js.map → chunk-6JDGADXK.js.map} +0 -0
  248. /package/dist/{chunk-IZ5E6ZTK.js.map → chunk-CCOXIDRM.js.map} +0 -0
  249. /package/dist/{chunk-COEZR6F5.js.map → chunk-CHM274U6.js.map} +0 -0
  250. /package/dist/{chunk-TIVZ4MCG.js.map → chunk-D75JXBV4.js.map} +0 -0
  251. /package/dist/{chunk-M4DQWUKX.js.map → chunk-DR2JTSLZ.js.map} +0 -0
  252. /package/dist/{chunk-XL5RSHZP.js.map → chunk-EOBJRBLC.js.map} +0 -0
  253. /package/dist/{chunk-YY2HGKWR.js.map → chunk-G5PKTQ5J.js.map} +0 -0
  254. /package/dist/{chunk-E6GOVHHJ.js.map → chunk-GYVVQYA3.js.map} +0 -0
  255. /package/dist/{chunk-2KJG6ZZK.js.map → chunk-HDLC75NX.js.map} +0 -0
  256. /package/dist/{chunk-QVTSLRKT.js.map → chunk-IJEZMWKA.js.map} +0 -0
  257. /package/dist/{chunk-N55RJT4N.js.map → chunk-JKW5XSWC.js.map} +0 -0
  258. /package/dist/{chunk-CQ4PGFMC.js.map → chunk-JX3YZVII.js.map} +0 -0
  259. /package/dist/{chunk-HZ5DRA4Q.js.map → chunk-NHBEO3F3.js.map} +0 -0
  260. /package/dist/{chunk-FSEQXHEZ.js.map → chunk-O54DY26V.js.map} +0 -0
  261. /package/dist/{chunk-JI6HWBYL.js.map → chunk-Q4O3ET6F.js.map} +0 -0
  262. /package/dist/{chunk-ANZLT74L.js.map → chunk-RJ2THZ4H.js.map} +0 -0
  263. /package/dist/{chunk-LPIWEKZ3.js.map → chunk-SDPDU2PM.js.map} +0 -0
  264. /package/dist/{chunk-A62RAIBN.js.map → chunk-SEWF2O74.js.map} +0 -0
  265. /package/dist/{chunk-2ULWWAQH.js.map → chunk-SMIVW7XC.js.map} +0 -0
  266. /package/dist/{chunk-RTN2BLZM.js.map → chunk-T5QAZIBO.js.map} +0 -0
  267. /package/dist/{chunk-SIDSEXUG.js.map → chunk-XY4WJTEX.js.map} +0 -0
  268. /package/dist/{chunk-LIWU4QI6.js.map → chunk-YMTGXDN6.js.map} +0 -0
  269. /package/dist/{chunk-MRX6S22R.js.map → chunk-YXIFA36P.js.map} +0 -0
  270. /package/dist/{chunk-AH2JUU6X.js.map → chunk-Z2YFLXHU.js.map} +0 -0
  271. /package/dist/{chunk-PJIHCOGV.js.map → chunk-ZDK2IW5F.js.map} +0 -0
@@ -22,6 +22,8 @@
22
22
  */
23
23
 
24
24
  import type { StorageManager } from "../storage.js";
25
+ import { buildRetiredFactTombstoneInputs } from "../lifecycle/tombstones.js";
26
+ import { supersessionKeysForFact } from "../temporal-supersession.js";
25
27
  import type { MemoryFile } from "../types.js";
26
28
 
27
29
  export interface ForgetMemoryRequest {
@@ -100,6 +102,45 @@ export async function forgetMemory(
100
102
  actor: "remnic-forget",
101
103
  reasonCode: "operator_forget",
102
104
  });
105
+ // Issue #1579 thread OchiF: emit a LIVE tombstone at forget time so a
106
+ // forgotten FACT cannot resurrect via re-extraction/import before an
107
+ // operator runs `remnic doctor --rebuild-tombstones`. Without this, the
108
+ // non-resurrection chokepoint in writeMemory has nothing to match — the
109
+ // forgotten fact's content hash is gone from the active index but no
110
+ // tombstone exists until a manual rebuild derives one — so the same fact
111
+ // written again through writeMemory becomes active. Mirrors supersedeMemory
112
+ // (contradiction) and applyTemporalSupersession via the shared
113
+ // buildRetiredFactTombstoneInputs helper. Best-effort (rule 34): a tombstone
114
+ // append failure must not fail the forget, which already succeeded on disk.
115
+ if (memory.frontmatter.category === "fact") {
116
+ for (const input of buildRetiredFactTombstoneInputs(
117
+ {
118
+ id,
119
+ content: memory.content,
120
+ ...(memory.frontmatter.contentHash
121
+ ? { contentHash: memory.frontmatter.contentHash }
122
+ : {}),
123
+ ...(memory.frontmatter.entityRef
124
+ ? { entityRef: memory.frontmatter.entityRef }
125
+ : {}),
126
+ ...(memory.frontmatter.structuredAttributes
127
+ ? { structuredAttributes: memory.frontmatter.structuredAttributes }
128
+ : {}),
129
+ },
130
+ {
131
+ reason: "retraction",
132
+ createdBy: "user_correction",
133
+ createdAt: forgottenAt,
134
+ supersessionKeysForFact,
135
+ },
136
+ )) {
137
+ try {
138
+ await storage.appendTombstone(input);
139
+ } catch {
140
+ // Best-effort — the forget already succeeded on disk.
141
+ }
142
+ }
143
+ }
103
144
  return {
104
145
  id,
105
146
  path: memory.path,
@@ -33,6 +33,8 @@
33
33
  */
34
34
 
35
35
  import { clusterByKey } from "../procedural/reinforcement-core.js";
36
+ import { buildRetiredFactTombstoneInputs, type TombstoneReason, type TombstoneCreatedBy } from "../lifecycle/tombstones.js";
37
+ import { supersessionKeysForFact } from "../temporal-supersession.js";
36
38
  import type {
37
39
  MemoryFile,
38
40
  MemoryFrontmatter,
@@ -50,6 +52,21 @@ export interface PatternReinforcementStorage {
50
52
  memory: MemoryFile,
51
53
  patch: Partial<MemoryFrontmatter>,
52
54
  ): Promise<boolean>;
55
+ /**
56
+ * Best-effort tombstone append for retired duplicates (issue #1579 thread
57
+ * Oblq9). Optional so unit-test stubs need not implement it; the production
58
+ * StorageManager satisfies it via its appendTombstone chokepoint.
59
+ */
60
+ appendTombstone?(input: {
61
+ reason: TombstoneReason;
62
+ createdBy: TombstoneCreatedBy;
63
+ sourceMemoryId: string;
64
+ contentHash?: string;
65
+ rawContent: string;
66
+ entityRef?: string;
67
+ supersessionKey?: string;
68
+ createdAt: string;
69
+ }): Promise<string | null>;
53
70
  }
54
71
 
55
72
  export interface PatternReinforcementOptions {
@@ -345,6 +362,36 @@ export async function runPatternReinforcement(
345
362
  await storage.writeMemoryFrontmatter(dup, patch);
346
363
  supersededIds.push(dup.frontmatter.id);
347
364
  result.duplicatesSuperseded += 1;
365
+ // Issue #1579 thread Oblq9 / Ocn0C / OcoPs: emit a tombstone so the
366
+ // retired duplicate cannot resurrect through re-extraction / import.
367
+ // Pattern reinforcement retires via writeMemoryFrontmatter which
368
+ // bypasses the writeMemory chokepoint; without this emit, the same
369
+ // content re-extracted before a manual rebuild would be stored active.
370
+ // Threads Ocn0C/OcoPs: emit one tombstone PER derived supersession key
371
+ // via buildRetiredFactTombstoneInputs (same helper as supersedeMemory +
372
+ // forgetMemory), not a single entityRef-only record — a paraphrased
373
+ // re-extraction with the same entity/attribute can only be caught by
374
+ // the keyed tier. Best-effort (the interface method is optional; a
375
+ // failure must not fail the reinforcement job).
376
+ if (storage.appendTombstone) {
377
+ for (const input of buildRetiredFactTombstoneInputs(
378
+ {
379
+ id: dup.frontmatter.id,
380
+ content: dup.content,
381
+ contentHash: dup.frontmatter.contentHash,
382
+ entityRef: dup.frontmatter.entityRef,
383
+ structuredAttributes: dup.frontmatter.structuredAttributes,
384
+ },
385
+ { reason: "supersession", createdBy: "supersession", createdAt: nowIso, supersessionKeysForFact },
386
+ )) {
387
+ try {
388
+ await storage.appendTombstone(input);
389
+ } catch {
390
+ // Best-effort: a tombstone append failure must not fail the
391
+ // reinforcement job (gotcha #13 / rule 34 spirit).
392
+ }
393
+ }
394
+ }
348
395
  }
349
396
 
350
397
  result.clusters.push({
@@ -328,6 +328,18 @@ function inferSafetyReasons(input: {
328
328
  if (frontmatter.verificationState === "disputed") {
329
329
  reasons.push("verification=disputed");
330
330
  }
331
+ // Issue #1579 — x-ray visibility (rule 34: never a silent drop). When a
332
+ // memory carries `blockedBy`, it was tombstone-blocked at the storage
333
+ // chokepoint. Surface the tombstone id (and the matched tier when known)
334
+ // in the safety reasons so every recall x-ray consumer (CLI / HTTP / MCP)
335
+ // can attribute the pending_review state to a tombstone rather than a
336
+ // generic low-confidence review.
337
+ if (typeof frontmatter.blockedBy === "string" && frontmatter.blockedBy.length > 0) {
338
+ reasons.push(`tombstone-blocked=${frontmatter.blockedBy}`);
339
+ if (frontmatter.tombstoneBlockTier) {
340
+ reasons.push(`tombstone-tier=${frontmatter.tombstoneBlockTier}`);
341
+ }
342
+ }
331
343
  if (stale) reasons.push("stale=true");
332
344
 
333
345
  const boundaryScopes = userContextScopes.filter(isUserBoundaryScope);
@@ -297,6 +297,9 @@ export class NamespaceStorageRouter {
297
297
  // #1522: install the post-write catalog touch at the chokepoint — every
298
298
  // successful write on this StorageManager records the namespace touch.
299
299
  this.bindCatalogWriteHook(sm, ns);
300
+ // #1579: apply the tombstone non-resurrection config so every namespace
301
+ // storage enforces the invariant at its own writeMemory chokepoint.
302
+ this.applyTombstonesConfig(sm, ns);
300
303
  this.cache.set(ns, sm);
301
304
  this.notifyResolved(ns, root);
302
305
  return sm;
@@ -387,6 +390,36 @@ export class NamespaceStorageRouter {
387
390
  sm.onCatalogWrite = () => this.touchCatalogWrite(namespace, sm.dir);
388
391
  }
389
392
 
393
+ /**
394
+ * Install the tombstone config on an externally-constructed StorageManager
395
+ * (issue #1579). Mirrors `bindCatalogWriteHook` — used for the legacy
396
+ * default-namespace storage that bypasses the router. Router-created
397
+ * storages are wired inline in `storageFor()` via `applyTombstonesConfig`.
398
+ */
399
+ bindTombstonesConfig(
400
+ sm: StorageManager,
401
+ namespace: string,
402
+ config: { enabled: boolean; semanticMatch: boolean; semanticThreshold: number },
403
+ ): void {
404
+ this.tombstonesGlobalConfig = { ...config };
405
+ sm.setTombstonesConfig({ ...config, namespace });
406
+ }
407
+
408
+ private tombstonesGlobalConfig: {
409
+ enabled: boolean;
410
+ semanticMatch: boolean;
411
+ semanticThreshold: number;
412
+ } = { enabled: false, semanticMatch: false, semanticThreshold: 0.9 };
413
+
414
+ /**
415
+ * Apply the tombstone config to a router-created StorageManager. Called
416
+ * inline in `storageFor()` so every namespace storage enforces the
417
+ * non-resurrection invariant, namespace-scoped (rule 42).
418
+ */
419
+ private applyTombstonesConfig(sm: StorageManager, namespace: string): void {
420
+ sm.setTombstonesConfig({ ...this.tombstonesGlobalConfig, namespace });
421
+ }
422
+
390
423
  /**
391
424
  * Post-write catalog touch (issue #1522 chokepoint). Called by every
392
425
  * StorageManager's post-write hook AFTER a successful write. Best-effort
@@ -1326,6 +1326,12 @@ export async function runOperatorDoctor(options: OperatorDoctorOptions): Promise
1326
1326
  details: namespaceMaintenanceHealth,
1327
1327
  });
1328
1328
 
1329
+ // Tombstone non-resurrection invariant (issue #1579). Reports active
1330
+ // tombstone count, last append, corrupted-line count, and rebuild
1331
+ // staleness so operators can verify the invariant is enforced and know
1332
+ // when a rebuild is warranted. Informational: never errors on its own.
1333
+ checks.push(await summarizeTombstoneStatus(options.orchestrator.storage));
1334
+
1329
1335
  const summary = checks.reduce(
1330
1336
  (acc, check) => {
1331
1337
  acc[check.status] += 1;
@@ -1871,6 +1877,77 @@ export async function summarizeObservationThroughput(
1871
1877
  }
1872
1878
  }
1873
1879
 
1880
+ /**
1881
+ * Tombstone non-resurrection invariant status for `remnic doctor`
1882
+ * (issue #1579).
1883
+ *
1884
+ * Reports:
1885
+ * - active tombstone count (excluding revoked)
1886
+ * - revoked count (re-allowed via the review queue)
1887
+ * - last append timestamp
1888
+ * - corrupted-line count (skipped, not crashed — rule 34/18)
1889
+ * - rebuild staleness (whether the in-memory index loaded cleanly)
1890
+ *
1891
+ * Always informational: a disabled or empty tombstone store is the expected
1892
+ * cold-install state and is never an error. A non-zero corrupted-line count
1893
+ * surfaces as `warn` so operators know a rebuild (`remnic doctor
1894
+ * --rebuild-tombstones`) will repair the log.
1895
+ */
1896
+ export async function summarizeTombstoneStatus(
1897
+ storage: StorageManager,
1898
+ ): Promise<OperatorDoctorCheck> {
1899
+ try {
1900
+ const stats = await storage.getTombstoneStats();
1901
+ if (stats === null) {
1902
+ return {
1903
+ key: "tombstones",
1904
+ status: "ok",
1905
+ summary: "Tombstone non-resurrection invariant is disabled (tombstonesEnabled=false).",
1906
+ details: { enabled: false },
1907
+ };
1908
+ }
1909
+ const corrupted = stats.corruptedLines > 0;
1910
+ const parts = [
1911
+ `${stats.count} active tombstone(s)`,
1912
+ ...(stats.revoked > 0 ? [`${stats.revoked} revoked`] : []),
1913
+ ];
1914
+ if (stats.lastAppendAt) {
1915
+ parts.push(`last append ${stats.lastAppendAt}`);
1916
+ }
1917
+ if (corrupted) {
1918
+ parts.push(`${stats.corruptedLines} corrupted line(s) skipped`);
1919
+ }
1920
+ return {
1921
+ key: "tombstones",
1922
+ status: corrupted ? "warn" : "ok",
1923
+ summary:
1924
+ `Tombstone invariant enabled. ${parts.join(", ")}. ` +
1925
+ (corrupted
1926
+ ? "Run `remnic doctor --rebuild-tombstones` to repair the log."
1927
+ : "Re-observation of retired facts is blocked at the storage chokepoint."),
1928
+ remediation: corrupted
1929
+ ? "Rebuild the tombstone log from retired memories on disk."
1930
+ : undefined,
1931
+ details: {
1932
+ enabled: true,
1933
+ count: stats.count,
1934
+ revoked: stats.revoked,
1935
+ lastAppendAt: stats.lastAppendAt,
1936
+ corruptedLines: stats.corruptedLines,
1937
+ loaded: stats.loaded,
1938
+ },
1939
+ };
1940
+ } catch (err) {
1941
+ return {
1942
+ key: "tombstones",
1943
+ status: "warn",
1944
+ summary: "Could not read tombstone store stats.",
1945
+ remediation: "Ensure the memory state directory is readable and rerun `remnic doctor`.",
1946
+ details: { error: String(err) },
1947
+ };
1948
+ }
1949
+ }
1950
+
1874
1951
  export async function summarizeConsolidationProvenance(
1875
1952
  storage: StorageManager,
1876
1953
  config: Pick<PluginConfig, "memoryDir"> & { versioningSidecarDir?: string },
@@ -2728,6 +2728,22 @@ export class Orchestrator {
2728
2728
  maxVersionsPerPage: config.versioningMaxPerPage,
2729
2729
  sidecarDir: config.versioningSidecarDir,
2730
2730
  });
2731
+ // Wire the tombstone non-resurrection invariant (issue #1579) on the
2732
+ // primary default-namespace storage. Router-created namespace storages
2733
+ // get the same config via NamespaceStorageRouter.bindTombstonesConfig
2734
+ // (installed below) so every write path funnels through the chokepoint.
2735
+ const { tombstonesEnabled, tombstonesSemanticMatch, tombstonesSemanticThreshold } = config;
2736
+ this.storage.setTombstonesConfig({
2737
+ enabled: tombstonesEnabled,
2738
+ semanticMatch: tombstonesSemanticMatch,
2739
+ semanticThreshold: tombstonesSemanticThreshold,
2740
+ namespace: config.defaultNamespace,
2741
+ });
2742
+ this.storageRouter.bindTombstonesConfig(this.storage, config.defaultNamespace, {
2743
+ enabled: tombstonesEnabled,
2744
+ semanticMatch: tombstonesSemanticMatch,
2745
+ semanticThreshold: tombstonesSemanticThreshold,
2746
+ });
2731
2747
  // Wire at-rest encryption (issue #690 PR 3/4).
2732
2748
  // If secureStoreEnabled, check whether the keyring already holds a key
2733
2749
  // for this memory dir (e.g. operator unlocked before daemon restart).
@@ -29,7 +29,7 @@ export interface ReviewItem {
29
29
  /** Created date */
30
30
  created: string;
31
31
  /** Reason it's in review */
32
- reviewReason: "low_confidence" | "suggestion" | "contradiction" | "duplicate";
32
+ reviewReason: "low_confidence" | "suggestion" | "contradiction" | "duplicate" | "tombstone_blocked";
33
33
  /** Additional context */
34
34
  context?: string;
35
35
  }
@@ -45,6 +45,8 @@ export interface ReviewResult {
45
45
  updatedPath?: string;
46
46
  /** Status message */
47
47
  message: string;
48
+ /** Tombstone id cleared by an approve action (for the caller to revoke). */
49
+ clearedTombstoneId?: string;
48
50
  }
49
51
 
50
52
  export interface ReviewListResult {
@@ -70,6 +72,15 @@ export interface ReviewOptions {
70
72
  export interface ReviewActionOptions {
71
73
  /** Match the threshold used when listing review items (default: 0.7) */
72
74
  confidenceThreshold?: number;
75
+ /**
76
+ * Revocation hook (issue #1579). When approving a memory whose frontmatter
77
+ * carries `blockedBy: <tombstoneId>`, the hook fires so the caller (CLI /
78
+ * orchestrator) can append a `kind: "revocation"` tombstone entry —
79
+ * re-allowing the content. Fire-and-forget: a revocation failure MUST NOT
80
+ * fail the approval (gotcha #13). The hook receives the tombstone id and
81
+ * the memory id.
82
+ */
83
+ onApproveBlockedMemory?: (tombstoneId: string, memoryId: string) => void | Promise<void>;
73
84
  }
74
85
 
75
86
  interface ReviewFileMatch {
@@ -211,7 +222,7 @@ export function listReviewItems(options: ReviewOptions): ReviewListResult {
211
222
  if (!fm?.id) return false;
212
223
 
213
224
  const confidence = parseConfidence(fm.confidence, 1);
214
- if (confidence >= confidenceThreshold) return false;
225
+ if (confidence >= confidenceThreshold && !(typeof fm.blockedBy === "string" && fm.blockedBy.length > 0)) return false;
215
226
  if (parseBoolean(fm.reviewDismissed)) return false;
216
227
 
217
228
  // Skip if already in items
@@ -226,7 +237,7 @@ export function listReviewItems(options: ReviewOptions): ReviewListResult {
226
237
  source: (fm.source as string) ?? "unknown",
227
238
  filePath,
228
239
  created: (fm.created as string) ?? new Date().toISOString(),
229
- reviewReason: "low_confidence",
240
+ reviewReason: (typeof fm.blockedBy === "string" && fm.blockedBy.length > 0) ? "tombstone_blocked" : "low_confidence",
230
241
  });
231
242
  return isLimitReached();
232
243
  });
@@ -276,10 +287,32 @@ function approveItem(
276
287
  const fm = parseFrontmatter(content);
277
288
  if (!fm) return { itemId, action: "approve", message: "Could not parse frontmatter" };
278
289
 
290
+ // Issue #1579 — when approving a tombstone-blocked memory, fire the
291
+ // revocation hook so the content is re-allowed (append-only log: a
292
+ // `kind: "revocation"` entry supersedes the tombstone at lookup). Clear
293
+ // blockedBy/tombstoneBlockTier on the promoted memory so it is fully
294
+ // active. Fire-and-forget: a hook failure never fails the approval.
295
+ const blockedBy = typeof fm.blockedBy === "string" ? fm.blockedBy : null;
296
+ if (blockedBy && options.onApproveBlockedMemory) {
297
+ try {
298
+ const maybe = options.onApproveBlockedMemory(blockedBy, itemId);
299
+ if (maybe && typeof (maybe as Promise<void>).then === "function") {
300
+ void (maybe as Promise<void>).catch(() => undefined);
301
+ }
302
+ } catch {
303
+ /* fire-and-forget — never fail the approval (gotcha #13) */
304
+ }
305
+ }
306
+
279
307
  const updatedContent = updateFrontmatterFields(content, {
280
308
  confidence: "0.9",
281
309
  confidenceTier: "high",
282
310
  reviewDismissed: null,
311
+ // Clear the tombstone-block markers so the promoted memory is active.
312
+ // Clear the tombstone-block markers AND restore active status so the
313
+ // promoted memory is fully active in recall + provenance (issue #1579
314
+ // review: leaving status: pending_review kept it invisible to recall).
315
+ ...(blockedBy ? { blockedBy: null, tombstoneBlockTier: null, status: "active" } : {}),
283
316
  });
284
317
 
285
318
  if (found.location === "category") {
@@ -289,6 +322,7 @@ function approveItem(
289
322
  action: "approve",
290
323
  updatedPath: found.filePath,
291
324
  message: "Approved low-confidence memory in place with confidence 0.9",
325
+ ...(blockedBy ? { clearedTombstoneId: blockedBy } : {}),
292
326
  };
293
327
  }
294
328
 
@@ -310,6 +344,7 @@ function approveItem(
310
344
  action: "approve",
311
345
  updatedPath: promotedPath,
312
346
  message: `Promoted to ${category} with confidence 0.9`,
347
+ ...(blockedBy ? { clearedTombstoneId: blockedBy } : {}),
313
348
  };
314
349
  }
315
350
 
@@ -418,6 +453,16 @@ function isLowConfidenceReviewCandidate(
418
453
  options: ReviewActionOptions,
419
454
  ): boolean {
420
455
  const threshold = options.confidenceThreshold ?? DEFAULT_CONFIDENCE_THRESHOLD;
456
+ // Issue #1579: tombstone-blocked facts are ALWAYS review candidates,
457
+ // regardless of confidence. The block sets status: pending_review +
458
+ // blockedBy; these must be visible in the review queue so an operator
459
+ // can approve (revoke the tombstone) or dismiss them. Without this, a
460
+ // high-confidence blocked fact is visible on disk but absent from the
461
+ // review UI — a silent drop, which violates the non-resurrection design
462
+ // (rule 34: visible, not silent).
463
+ if (typeof fm.blockedBy === "string" && fm.blockedBy.length > 0) {
464
+ return !parseBoolean(fm.reviewDismissed);
465
+ }
421
466
  return (
422
467
  parseConfidence(fm.confidence, 1) < threshold &&
423
468
  !parseBoolean(fm.reviewDismissed)