@remnic/core 9.3.708 → 9.3.710

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 (266) hide show
  1. package/dist/access-boundary.d.ts +7 -7
  2. package/dist/access-boundary.js +8 -8
  3. package/dist/access-cli.js +111 -24
  4. package/dist/access-cli.js.map +1 -1
  5. package/dist/access-http.d.ts +6 -6
  6. package/dist/access-http.js +12 -12
  7. package/dist/access-mcp.d.ts +14 -6
  8. package/dist/access-mcp.js +11 -11
  9. package/dist/access-operations-batch.js +9 -9
  10. package/dist/access-operations.d.ts +28 -8
  11. package/dist/access-operations.js +14 -10
  12. package/dist/access-schema.d.ts +6 -6
  13. package/dist/{access-service-j1c1gptF.d.ts → access-service-CoIA0NrG.d.ts} +186 -3
  14. package/dist/access-service.d.ts +6 -6
  15. package/dist/access-service.js +7 -7
  16. package/dist/access-surface-catalog.d.ts +6 -6
  17. package/dist/access-surface-catalog.js +7 -0
  18. package/dist/access-surface-catalog.js.map +1 -1
  19. package/dist/action-confidence.d.ts +1 -1
  20. package/dist/active-memory-bridge.d.ts +1 -1
  21. package/dist/active-recall.d.ts +1 -1
  22. package/dist/active-recall.js +1 -1
  23. package/dist/behavior-learner.d.ts +1 -1
  24. package/dist/behavior-signals.d.ts +1 -1
  25. package/dist/bootstrap.d.ts +4 -4
  26. package/dist/briefing.d.ts +1 -1
  27. package/dist/briefing.js +3 -3
  28. package/dist/buffer-surprise-report.d.ts +1 -1
  29. package/dist/buffer.d.ts +1 -1
  30. package/dist/calibration.d.ts +1 -1
  31. package/dist/capabilities.d.ts +1 -1
  32. package/dist/{catalog-CKxilpzS.d.ts → catalog-DQCZrBjw.d.ts} +1 -1
  33. package/dist/causal-behavior.d.ts +1 -1
  34. package/dist/causal-consolidation.d.ts +1 -1
  35. package/dist/causal-consolidation.js +4 -4
  36. package/dist/{chunk-PDJQVAGM.js → chunk-2T4TDXPC.js} +14 -14
  37. package/dist/{chunk-W2WQ4LE7.js → chunk-35YJ6KCV.js} +2 -2
  38. package/dist/{chunk-C63MK3WL.js → chunk-3TCRU4JA.js} +2 -2
  39. package/dist/{chunk-WRGPE6AW.js → chunk-3XHD3XGK.js} +44 -1
  40. package/dist/chunk-3XHD3XGK.js.map +1 -0
  41. package/dist/{chunk-GKI6LX5L.js → chunk-54TI5GLV.js} +2 -2
  42. package/dist/{chunk-IVCQW4C4.js → chunk-7IBEWQLG.js} +2 -2
  43. package/dist/{chunk-Y6YHAGQP.js → chunk-7NDYFAJS.js} +2 -2
  44. package/dist/{chunk-SPETAWFE.js → chunk-AZVHBFI3.js} +2 -2
  45. package/dist/{chunk-MLLTU5FX.js → chunk-BOGENF7P.js} +1 -1
  46. package/dist/chunk-BOGENF7P.js.map +1 -0
  47. package/dist/{chunk-3PTOZJOI.js → chunk-BQCFXAMN.js} +2 -2
  48. package/dist/{chunk-4E2QCH46.js → chunk-CNVIWMQI.js} +2 -2
  49. package/dist/{chunk-4DZATVK5.js → chunk-CXMXAC5R.js} +3 -3
  50. package/dist/{chunk-STYMKPFT.js → chunk-EVX52NCY.js} +1459 -21
  51. package/dist/chunk-EVX52NCY.js.map +1 -0
  52. package/dist/{chunk-W2S3Z5MT.js → chunk-GMRNKPWO.js} +2 -2
  53. package/dist/{chunk-HIV5E57C.js → chunk-HSDJCT3V.js} +2 -2
  54. package/dist/{chunk-NM6TSEGQ.js → chunk-JNOYYWCA.js} +2 -2
  55. package/dist/{chunk-IONFO7UK.js → chunk-JQ7XVM4V.js} +3 -3
  56. package/dist/{chunk-2VQYHHWB.js → chunk-JSDZMOT7.js} +11 -2
  57. package/dist/chunk-JSDZMOT7.js.map +1 -0
  58. package/dist/{chunk-WSWYIKXX.js → chunk-KOEKDZ6A.js} +62 -5
  59. package/dist/chunk-KOEKDZ6A.js.map +1 -0
  60. package/dist/{chunk-WQADZ3ZY.js → chunk-NK3SPJLM.js} +20 -19
  61. package/dist/chunk-NK3SPJLM.js.map +1 -0
  62. package/dist/{chunk-WDH3KUZU.js → chunk-NXL5CVE7.js} +2 -2
  63. package/dist/{chunk-BGAHTI4C.js → chunk-OK7FUX6R.js} +6 -6
  64. package/dist/{chunk-24FGNOQS.js → chunk-P6PRSI3W.js} +85 -4
  65. package/dist/chunk-P6PRSI3W.js.map +1 -0
  66. package/dist/{chunk-66TSLESZ.js → chunk-PKE7EJMX.js} +2 -2
  67. package/dist/chunk-PKE7EJMX.js.map +1 -0
  68. package/dist/{chunk-MOXFPLD6.js → chunk-QXNFQKWU.js} +2 -2
  69. package/dist/{chunk-MRX6ZXHZ.js → chunk-S6FQLQGH.js} +2 -2
  70. package/dist/{chunk-6HPJMR5I.js → chunk-SKQCFAYU.js} +2 -2
  71. package/dist/{chunk-EJDAXR7O.js → chunk-VWB3HDY6.js} +56 -8
  72. package/dist/chunk-VWB3HDY6.js.map +1 -0
  73. package/dist/{chunk-N75N5SNX.js → chunk-XD33EX2F.js} +3 -3
  74. package/dist/{chunk-ESMY4RJ4.js → chunk-XKU4YE6Z.js} +2 -2
  75. package/dist/{chunk-XKMDDM7P.js → chunk-Y6PIFKXX.js} +2 -2
  76. package/dist/{chunk-RH2OSRQY.js → chunk-Z56IHRVV.js} +2 -2
  77. package/dist/{cli-CbT-pyM4.d.ts → cli-qex-L3GT.d.ts} +3 -3
  78. package/dist/cli.d.ts +6 -6
  79. package/dist/cli.js +26 -26
  80. package/dist/compounding/engine.d.ts +1 -1
  81. package/dist/compounding/engine.js +3 -3
  82. package/dist/compounding/preference-consolidator.d.ts +1 -1
  83. package/dist/compression-optimizer.d.ts +1 -1
  84. package/dist/config.d.ts +1 -1
  85. package/dist/config.js +1 -1
  86. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  87. package/dist/connectors/codex-materialize-runner.js +3 -3
  88. package/dist/connectors/codex-materialize.d.ts +1 -1
  89. package/dist/connectors/index.d.ts +1 -1
  90. package/dist/connectors/index.js +3 -3
  91. package/dist/consolidation-provenance-check.d.ts +1 -1
  92. package/dist/consolidation-undo.d.ts +1 -1
  93. package/dist/contradiction/index.d.ts +1 -1
  94. package/dist/conversation-index/backend.d.ts +1 -1
  95. package/dist/conversation-index/chunker.d.ts +1 -1
  96. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  97. package/dist/conversation-index/indexer.d.ts +1 -1
  98. package/dist/conversation-index/search.d.ts +1 -1
  99. package/dist/day-summary.d.ts +1 -1
  100. package/dist/delinearize.d.ts +1 -1
  101. package/dist/direct-answer-wiring.d.ts +1 -1
  102. package/dist/direct-answer.d.ts +1 -1
  103. package/dist/embedding-fallback.d.ts +1 -1
  104. package/dist/enrichment/index.d.ts +1 -1
  105. package/dist/entity-retrieval.d.ts +1 -1
  106. package/dist/entity-retrieval.js +3 -3
  107. package/dist/entity-schema.d.ts +1 -1
  108. package/dist/explicit-capture.d.ts +4 -4
  109. package/dist/extraction-faithfulness.d.ts +1 -1
  110. package/dist/extraction-judge-telemetry.d.ts +1 -1
  111. package/dist/extraction-judge-training.d.ts +1 -1
  112. package/dist/extraction-judge.d.ts +1 -1
  113. package/dist/extraction.d.ts +1 -1
  114. package/dist/fallback-llm.d.ts +1 -1
  115. package/dist/identity-continuity.d.ts +1 -1
  116. package/dist/importance.d.ts +1 -1
  117. package/dist/index.d.ts +9 -9
  118. package/dist/index.js +32 -32
  119. package/dist/intent.d.ts +1 -1
  120. package/dist/lcm/engine.d.ts +1 -1
  121. package/dist/lcm/index.d.ts +1 -1
  122. package/dist/lcm/tools.d.ts +1 -1
  123. package/dist/lifecycle.d.ts +1 -1
  124. package/dist/live-connectors-runner.d.ts +1 -1
  125. package/dist/local-llm.d.ts +1 -1
  126. package/dist/maintenance/memory-governance.d.ts +1 -1
  127. package/dist/maintenance/memory-governance.js +3 -3
  128. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  129. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  130. package/dist/mcp-memory-inspector-app.d.ts +6 -6
  131. package/dist/memory-action-policy.d.ts +1 -1
  132. package/dist/memory-cache.d.ts +1 -1
  133. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  134. package/dist/memory-projection-store.d.ts +1 -1
  135. package/dist/memory-provenance.d.ts +1 -1
  136. package/dist/memory-worth-outcomes.d.ts +1 -1
  137. package/dist/models-json.d.ts +1 -1
  138. package/dist/namespaces/migrate.d.ts +2 -2
  139. package/dist/namespaces/migrate.js +4 -4
  140. package/dist/namespaces/principal.d.ts +1 -1
  141. package/dist/namespaces/search.d.ts +1 -1
  142. package/dist/namespaces/storage.d.ts +2 -2
  143. package/dist/namespaces/storage.js +3 -3
  144. package/dist/native-knowledge.d.ts +1 -1
  145. package/dist/operator-toolkit.d.ts +1 -1
  146. package/dist/operator-toolkit.js +9 -9
  147. package/dist/orchestration/maintenance.d.ts +2 -2
  148. package/dist/orchestration/maintenance.js +5 -5
  149. package/dist/{orchestrator-D4ovYV3x.d.ts → orchestrator-DsVKLEBk.d.ts} +3 -3
  150. package/dist/orchestrator.d.ts +4 -4
  151. package/dist/orchestrator.js +15 -15
  152. package/dist/patterns-cli.d.ts +1 -1
  153. package/dist/policy-runtime.d.ts +1 -1
  154. package/dist/provenance.d.ts +1 -1
  155. package/dist/qmd-recall-cache.d.ts +1 -1
  156. package/dist/qmd.d.ts +1 -1
  157. package/dist/recall-disclosure-escalation.d.ts +1 -1
  158. package/dist/recall-explain-renderer.d.ts +1 -1
  159. package/dist/recall-explain-renderer.js +3 -3
  160. package/dist/recall-planner-llm.d.ts +1 -1
  161. package/dist/recall-state.d.ts +1 -1
  162. package/dist/recall-tag-filter.d.ts +1 -1
  163. package/dist/recall-xray-cli.d.ts +1 -1
  164. package/dist/recall-xray-cli.js +4 -4
  165. package/dist/recall-xray-renderer.d.ts +1 -1
  166. package/dist/recall-xray-renderer.js +3 -3
  167. package/dist/recall-xray.d.ts +1 -1
  168. package/dist/recall-xray.js +2 -2
  169. package/dist/resolve-auth-token.d.ts +1 -1
  170. package/dist/resume-bundles.js +2 -2
  171. package/dist/retrieval-agents.d.ts +1 -1
  172. package/dist/retrieval-tiers.d.ts +1 -1
  173. package/dist/routing/engine.d.ts +1 -1
  174. package/dist/routing/store.d.ts +1 -1
  175. package/dist/schemas.d.ts +2 -2
  176. package/dist/search/embed-helper.d.ts +1 -1
  177. package/dist/search/factory.d.ts +1 -1
  178. package/dist/search/index.d.ts +1 -1
  179. package/dist/search/lancedb-backend.d.ts +1 -1
  180. package/dist/search/meilisearch-backend.d.ts +1 -1
  181. package/dist/search/noop-backend.d.ts +1 -1
  182. package/dist/search/orama-backend.d.ts +1 -1
  183. package/dist/search/port.d.ts +1 -1
  184. package/dist/search/remote-backend.d.ts +1 -1
  185. package/dist/{semantic-consolidation-DyMUCsfN.d.ts → semantic-consolidation-_hVxkTuF.d.ts} +1 -1
  186. package/dist/semantic-consolidation.d.ts +2 -2
  187. package/dist/semantic-consolidation.js +4 -4
  188. package/dist/semantic-rule-promotion.js +3 -3
  189. package/dist/semantic-rule-verifier.d.ts +1 -1
  190. package/dist/semantic-rule-verifier.js +3 -3
  191. package/dist/session-observer-bands.d.ts +1 -1
  192. package/dist/session-observer-state.d.ts +1 -1
  193. package/dist/shared-context/manager.d.ts +1 -1
  194. package/dist/signal.d.ts +1 -1
  195. package/dist/storage.d.ts +8 -1
  196. package/dist/storage.js +2 -2
  197. package/dist/summarizer.d.ts +1 -1
  198. package/dist/summary-snapshot.d.ts +1 -1
  199. package/dist/temporal-supersession.d.ts +1 -1
  200. package/dist/temporal-validity.d.ts +1 -1
  201. package/dist/threading.d.ts +1 -1
  202. package/dist/tier-migration.d.ts +1 -1
  203. package/dist/tier-routing.d.ts +1 -1
  204. package/dist/topics.d.ts +1 -1
  205. package/dist/transcript.d.ts +1 -1
  206. package/dist/transfer/types.d.ts +12 -12
  207. package/dist/{types-Couvz-L3.d.ts → types-DUK4vVnN.d.ts} +27 -0
  208. package/dist/types.d.ts +1 -1
  209. package/dist/types.js +1 -1
  210. package/dist/utility-runtime.d.ts +1 -1
  211. package/dist/verified-recall.js +3 -3
  212. package/package.json +2 -2
  213. package/src/access-boundary.ts +2 -0
  214. package/src/access-cli.ts +107 -2
  215. package/src/access-http.ts +70 -1
  216. package/src/access-mcp.ts +65 -0
  217. package/src/access-operations.ts +122 -0
  218. package/src/access-service.ts +92 -0
  219. package/src/access-surface-catalog.test.ts +6 -1
  220. package/src/access-surface-catalog.ts +7 -0
  221. package/src/cli.ts +1 -0
  222. package/src/config.test.ts +10 -0
  223. package/src/config.ts +43 -0
  224. package/src/correction/correction-access-wiring.ts +887 -0
  225. package/src/correction/correction-contract.ts +416 -0
  226. package/src/correction/correction-executor.test.ts +742 -0
  227. package/src/correction/correction-executor.ts +473 -0
  228. package/src/correction/correction-planner.test.ts +446 -0
  229. package/src/correction/correction-planner.ts +546 -0
  230. package/src/correction/correction-service.ts +180 -0
  231. package/src/correction/correction-surfaces.test.ts +245 -0
  232. package/src/correction/index.ts +43 -0
  233. package/src/storage.ts +10 -0
  234. package/src/types.ts +27 -0
  235. package/dist/chunk-24FGNOQS.js.map +0 -1
  236. package/dist/chunk-2VQYHHWB.js.map +0 -1
  237. package/dist/chunk-66TSLESZ.js.map +0 -1
  238. package/dist/chunk-EJDAXR7O.js.map +0 -1
  239. package/dist/chunk-MLLTU5FX.js.map +0 -1
  240. package/dist/chunk-STYMKPFT.js.map +0 -1
  241. package/dist/chunk-WQADZ3ZY.js.map +0 -1
  242. package/dist/chunk-WRGPE6AW.js.map +0 -1
  243. package/dist/chunk-WSWYIKXX.js.map +0 -1
  244. /package/dist/{chunk-PDJQVAGM.js.map → chunk-2T4TDXPC.js.map} +0 -0
  245. /package/dist/{chunk-W2WQ4LE7.js.map → chunk-35YJ6KCV.js.map} +0 -0
  246. /package/dist/{chunk-C63MK3WL.js.map → chunk-3TCRU4JA.js.map} +0 -0
  247. /package/dist/{chunk-GKI6LX5L.js.map → chunk-54TI5GLV.js.map} +0 -0
  248. /package/dist/{chunk-IVCQW4C4.js.map → chunk-7IBEWQLG.js.map} +0 -0
  249. /package/dist/{chunk-Y6YHAGQP.js.map → chunk-7NDYFAJS.js.map} +0 -0
  250. /package/dist/{chunk-SPETAWFE.js.map → chunk-AZVHBFI3.js.map} +0 -0
  251. /package/dist/{chunk-3PTOZJOI.js.map → chunk-BQCFXAMN.js.map} +0 -0
  252. /package/dist/{chunk-4E2QCH46.js.map → chunk-CNVIWMQI.js.map} +0 -0
  253. /package/dist/{chunk-4DZATVK5.js.map → chunk-CXMXAC5R.js.map} +0 -0
  254. /package/dist/{chunk-W2S3Z5MT.js.map → chunk-GMRNKPWO.js.map} +0 -0
  255. /package/dist/{chunk-HIV5E57C.js.map → chunk-HSDJCT3V.js.map} +0 -0
  256. /package/dist/{chunk-NM6TSEGQ.js.map → chunk-JNOYYWCA.js.map} +0 -0
  257. /package/dist/{chunk-IONFO7UK.js.map → chunk-JQ7XVM4V.js.map} +0 -0
  258. /package/dist/{chunk-WDH3KUZU.js.map → chunk-NXL5CVE7.js.map} +0 -0
  259. /package/dist/{chunk-BGAHTI4C.js.map → chunk-OK7FUX6R.js.map} +0 -0
  260. /package/dist/{chunk-MOXFPLD6.js.map → chunk-QXNFQKWU.js.map} +0 -0
  261. /package/dist/{chunk-MRX6ZXHZ.js.map → chunk-S6FQLQGH.js.map} +0 -0
  262. /package/dist/{chunk-6HPJMR5I.js.map → chunk-SKQCFAYU.js.map} +0 -0
  263. /package/dist/{chunk-N75N5SNX.js.map → chunk-XD33EX2F.js.map} +0 -0
  264. /package/dist/{chunk-ESMY4RJ4.js.map → chunk-XKU4YE6Z.js.map} +0 -0
  265. /package/dist/{chunk-XKMDDM7P.js.map → chunk-Y6PIFKXX.js.map} +0 -0
  266. /package/dist/{chunk-RH2OSRQY.js.map → chunk-Z56IHRVV.js.map} +0 -0
@@ -0,0 +1,473 @@
1
+ /**
2
+ * correction/correction-executor.ts — the only writer for corrections
3
+ * (issue #1580 PR 2).
4
+ *
5
+ * Applies a {@link CorrectionPlan} in NON-DESTRUCTIVE ORDER
6
+ * (rule 25 / checklist §14 — new state confirmed before old state destroyed):
7
+ *
8
+ * 1. Write replacement / edited memories first (through the injected
9
+ * persist pipeline so catalog/reindex/dedup fire — rule 43; edits go
10
+ * through page-versioning so every change is revertable).
11
+ * 2. Then supersede / retract losers: status flip + `validUntil` stamp
12
+ * (#1578, when gated on) + tombstone append (#1579).
13
+ * 3. Then propagation: QMD reindex for touched files, graph edge updates,
14
+ * belief-ledger claim status (optional-package dynamic import, rule 57),
15
+ * profile.md line removal when an affected memory was profile-sourced.
16
+ * 4. Append an audit record to `corrections/` capturing plan + outcome —
17
+ * corrections are themselves memories, searchable and namespaced.
18
+ * 5. Partial failure → tagged partial result (rule 34; checklist §22):
19
+ * never a half-applied plan reported as success, and never old state
20
+ * destroyed for an action whose replacement write failed.
21
+ *
22
+ * Concurrency: a plan may be applied exactly once. A second apply of the same
23
+ * plan is rejected (plan consumed). Backed by the planner's atomic plan-store
24
+ * (`markConsumed`) under `serializeMutations` keyed by plan id (rule 40).
25
+ */
26
+
27
+ import { serializeMutations } from "../utils/serialize-mutations.js";
28
+ import type { CorrectionPlanner } from "./correction-planner.js";
29
+ import {
30
+ CorrectionContractError,
31
+ validateCorrectionAction,
32
+ validateRedactionPattern,
33
+ type CorrectionAction,
34
+ type CorrectionActionResult,
35
+ type CorrectionOutcome,
36
+ type CorrectionPlan,
37
+ } from "./correction-contract.js";
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Injected collaborators
41
+ // ---------------------------------------------------------------------------
42
+
43
+ /** A memory the executor is operating on (subset of MemoryFile). */
44
+ export interface ExecutorMemory {
45
+ memoryId: string;
46
+ content: string;
47
+ category: string;
48
+ /** True when the memory has frontmatter provenance (#1575 sourceQuote). */
49
+ sourceQuote?: string;
50
+ /** Structured-attribute supersession key, when one exists. */
51
+ supersessionKey?: string;
52
+ entityRef?: string;
53
+ /** Raw content used for the tombstone hash (rule 23). */
54
+ rawContent: string;
55
+ }
56
+
57
+ export interface ExecutorDeps {
58
+ /** Lookup a memory by id within the plan's namespace. null if not found. */
59
+ getMemory(namespace: string, memoryId: string): Promise<ExecutorMemory | null>;
60
+ /**
61
+ * Persist a NEW memory through the orchestrator's normal write pipeline
62
+ * (catalog/reindex/dedup fire — rule 43). Returns the new memory id.
63
+ * Used by `supersede` (the replacement).
64
+ */
65
+ writeReplacement(
66
+ namespace: string,
67
+ draft: {
68
+ content: string;
69
+ category?: string;
70
+ confidence?: number;
71
+ tags?: string[];
72
+ entityRef?: string;
73
+ validAt?: string;
74
+ observedAt?: string;
75
+ structuredAttributes?: Record<string, string>;
76
+ supersedes?: string;
77
+ },
78
+ ): Promise<string>;
79
+ /**
80
+ * Apply a versioned edit to an existing memory (page-versioning — every
81
+ * change revertable). Returns the memory id. The patch is the NEW full
82
+ * content; the implementation snapshots the prior version.
83
+ */
84
+ applyEdit(
85
+ namespace: string,
86
+ memoryId: string,
87
+ patch: string,
88
+ ): Promise<string>;
89
+ /**
90
+ * Flip a memory's status to superseded/retracted and stamp `validUntil`
91
+ * when bi-temporal is gated on (#1578). Idempotent.
92
+ */
93
+ retireMemory(
94
+ namespace: string,
95
+ memoryId: string,
96
+ opts: {
97
+ status: "superseded" | "retracted";
98
+ supersededBy?: string;
99
+ validUntil?: string;
100
+ },
101
+ ): Promise<void>;
102
+ /**
103
+ * Move a memory to a different namespace (rescope). The destination
104
+ * namespace is re-authorized by the service before the executor runs; the
105
+ * implementation performs the move atomically (write-then-unlink).
106
+ */
107
+ rescopeMemory(namespace: string, memoryId: string, toNamespace: string): Promise<string>;
108
+ /**
109
+ * Append a tombstone (#1579) for a retired memory. Returns the tombstone
110
+ * id, or null if tombstones are disabled (off = pre-feature behavior).
111
+ */
112
+ appendTombstone(
113
+ namespace: string,
114
+ input: {
115
+ reason: "correction" | "supersession" | "retraction";
116
+ sourceMemoryId: string;
117
+ rawContent: string;
118
+ entityRef?: string;
119
+ supersessionKey?: string;
120
+ },
121
+ ): Promise<string | null>;
122
+ /**
123
+ * Persist a redaction rule so extraction consults it the same way tombstones
124
+ * are consulted (route through the same chokepoint check). Idempotent.
125
+ */
126
+ registerRedactionRule(namespace: string, pattern: string): Promise<void>;
127
+ /**
128
+ * Append an audit record under `corrections/` (existing storage category)
129
+ * capturing plan + outcome. Returns the audit memory id.
130
+ */
131
+ appendAuditRecord(
132
+ namespace: string,
133
+ record: {
134
+ planId: string;
135
+ classification: CorrectionPlan["classification"];
136
+ outcome: CorrectionOutcome;
137
+ requestText: string;
138
+ },
139
+ ): Promise<string>;
140
+ /**
141
+ * Post-write propagation: QMD reindex for touched files (checklist §31),
142
+ * graph edge updates, belief-ledger claim status. Best-effort — a failure
143
+ * here is recorded as a warning, never as a failed action (propagation is
144
+ * not part of the §14 non-destructive-order guarantee; it runs after).
145
+ */
146
+ propagate(namespace: string, touchedMemoryIds: readonly string[]): Promise<void>;
147
+ /** Whether the bi-temporal gate (#1578) is on. When off, validUntil is omitted. */
148
+ readonly biTemporalEnabled: boolean;
149
+ /** Injected clock for deterministic tests. */
150
+ now(): Date;
151
+ }
152
+
153
+ // ---------------------------------------------------------------------------
154
+ // Executor
155
+ // ---------------------------------------------------------------------------
156
+
157
+ export class CorrectionExecutor {
158
+ constructor(
159
+ private readonly deps: ExecutorDeps,
160
+ private readonly planner: CorrectionPlanner,
161
+ ) {}
162
+
163
+ /**
164
+ * Apply a persisted plan by id. Idempotent-once: a second apply of the same
165
+ * plan is rejected. Returns the {@link CorrectionOutcome}.
166
+ */
167
+ async apply(
168
+ namespace: string,
169
+ planId: string,
170
+ opts: {
171
+ confirm: boolean;
172
+ /**
173
+ * Authorize a rescope destination namespace. Bound per-request by the
174
+ * service from the namespace policy + principal so a plan can never
175
+ * write into a namespace the caller lacks write scope for (review
176
+ * thread: authorize-rescope-destination). Defaults to allow when the
177
+ * source namespace already resolves to a writable scope (single-tenant).
178
+ */
179
+ canWriteDestination?: (namespace: string) => Promise<boolean>;
180
+ },
181
+ ): Promise<CorrectionOutcome> {
182
+ if (!opts.confirm) {
183
+ throw new CorrectionContractError(
184
+ "Correction apply requires explicit confirmation (correction.applyRequiresConfirm).",
185
+ );
186
+ }
187
+ // serializeMutations on the plan id so two concurrent applies of the SAME
188
+ // plan serialize — the second observes the consumed status and rejects.
189
+ return serializeMutations(`correction-apply:${namespace}:${planId}`, () =>
190
+ this.applyInternal(namespace, planId, opts.canWriteDestination),
191
+ );
192
+ }
193
+
194
+ private async applyInternal(namespace: string, planId: string, canWriteDestination?: (namespace: string) => Promise<boolean>): Promise<CorrectionOutcome> {
195
+ const plan = await this.planner.loadPlan(namespace, planId);
196
+ if (!plan) {
197
+ throw new CorrectionContractError(`Correction plan not found: ${planId}`);
198
+ }
199
+ if (plan.namespace !== namespace) {
200
+ // Cross-namespace foreign-id guard (rule 42 / checklist §16).
201
+ throw new CorrectionContractError(
202
+ `Correction plan ${planId} belongs to namespace '${plan.namespace}', not '${namespace}'.`,
203
+ );
204
+ }
205
+ if (plan.status === "applied" || plan.status === "partial" || plan.status === "applying") {
206
+ throw new CorrectionContractError(
207
+ `Correction plan ${planId} has already been applied or is in progress (status=${plan.status}).`,
208
+ );
209
+ }
210
+ if (plan.status === "discarded") {
211
+ throw new CorrectionContractError(`Correction plan ${planId} has been discarded.`);
212
+ }
213
+ // TTL check — expired plans are rejected with a clear error.
214
+ if (this.deps.now().getTime() > new Date(plan.expiresAt).getTime()) {
215
+ await this.planner.markConsumed(namespace, planId, "discarded");
216
+ throw new CorrectionContractError(
217
+ `Correction plan ${planId} expired at ${plan.expiresAt} and has been discarded.`,
218
+ );
219
+ }
220
+
221
+ // Re-validate every action shape before applying (defense in depth).
222
+ for (const action of plan.actions) {
223
+ validateCorrectionAction(action);
224
+ if (action.kind === "redaction_rule") {
225
+ validateRedactionPattern(action.pattern);
226
+ }
227
+ }
228
+
229
+ // Optimistically mark the plan `applying` BEFORE any mutation (review
230
+ // thread OgIqt). If the process dies mid-apply — or the final
231
+ // markConsumed("applied"|"partial") fails — the plan stays `applying`
232
+ // and is NOT silently retryable. A partially-applied plan must never be
233
+ // re-applied wholesale: re-running succeeded actions would duplicate
234
+ // replacements, tombstones, and audits. The operator inspects the
235
+ // outcome and files a NEW plan for any failed actions. This mark runs
236
+ // inside the serializeMutations lock so concurrent applies serialize.
237
+ try {
238
+ await this.planner.markConsumed(namespace, planId, "applying");
239
+ } catch {
240
+ // If we cannot even mark the plan in-progress, the filesystem is
241
+ // likely unwritable and mutations would fail too — fail closed now.
242
+ throw new CorrectionContractError(
243
+ `Correction plan ${planId}: cannot mark in-progress (filesystem unwritable?) — aborting before any mutation.`,
244
+ );
245
+ }
246
+
247
+ const results: CorrectionActionResult[] = [];
248
+ const appliedTouched: string[] = [];
249
+
250
+ // ── Phase 1: replacement / edit writes (new state first) ───────────────
251
+ // For each supersede with a replacement, write the replacement FIRST. If
252
+ // the write fails, the loser is NOT superseded (§14: never destroy old
253
+ // state for an action whose replacement write failed).
254
+ for (const action of plan.actions) {
255
+ if (action.kind === "supersede" && action.replacement) {
256
+ // Preflight the loser BEFORE writing the replacement (review thread
257
+ // Of0pz): if the loser was deleted between plan and apply, writing a
258
+ // replacement creates an orphan fact that supersedes nothing. Phase 2
259
+ // (retireAndTombstone) re-checks via getMemory, so verifying here
260
+ // keeps the two phases in agreement and avoids the orphan write.
261
+ const loser = await this.deps.getMemory(namespace, action.loserId);
262
+ if (!loser) {
263
+ results.push({
264
+ action,
265
+ status: "failed",
266
+ error: `supersede loser not found: ${action.loserId}`,
267
+ });
268
+ continue;
269
+ }
270
+ try {
271
+ const newId = await this.deps.writeReplacement(namespace, {
272
+ content: action.replacement.content,
273
+ ...(action.replacement.category ? { category: action.replacement.category } : {}),
274
+ ...(action.replacement.confidence !== undefined ? { confidence: action.replacement.confidence } : {}),
275
+ ...(action.replacement.tags ? { tags: action.replacement.tags } : {}),
276
+ ...(action.replacement.entityRef ? { entityRef: action.replacement.entityRef } : {}),
277
+ ...(action.replacement.validAt ? { validAt: action.replacement.validAt } : {}),
278
+ ...(action.replacement.observedAt ? { observedAt: action.replacement.observedAt } : {}),
279
+ ...(action.replacement.structuredAttributes
280
+ ? { structuredAttributes: action.replacement.structuredAttributes }
281
+ : {}),
282
+ supersedes: action.loserId,
283
+ });
284
+ results.push({ action, status: "applied", memoryId: newId });
285
+ appliedTouched.push(newId);
286
+ } catch (err) {
287
+ results.push({
288
+ action,
289
+ status: "failed",
290
+ error: errMsg(err),
291
+ });
292
+ }
293
+ } else if (action.kind === "edit") {
294
+ try {
295
+ const editedId = await this.deps.applyEdit(namespace, action.memoryId, action.patch);
296
+ results.push({ action, status: "applied", memoryId: editedId });
297
+ appliedTouched.push(editedId);
298
+ } catch (err) {
299
+ results.push({ action, status: "failed", error: errMsg(err) });
300
+ }
301
+ } else if (action.kind === "redaction_rule") {
302
+ try {
303
+ await this.deps.registerRedactionRule(namespace, action.pattern);
304
+ results.push({ action, status: "applied" });
305
+ } catch (err) {
306
+ results.push({ action, status: "failed", error: errMsg(err) });
307
+ }
308
+ }
309
+ }
310
+
311
+ // ── Phase 2: retire losers + tombstones ───────────────────────────────
312
+ // Only run for supersede/retract actions whose replacement write (if any)
313
+ // succeeded. A supersede WITHOUT a replacement is a pure retract.
314
+ for (const action of plan.actions) {
315
+ if (action.kind === "supersede") {
316
+ const replacementResult = results.find(
317
+ (r) => r.action === action && r.status === "applied",
318
+ );
319
+ // If the replacement write failed, skip retirement (§14).
320
+ if (action.replacement && !replacementResult) {
321
+ continue;
322
+ }
323
+ await this.retireAndTombstone(namespace, action, "supersession", results, appliedTouched, {
324
+ supersededBy: replacementResult?.memoryId,
325
+ });
326
+ } else if (action.kind === "retract") {
327
+ await this.retireAndTombstone(namespace, action, "retraction", results, appliedTouched);
328
+ } else if (action.kind === "rescope") {
329
+ try {
330
+ // Authorize the destination namespace BEFORE the move — the plan's
331
+ // toNamespace comes from the LLM/persisted plan and must not bypass
332
+ // the write ACL (review thread: authorize-rescope-destination).
333
+ // canWriteDestination defaults to allow when absent (single-tenant,
334
+ // where the source namespace already resolved to a writable scope).
335
+ const allowed = canWriteDestination ? await canWriteDestination(action.toNamespace) : true;
336
+ if (!allowed) {
337
+ results.push({
338
+ action,
339
+ status: "failed",
340
+ error: `rescope destination namespace not writable: ${action.toNamespace}`,
341
+ });
342
+ continue;
343
+ }
344
+ const destId = await this.deps.rescopeMemory(namespace, action.memoryId, action.toNamespace);
345
+ results.push({ action, status: "applied", memoryId: action.memoryId });
346
+ appliedTouched.push(action.memoryId);
347
+ // Propagate the destination memory in its namespace too (review
348
+ // thread: propagate-rescoped-destination) — best-effort.
349
+ try {
350
+ await this.deps.propagate(action.toNamespace, [destId]);
351
+ } catch {
352
+ // non-fatal — the source propagation still fires.
353
+ }
354
+ } catch (err) {
355
+ results.push({ action, status: "failed", error: errMsg(err) });
356
+ }
357
+ }
358
+ }
359
+
360
+ // ── Phase 3: propagation (post-write reindex + graph) ─────────────────
361
+ // Best-effort: a propagation failure is recorded as a warning on the
362
+ // outcome, never as a failed action (it runs AFTER the §14 guarantee).
363
+ const propagationWarnings: string[] = [];
364
+ if (appliedTouched.length > 0) {
365
+ try {
366
+ await this.deps.propagate(namespace, appliedTouched);
367
+ } catch (err) {
368
+ propagationWarnings.push(`propagation failed (non-fatal): ${errMsg(err)}`);
369
+ }
370
+ }
371
+
372
+ // ── Phase 4: audit record ─────────────────────────────────────────────
373
+ const anyFailed = results.some((r) => r.status === "failed");
374
+ const status: CorrectionOutcome["status"] = anyFailed ? "partial" : "applied";
375
+ const appliedAt = this.deps.now().toISOString();
376
+ const outcome: CorrectionOutcome = {
377
+ planId,
378
+ status,
379
+ results,
380
+ auditMemoryId: "", // filled after the audit write
381
+ appliedAt,
382
+ };
383
+ if (propagationWarnings.length > 0) {
384
+ (outcome as CorrectionOutcome & { warnings?: string[] }).warnings = propagationWarnings;
385
+ }
386
+ try {
387
+ const auditId = await this.deps.appendAuditRecord(namespace, {
388
+ planId,
389
+ classification: plan.classification,
390
+ outcome,
391
+ requestText: plan.request.text,
392
+ });
393
+ outcome.auditMemoryId = auditId;
394
+ } catch (err) {
395
+ // The audit record is part of the contract but a failure to write it
396
+ // must NOT undo the applied corrections. Record as a warning.
397
+ (outcome as CorrectionOutcome & { warnings?: string[] }).warnings = [
398
+ ...propagationWarnings,
399
+ `audit record write failed (non-fatal): ${errMsg(err)}`,
400
+ ];
401
+ }
402
+
403
+ // ── Phase 5: mark plan consumed ───────────────────────────────────────
404
+ // Corrections are already applied (phases 1-4 succeeded). A markConsumed
405
+ // failure must NOT propagate — that would make a client retry re-apply all
406
+ // corrections (review thread: applyInternal-plan-unconsumed). Record as a
407
+ // warning; the pending plan is reconciled by TTL discard on the next pass.
408
+ try {
409
+ await this.planner.markConsumed(namespace, planId, status);
410
+ } catch (err) {
411
+ const w = outcome as CorrectionOutcome & { warnings?: string[] };
412
+ w.warnings = [...(w.warnings ?? []), `plan mark-consumed failed (non-fatal): ${errMsg(err)}`];
413
+ }
414
+ return outcome;
415
+ }
416
+
417
+ private async retireAndTombstone(
418
+ namespace: string,
419
+ action: CorrectionAction,
420
+ reason: "supersession" | "retraction",
421
+ results: CorrectionActionResult[],
422
+ appliedTouched: string[],
423
+ opts: { supersededBy?: string } = {},
424
+ ): Promise<void> {
425
+ const memoryId =
426
+ action.kind === "supersede" ? action.loserId : action.kind === "retract" ? action.memoryId : null;
427
+ if (!memoryId) return;
428
+ try {
429
+ const memory = await this.deps.getMemory(namespace, memoryId);
430
+ if (!memory) {
431
+ results.push({
432
+ action,
433
+ status: "failed",
434
+ error: `memory not found: ${memoryId}`,
435
+ });
436
+ return;
437
+ }
438
+ const validUntil = this.deps.biTemporalEnabled ? this.deps.now().toISOString() : undefined;
439
+ // Write the tombstone BEFORE retiring the source memory (review thread
440
+ // PG9): if appendTombstone throws here, retireMemory has not run yet, so
441
+ // the source stays active, the action fails cleanly, and a retry
442
+ // operates on un-mutated state with no resurrection window. A tombstone
443
+ // for a still-active memory (if retire later fails) is benign — it only
444
+ // blocks re-ingestion of the same content, which is exactly the intent.
445
+ const tombstoneId = await this.deps.appendTombstone(namespace, {
446
+ reason,
447
+ sourceMemoryId: memoryId,
448
+ rawContent: memory.rawContent,
449
+ ...(memory.entityRef ? { entityRef: memory.entityRef } : {}),
450
+ ...(memory.supersessionKey ? { supersessionKey: memory.supersessionKey } : {}),
451
+ });
452
+ await this.deps.retireMemory(namespace, memoryId, {
453
+ status: reason === "supersession" ? "superseded" : "retracted",
454
+ ...(opts.supersededBy ? { supersededBy: opts.supersededBy } : {}),
455
+ ...(validUntil ? { validUntil } : {}),
456
+ });
457
+ results.push({
458
+ action,
459
+ status: "applied",
460
+ memoryId,
461
+ ...(tombstoneId ? { tombstoneId } : {}),
462
+ });
463
+ appliedTouched.push(memoryId);
464
+ } catch (err) {
465
+ results.push({ action, status: "failed", error: errMsg(err) });
466
+ }
467
+ }
468
+ }
469
+
470
+ function errMsg(err: unknown): string {
471
+ if (err instanceof Error) return err.message;
472
+ return String(err);
473
+ }