@remnic/core 9.3.708 → 9.3.709

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,416 @@
1
+ /**
2
+ * correction/correction-contract.ts — Types for the Correction Contract
3
+ * (issue #1580).
4
+ *
5
+ * The Correction Contract is the SINGLE plan/apply pipeline every memory
6
+ * correction flows through: supersession, invalidation, tombstone, edit,
7
+ * rescope, redaction. A correction arrives as a plain-language statement
8
+ * ("we migrated to MySQL in March"); the planner turns it into a structured
9
+ * {@link CorrectionPlan}; the executor applies the plan in non-destructive
10
+ * order through the existing storage/orchestrator chokepoints.
11
+ *
12
+ * This module is PURE types + validation helpers — no I/O, no side effects.
13
+ * The planner and executor live in sibling modules and inject their own
14
+ * collaborators so the contract is testable in isolation (rule 33).
15
+ *
16
+ * Design rules honored (issue #1580 design section):
17
+ * - Plans are per-request state on disk, never module-level (rules 11/47).
18
+ * - Caller-supplied namespaces are NEVER trusted raw — the service
19
+ * resolves them through the normal namespace policy (rule 42).
20
+ * - Bulk operations refuse past maxAffected (no silent truncation, §39).
21
+ * - `never_store` redaction patterns must be bounded and safe (§34).
22
+ */
23
+
24
+ import type { MemoryCategory, MemoryFrontmatter } from "../types.js";
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Request
28
+ // ---------------------------------------------------------------------------
29
+
30
+ /**
31
+ * The user-facing input: a plain-language correction plus optional explicit
32
+ * targets. This is what every surface (MCP / HTTP / CLI) collects before
33
+ * handing off to the planner.
34
+ */
35
+ export interface CorrectionRequest {
36
+ /** Natural-language correction statement. Required, non-empty. */
37
+ text: string;
38
+ /**
39
+ * Explicit target memory ids (or handles via #1582). When present the
40
+ * planner resolves these directly; an unknown id is an explicit error
41
+ * (rule 34), never a silent empty plan. When absent the planner searches.
42
+ */
43
+ targetIds?: string[];
44
+ /** Session key for namespace/principal resolution. */
45
+ sessionKey?: string;
46
+ /** Authenticated principal (resolved by the service, never trusted raw). */
47
+ principal?: string;
48
+ /**
49
+ * Caller-suggested namespace. ALWAYS re-resolved through the namespace
50
+ * policy by the service before reaching the planner (rule 42). The
51
+ * planner only sees the ALREADY-AUTHORIZED namespace.
52
+ */
53
+ namespace?: string;
54
+ }
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Classification + actions
58
+ // ---------------------------------------------------------------------------
59
+
60
+ export type CorrectionClassification =
61
+ | "wrong"
62
+ | "outdated"
63
+ | "incomplete"
64
+ | "wrong_scope"
65
+ | "never_store";
66
+
67
+ export const CORRECTION_CLASSIFICATIONS: readonly CorrectionClassification[] = [
68
+ "wrong",
69
+ "outdated",
70
+ "incomplete",
71
+ "wrong_scope",
72
+ "never_store",
73
+ ];
74
+
75
+ /**
76
+ * A draft for a replacement memory (supersede action). Mirrors the subset of
77
+ * {@link MemoryFrontmatter} a correction caller may legitimately set; the
78
+ * executor fills in id/created/updated/source.
79
+ */
80
+ export interface MemoryDraft {
81
+ content: string;
82
+ category?: MemoryCategory;
83
+ confidence?: number;
84
+ tags?: string[];
85
+ entityRef?: string;
86
+ /** Optional event-time anchor for bi-temporal supersession (#1578). */
87
+ validAt?: string;
88
+ /** Optional observed-at anchor for bi-temporal supersession (#1578). */
89
+ observedAt?: string;
90
+ structuredAttributes?: Record<string, string>;
91
+ }
92
+
93
+ /**
94
+ * The known {@link MemoryCategory} values. A replacement/rescope category must
95
+ * be one of these — rejecting path-like or unexpected strings before they reach
96
+ * `StorageManager.writeMemory`, which incorporates the category into the
97
+ * generated memory id/path (review thread Of-XJ).
98
+ */
99
+ export const MEMORY_CATEGORIES: readonly MemoryCategory[] = [
100
+ "fact", "preference", "correction", "entity", "decision",
101
+ "relationship", "principle", "commitment", "moment",
102
+ "skill", "rule", "procedure", "reasoning_trace",
103
+ ];
104
+
105
+ export type CorrectionAction =
106
+ /** Outdated: a new fact replaces the loser. */
107
+ | { kind: "supersede"; loserId: string; replacement?: MemoryDraft }
108
+ /** Incomplete / minor-wrong: a versioned edit to an existing memory. */
109
+ | { kind: "edit"; memoryId: string; patch: string }
110
+ /** Wrong: retire + tombstone. */
111
+ | { kind: "retract"; memoryId: string }
112
+ /** Wrong scope: move to a different namespace. */
113
+ | { kind: "rescope"; memoryId: string; toNamespace: string }
114
+ /** Never-store: a future-extraction redaction rule. */
115
+ | { kind: "redaction_rule"; pattern: string };
116
+
117
+ export const CORRECTION_ACTION_KINDS: readonly CorrectionAction["kind"][] = [
118
+ "supersede",
119
+ "edit",
120
+ "retract",
121
+ "rescope",
122
+ "redaction_rule",
123
+ ];
124
+
125
+ // ---------------------------------------------------------------------------
126
+ // Plan + outcome
127
+ // ---------------------------------------------------------------------------
128
+
129
+ /**
130
+ * One affected memory in a plan: where it lives, an excerpt, why it is
131
+ * affected, and (when available) the source quote that supports the
132
+ * correction (#1575 provenance).
133
+ */
134
+ export interface CorrectionAffectedEntry {
135
+ memoryId: string;
136
+ /** File path relative to the storage dir, for diff rendering. */
137
+ path: string;
138
+ excerpt: string;
139
+ why: string;
140
+ sourceQuote?: string;
141
+ }
142
+
143
+ /**
144
+ * A persisted, expiring correction plan. Read-only artifact produced by the
145
+ * planner; consumed (once) by the executor.
146
+ */
147
+ export interface CorrectionPlan {
148
+ planId: string;
149
+ request: CorrectionRequest;
150
+ /** Authorized namespace the plan is scoped to (resolved by the service). */
151
+ namespace: string;
152
+ affected: CorrectionAffectedEntry[];
153
+ classification: CorrectionClassification;
154
+ actions: CorrectionAction[];
155
+ /** Human-readable diff preview rendered via page-versioning. */
156
+ diff: string;
157
+ /** Planner confidence in [0, 1]. `0` means manual selection required. */
158
+ confidence: number;
159
+ warnings: string[];
160
+ createdAt: string;
161
+ /** ISO timestamp after which apply rejects the plan as expired. */
162
+ expiresAt: string;
163
+ /**
164
+ * Lifecycle: `pending` → `applying` → `applied`|`partial` (or `discarded`).
165
+ * The executor flips a plan to `applying` BEFORE running any mutation
166
+ * (review thread OgIqt): if the process dies mid-apply, the plan stays
167
+ * `applying` and is NOT silently retryable — a partially-applied plan must
168
+ * never be re-applied wholesale (it would duplicate succeeded actions).
169
+ */
170
+ status?: "pending" | "applying" | "applied" | "discarded" | "partial";
171
+ }
172
+
173
+ /**
174
+ * Per-action outcome recorded by the executor. An action whose new-state
175
+ * write failed is `failed`; the executor never destroys old state for a
176
+ * failed action (rule 25 / checklist §14).
177
+ */
178
+ export interface CorrectionActionResult {
179
+ action: CorrectionAction;
180
+ status: "applied" | "failed" | "skipped";
181
+ /** For supersede: the new memory id. For edit: the memory id. */
182
+ memoryId?: string;
183
+ /** For supersede/retract: the emitted tombstone id (if any). */
184
+ tombstoneId?: string;
185
+ error?: string;
186
+ }
187
+
188
+ export interface CorrectionOutcome {
189
+ planId: string;
190
+ status: "applied" | "partial";
191
+ results: CorrectionActionResult[];
192
+ /** Audit-record memory id (corrections are themselves memories). */
193
+ auditMemoryId: string;
194
+ /** ISO timestamp of the apply. */
195
+ appliedAt: string;
196
+ }
197
+
198
+ // ---------------------------------------------------------------------------
199
+ // Validation helpers (pure) — shared by planner, executor, and surface tests
200
+ // ---------------------------------------------------------------------------
201
+
202
+ /** Maximum supported `text` length. Surfaces bound this earlier; the planner
203
+ * re-validates so a direct-service caller cannot bypass. */
204
+ export const CORRECTION_TEXT_MAX = 10_000;
205
+
206
+ /** Maximum supported redaction pattern length (§34 — bounded patterns). */
207
+ export const REDACTION_PATTERN_MAX = 256;
208
+
209
+ /**
210
+ * Validate a {@link CorrectionRequest}'s invariants. Returns the cleaned
211
+ * request or throws with a field-specific message (rule 51 — list valid
212
+ * options, never silently default).
213
+ */
214
+ export function validateCorrectionRequest(request: CorrectionRequest): CorrectionRequest {
215
+ if (!request || typeof request !== "object") {
216
+ throw new CorrectionContractError("CorrectionRequest must be an object.");
217
+ }
218
+ const text = typeof request.text === "string" ? request.text.trim() : "";
219
+ if (text.length === 0) {
220
+ throw new CorrectionContractError("CorrectionRequest.text is required and must be non-empty.");
221
+ }
222
+ if (text.length > CORRECTION_TEXT_MAX) {
223
+ throw new CorrectionContractError(
224
+ `CorrectionRequest.text exceeds the ${CORRECTION_TEXT_MAX}-character limit (${text.length}).`,
225
+ );
226
+ }
227
+ const targetIds = Array.isArray(request.targetIds)
228
+ ? request.targetIds.filter((id): id is string => typeof id === "string" && id.length > 0)
229
+ : undefined;
230
+ if (targetIds !== undefined && targetIds.length === 0) {
231
+ // An empty array is treated as "not provided" so callers can forward
232
+ // optional fields without a separate presence flag.
233
+ return {
234
+ text,
235
+ ...(request.sessionKey ? { sessionKey: request.sessionKey } : {}),
236
+ ...(request.principal ? { principal: request.principal } : {}),
237
+ ...(request.namespace ? { namespace: request.namespace } : {}),
238
+ };
239
+ }
240
+ return {
241
+ text,
242
+ ...(targetIds ? { targetIds } : {}),
243
+ ...(request.sessionKey ? { sessionKey: request.sessionKey } : {}),
244
+ ...(request.principal ? { principal: request.principal } : {}),
245
+ ...(request.namespace ? { namespace: request.namespace } : {}),
246
+ };
247
+ }
248
+
249
+ /**
250
+ * Validate a redaction pattern (§34 — bounded, literal-or-safe-regex; reject
251
+ * catastrophic patterns). Returns the cleaned pattern or throws.
252
+ */
253
+ export function validateRedactionPattern(pattern: string): string {
254
+ if (typeof pattern !== "string") {
255
+ throw new CorrectionContractError("redaction_rule.pattern must be a string.");
256
+ }
257
+ const trimmed = pattern.trim();
258
+ if (trimmed.length === 0) {
259
+ throw new CorrectionContractError("redaction_rule.pattern is required.");
260
+ }
261
+ if (trimmed.length > REDACTION_PATTERN_MAX) {
262
+ throw new CorrectionContractError(
263
+ `redaction_rule.pattern exceeds the ${REDACTION_PATTERN_MAX}-character bound.`,
264
+ );
265
+ }
266
+ // Reject patterns that could match an unbounded string (catastrophic /
267
+ // overly-broad). A literal or a bounded regex is fine; a bare `.*` / `.`
268
+ // regex is not. We do NOT execute the regex (ReDoS); we only inspect shape.
269
+ if (isRegexLike(trimmed) && isOverlyBroadRegex(trimmed)) {
270
+ throw new CorrectionContractError(
271
+ "redaction_rule.pattern is too broad — use a bounded literal or a more specific pattern.",
272
+ );
273
+ }
274
+ return trimmed;
275
+ }
276
+
277
+ /** Heuristic: treat `/.../` or presence of regex metacharacters as regex. */
278
+ function isRegexLike(pattern: string): boolean {
279
+ if (pattern.startsWith("/") && pattern.endsWith("/") && pattern.length >= 2) return true;
280
+ return /[\\^$.|?*+()[\]{}]/.test(pattern);
281
+ }
282
+
283
+ /** Reject a regex that would match an arbitrary-length run of any character. */
284
+ function isOverlyBroadRegex(pattern: string): boolean {
285
+ const body = pattern.startsWith("/") && pattern.endsWith("/")
286
+ ? pattern.slice(1, -1)
287
+ : pattern;
288
+ // `.*`, `.+`, `.`, or `(.*)` etc. anywhere → overly broad.
289
+ if (/(?:^|[^\\])\(\.\*\)|(?:^|[^\\])\.\*|(?:^|[^\\])\.\+|^\.([^*+]?)$/.test(body)) {
290
+ return true;
291
+ }
292
+ return false;
293
+ }
294
+
295
+ /**
296
+ * Validate the shape of a {@link CorrectionAction}. Used by the executor
297
+ * before applying and by the surface layer to reject malformed client input
298
+ * (rule 51 — list valid kinds).
299
+ */
300
+ export function validateCorrectionAction(action: unknown): asserts action is CorrectionAction {
301
+ if (!action || typeof action !== "object") {
302
+ throw new CorrectionContractError("CorrectionAction must be an object.");
303
+ }
304
+ const a = action as Record<string, unknown>;
305
+ if (typeof a.kind !== "string" || !CORRECTION_ACTION_KINDS.includes(a.kind as CorrectionAction["kind"])) {
306
+ throw new CorrectionContractError(
307
+ `CorrectionAction.kind must be one of: ${CORRECTION_ACTION_KINDS.join(", ")}.`,
308
+ );
309
+ }
310
+ switch (a.kind) {
311
+ case "supersede":
312
+ if (typeof a.loserId !== "string" || a.loserId.length === 0) {
313
+ throw new CorrectionContractError("supersede.loserId is required.");
314
+ }
315
+ if (a.replacement !== undefined && a.replacement !== null) {
316
+ validateMemoryDraft(a.replacement);
317
+ }
318
+ break;
319
+ case "edit":
320
+ if (typeof a.memoryId !== "string" || a.memoryId.length === 0) {
321
+ throw new CorrectionContractError("edit.memoryId is required.");
322
+ }
323
+ if (typeof a.patch !== "string" || a.patch.length === 0) {
324
+ throw new CorrectionContractError("edit.patch is required and must be non-empty.");
325
+ }
326
+ break;
327
+ case "retract":
328
+ if (typeof a.memoryId !== "string" || a.memoryId.length === 0) {
329
+ throw new CorrectionContractError("retract.memoryId is required.");
330
+ }
331
+ break;
332
+ case "rescope":
333
+ if (typeof a.memoryId !== "string" || a.memoryId.length === 0) {
334
+ throw new CorrectionContractError("rescope.memoryId is required.");
335
+ }
336
+ if (typeof a.toNamespace !== "string" || a.toNamespace.trim().length === 0) {
337
+ throw new CorrectionContractError("rescope.toNamespace is required.");
338
+ }
339
+ break;
340
+ case "redaction_rule":
341
+ if (typeof a.pattern !== "string") {
342
+ throw new CorrectionContractError("redaction_rule.pattern is required.");
343
+ }
344
+ validateRedactionPattern(a.pattern);
345
+ break;
346
+ }
347
+ }
348
+
349
+ /** Validate a {@link MemoryDraft}. Throws on invalid shape. */
350
+ export function validateMemoryDraft(draft: unknown): asserts draft is MemoryDraft {
351
+ if (!draft || typeof draft !== "object") {
352
+ throw new CorrectionContractError("MemoryDraft must be an object.");
353
+ }
354
+ const d = draft as Record<string, unknown>;
355
+ if (typeof d.content !== "string" || d.content.trim().length === 0) {
356
+ throw new CorrectionContractError("MemoryDraft.content is required and must be non-empty.");
357
+ }
358
+ if (d.category !== undefined) {
359
+ if (typeof d.category !== "string" || !(MEMORY_CATEGORIES as readonly string[]).includes(d.category)) {
360
+ throw new CorrectionContractError(
361
+ `MemoryDraft.category must be one of: ${MEMORY_CATEGORIES.join(", ")}.`,
362
+ );
363
+ }
364
+ }
365
+ if (d.confidence !== undefined && (typeof d.confidence !== "number" || d.confidence < 0 || d.confidence > 1)) {
366
+ throw new CorrectionContractError("MemoryDraft.confidence must be a number in [0, 1].");
367
+ }
368
+ if (d.tags !== undefined && !Array.isArray(d.tags)) {
369
+ throw new CorrectionContractError("MemoryDraft.tags must be an array.");
370
+ }
371
+ if (d.structuredAttributes !== undefined && (typeof d.structuredAttributes !== "object" || d.structuredAttributes === null)) {
372
+ throw new CorrectionContractError("MemoryDraft.structuredAttributes must be an object.");
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Deterministic fallback plan (rule 13): when the planner's LLM is
378
+ * unavailable, the plan degrades to a search result, never an error page.
379
+ * Classification `outdated`, confidence `0`, actions empty.
380
+ */
381
+ export function deterministicFallbackPlan(args: {
382
+ request: CorrectionRequest;
383
+ namespace: string;
384
+ affected: CorrectionAffectedEntry[];
385
+ warnings: string[];
386
+ createdAt: string;
387
+ expiresAt: string;
388
+ }): CorrectionPlan {
389
+ return {
390
+ planId: newPlanId(),
391
+ request: args.request,
392
+ namespace: args.namespace,
393
+ affected: args.affected,
394
+ classification: "outdated",
395
+ actions: [],
396
+ diff: "",
397
+ confidence: 0,
398
+ warnings: [...args.warnings, "planner LLM unavailable — manual action selection required"],
399
+ createdAt: args.createdAt,
400
+ expiresAt: args.expiresAt,
401
+ status: "pending",
402
+ };
403
+ }
404
+
405
+ /** Generate a stable plan id. Exposed for tests + deterministic fallback. */
406
+ export function newPlanId(): string {
407
+ return `corr-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
408
+ }
409
+
410
+ /** Error class for contract violations. Surfaces map it to a 400 / input error. */
411
+ export class CorrectionContractError extends Error {
412
+ constructor(message: string) {
413
+ super(message);
414
+ this.name = "CorrectionContractError";
415
+ }
416
+ }