@remnic/core 9.3.653 → 9.3.654

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 (240) hide show
  1. package/dist/access-cli.js +17 -17
  2. package/dist/access-http.d.ts +4 -4
  3. package/dist/access-http.js +10 -10
  4. package/dist/access-mcp.d.ts +4 -4
  5. package/dist/access-mcp.js +9 -9
  6. package/dist/access-schema.d.ts +12 -12
  7. package/dist/{access-service-CdJFd3_b.d.ts → access-service-C8A5hoXJ.d.ts} +11 -2
  8. package/dist/access-service.d.ts +4 -4
  9. package/dist/access-service.js +8 -8
  10. package/dist/action-confidence.d.ts +1 -1
  11. package/dist/active-memory-bridge.d.ts +1 -1
  12. package/dist/active-recall.d.ts +1 -1
  13. package/dist/active-recall.js +1 -1
  14. package/dist/behavior-learner.d.ts +1 -1
  15. package/dist/behavior-signals.d.ts +1 -1
  16. package/dist/bootstrap.d.ts +3 -3
  17. package/dist/briefing.d.ts +1 -1
  18. package/dist/briefing.js +3 -3
  19. package/dist/buffer-surprise-report.d.ts +1 -1
  20. package/dist/buffer.d.ts +1 -1
  21. package/dist/calibration.d.ts +1 -1
  22. package/dist/causal-behavior.d.ts +1 -1
  23. package/dist/causal-consolidation.d.ts +1 -1
  24. package/dist/causal-consolidation.js +4 -4
  25. package/dist/{chunk-KJDKZVF3.js → chunk-2DSTAWNZ.js} +3 -3
  26. package/dist/chunk-3RACUBII.js +212 -0
  27. package/dist/chunk-3RACUBII.js.map +1 -0
  28. package/dist/{chunk-Y7NWBBHV.js → chunk-6CVI6BP6.js} +2 -2
  29. package/dist/{chunk-R3PQUPQ4.js → chunk-6IMKOIZ6.js} +85 -3
  30. package/dist/chunk-6IMKOIZ6.js.map +1 -0
  31. package/dist/{chunk-WTI35CVJ.js → chunk-BJA6DQOC.js} +5 -5
  32. package/dist/{chunk-GI45G4BK.js → chunk-BP2EV6W5.js} +3 -3
  33. package/dist/{chunk-WLGE6KEO.js → chunk-DBM2BD22.js} +3 -3
  34. package/dist/{chunk-IENGGY2C.js → chunk-ENV6RDTD.js} +2 -2
  35. package/dist/{chunk-BEMWL2FZ.js → chunk-FVRBLJP6.js} +2 -2
  36. package/dist/{chunk-H3PHZLMF.js → chunk-GKKAXVAJ.js} +20 -11
  37. package/dist/chunk-GKKAXVAJ.js.map +1 -0
  38. package/dist/{chunk-NOBL7OUP.js → chunk-GPW2E4LN.js} +12 -5
  39. package/dist/{chunk-NOBL7OUP.js.map → chunk-GPW2E4LN.js.map} +1 -1
  40. package/dist/{chunk-KWM33SPU.js → chunk-JMQSYGXS.js} +2 -2
  41. package/dist/{chunk-QQHIQ7JD.js → chunk-JYN7QNTA.js} +87 -18
  42. package/dist/chunk-JYN7QNTA.js.map +1 -0
  43. package/dist/{chunk-AJE7FJVE.js → chunk-K6X553JB.js} +2 -2
  44. package/dist/{chunk-E3J6O6N7.js → chunk-LJCEWTG3.js} +19 -8
  45. package/dist/{chunk-E3J6O6N7.js.map → chunk-LJCEWTG3.js.map} +1 -1
  46. package/dist/{chunk-EW52H5EM.js → chunk-NAZWHTYV.js} +12 -5
  47. package/dist/chunk-NAZWHTYV.js.map +1 -0
  48. package/dist/{chunk-XMN6MMTU.js → chunk-NCGWXCSW.js} +2 -2
  49. package/dist/{chunk-C43KEWEV.js → chunk-NE2JBMLN.js} +1 -1
  50. package/dist/chunk-NE2JBMLN.js.map +1 -0
  51. package/dist/{chunk-SPMZZUEJ.js → chunk-OL2364SB.js} +2020 -368
  52. package/dist/chunk-OL2364SB.js.map +1 -0
  53. package/dist/{chunk-JF7SFXTG.js → chunk-QKK64Z6M.js} +2 -2
  54. package/dist/{chunk-IVYSVAC6.js → chunk-QW6JZO5P.js} +2 -2
  55. package/dist/{chunk-EHQLDFSH.js → chunk-RGPUQ66K.js} +2 -2
  56. package/dist/{chunk-CFOCZPIQ.js → chunk-T2C6QJG2.js} +2 -2
  57. package/dist/{chunk-V4UDXYGG.js → chunk-XWQ6ERUG.js} +2 -2
  58. package/dist/{chunk-BNFRL6QW.js → chunk-Y2RIIF6H.js} +2 -2
  59. package/dist/{chunk-C63WC454.js → chunk-YLZLPVKK.js} +22 -1
  60. package/dist/chunk-YLZLPVKK.js.map +1 -0
  61. package/dist/{chunk-RZOBQ23O.js → chunk-Z5MQI7K2.js} +2 -2
  62. package/dist/{chunk-PRQXUSQV.js → chunk-ZCORQM74.js} +2 -2
  63. package/dist/{cli-DDo7Qgs-.d.ts → cli-uQgvDFNE.d.ts} +3 -3
  64. package/dist/cli.d.ts +5 -5
  65. package/dist/cli.js +22 -22
  66. package/dist/compounding/engine.d.ts +1 -1
  67. package/dist/compounding/engine.js +3 -3
  68. package/dist/compounding/preference-consolidator.d.ts +1 -1
  69. package/dist/compression-optimizer.d.ts +1 -1
  70. package/dist/config.d.ts +1 -1
  71. package/dist/config.js +1 -1
  72. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  73. package/dist/connectors/codex-materialize-runner.js +3 -3
  74. package/dist/connectors/codex-materialize.d.ts +1 -1
  75. package/dist/connectors/index.d.ts +1 -1
  76. package/dist/connectors/index.js +3 -3
  77. package/dist/consolidation-provenance-check.d.ts +1 -1
  78. package/dist/consolidation-undo.d.ts +1 -1
  79. package/dist/contradiction/index.d.ts +19 -1
  80. package/dist/contradiction/index.js +1 -1
  81. package/dist/conversation-index/backend.d.ts +1 -1
  82. package/dist/conversation-index/chunker.d.ts +1 -1
  83. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  84. package/dist/conversation-index/indexer.d.ts +1 -1
  85. package/dist/conversation-index/search.d.ts +1 -1
  86. package/dist/day-summary.d.ts +1 -1
  87. package/dist/delinearize.d.ts +1 -1
  88. package/dist/direct-answer-wiring.d.ts +1 -1
  89. package/dist/direct-answer.d.ts +1 -1
  90. package/dist/embedding-fallback.d.ts +1 -1
  91. package/dist/enrichment/index.d.ts +1 -1
  92. package/dist/entity-retrieval.d.ts +1 -1
  93. package/dist/entity-retrieval.js +3 -3
  94. package/dist/entity-schema.d.ts +1 -1
  95. package/dist/explicit-capture.d.ts +3 -3
  96. package/dist/explicit-capture.js +1 -1
  97. package/dist/extraction-judge-telemetry.d.ts +1 -1
  98. package/dist/extraction-judge-training.d.ts +1 -1
  99. package/dist/extraction-judge.d.ts +1 -1
  100. package/dist/extraction.d.ts +1 -1
  101. package/dist/fallback-llm.d.ts +1 -1
  102. package/dist/identity-continuity.d.ts +1 -1
  103. package/dist/importance.d.ts +1 -1
  104. package/dist/index.d.ts +8 -8
  105. package/dist/index.js +30 -28
  106. package/dist/index.js.map +1 -1
  107. package/dist/intent.d.ts +1 -1
  108. package/dist/lcm/engine.d.ts +1 -1
  109. package/dist/lcm/index.d.ts +1 -1
  110. package/dist/lcm/tools.d.ts +1 -1
  111. package/dist/lifecycle.d.ts +1 -1
  112. package/dist/live-connectors-runner.d.ts +1 -1
  113. package/dist/local-llm.d.ts +1 -1
  114. package/dist/maintenance/memory-governance.d.ts +1 -1
  115. package/dist/maintenance/memory-governance.js +3 -3
  116. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  117. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  118. package/dist/mcp-memory-inspector-app.d.ts +4 -4
  119. package/dist/memory-action-policy.d.ts +1 -1
  120. package/dist/memory-cache.d.ts +1 -1
  121. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  122. package/dist/memory-projection-store.d.ts +1 -1
  123. package/dist/memory-provenance.d.ts +1 -1
  124. package/dist/memory-worth-outcomes.d.ts +1 -1
  125. package/dist/models-json.d.ts +1 -1
  126. package/dist/namespaces/migrate.d.ts +1 -1
  127. package/dist/namespaces/migrate.js +4 -4
  128. package/dist/namespaces/principal.d.ts +1 -1
  129. package/dist/namespaces/search.d.ts +1 -1
  130. package/dist/namespaces/storage.d.ts +52 -3
  131. package/dist/namespaces/storage.js +9 -5
  132. package/dist/native-knowledge.d.ts +1 -1
  133. package/dist/operator-toolkit.d.ts +1 -1
  134. package/dist/operator-toolkit.js +7 -7
  135. package/dist/{orchestrator-8fTZsa0y.d.ts → orchestrator-B4Y4sWQH.d.ts} +503 -3
  136. package/dist/orchestrator.d.ts +3 -3
  137. package/dist/orchestrator.js +13 -13
  138. package/dist/patterns-cli.d.ts +1 -1
  139. package/dist/policy-runtime.d.ts +1 -1
  140. package/dist/qmd-recall-cache.d.ts +1 -1
  141. package/dist/qmd.d.ts +1 -1
  142. package/dist/recall-disclosure-escalation.d.ts +1 -1
  143. package/dist/recall-explain-renderer.d.ts +1 -1
  144. package/dist/recall-explain-renderer.js +3 -3
  145. package/dist/recall-planner-llm.d.ts +1 -1
  146. package/dist/recall-state.d.ts +1 -1
  147. package/dist/recall-tag-filter.d.ts +1 -1
  148. package/dist/recall-xray-cli.d.ts +1 -1
  149. package/dist/recall-xray-cli.js +4 -4
  150. package/dist/recall-xray-renderer.d.ts +1 -1
  151. package/dist/recall-xray-renderer.js +3 -3
  152. package/dist/recall-xray.d.ts +1 -1
  153. package/dist/recall-xray.js +2 -2
  154. package/dist/{resolution-3SAP4SH2.js → resolution-IDTEBJFS.js} +2 -2
  155. package/dist/resolve-auth-token.d.ts +1 -1
  156. package/dist/resume-bundles.js +2 -2
  157. package/dist/retrieval-agents.d.ts +1 -1
  158. package/dist/retrieval-tiers.d.ts +1 -1
  159. package/dist/routing/engine.d.ts +1 -1
  160. package/dist/routing/store.d.ts +1 -1
  161. package/dist/search/embed-helper.d.ts +1 -1
  162. package/dist/search/factory.d.ts +1 -1
  163. package/dist/search/index.d.ts +1 -1
  164. package/dist/search/lancedb-backend.d.ts +1 -1
  165. package/dist/search/meilisearch-backend.d.ts +1 -1
  166. package/dist/search/noop-backend.d.ts +1 -1
  167. package/dist/search/orama-backend.d.ts +1 -1
  168. package/dist/search/port.d.ts +1 -1
  169. package/dist/search/remote-backend.d.ts +1 -1
  170. package/dist/{semantic-consolidation-DKdYzQOg.d.ts → semantic-consolidation-BKd0Pype.d.ts} +1 -1
  171. package/dist/semantic-consolidation.d.ts +2 -2
  172. package/dist/semantic-consolidation.js +4 -4
  173. package/dist/semantic-rule-promotion.js +3 -3
  174. package/dist/semantic-rule-verifier.d.ts +1 -1
  175. package/dist/semantic-rule-verifier.js +3 -3
  176. package/dist/session-observer-bands.d.ts +1 -1
  177. package/dist/session-observer-state.d.ts +1 -1
  178. package/dist/shared-context/manager.d.ts +1 -1
  179. package/dist/signal.d.ts +1 -1
  180. package/dist/storage.d.ts +1 -1
  181. package/dist/storage.js +2 -2
  182. package/dist/summarizer.d.ts +1 -1
  183. package/dist/summary-snapshot.d.ts +1 -1
  184. package/dist/temporal-supersession.d.ts +1 -1
  185. package/dist/temporal-validity.d.ts +1 -1
  186. package/dist/threading.d.ts +1 -1
  187. package/dist/tier-migration.d.ts +1 -1
  188. package/dist/tier-routing.d.ts +1 -1
  189. package/dist/topics.d.ts +1 -1
  190. package/dist/transcript.d.ts +1 -1
  191. package/dist/{types-D8yUmSik.d.ts → types-BgChEr0M.d.ts} +11 -0
  192. package/dist/types.d.ts +1 -1
  193. package/dist/types.js +1 -1
  194. package/dist/utility-runtime.d.ts +1 -1
  195. package/dist/verified-recall.js +3 -3
  196. package/package.json +1 -1
  197. package/src/access-http.ts +7 -0
  198. package/src/access-mcp.ts +7 -0
  199. package/src/access-service.ts +12 -0
  200. package/src/cli.ts +104 -0
  201. package/src/config.test.ts +40 -0
  202. package/src/config.ts +29 -0
  203. package/src/contradiction/contradiction.test.ts +284 -0
  204. package/src/contradiction/resolution.ts +151 -4
  205. package/src/explicit-capture.ts +31 -10
  206. package/src/index.ts +10 -0
  207. package/src/namespaces/catalog.test.ts +3356 -0
  208. package/src/namespaces/catalog.ts +2123 -0
  209. package/src/namespaces/storage.ts +210 -30
  210. package/src/orchestrator-flush.test.ts +300 -0
  211. package/src/orchestrator.ts +851 -240
  212. package/src/types.ts +11 -0
  213. package/dist/chunk-C43KEWEV.js.map +0 -1
  214. package/dist/chunk-C63WC454.js.map +0 -1
  215. package/dist/chunk-EW52H5EM.js.map +0 -1
  216. package/dist/chunk-H3PHZLMF.js.map +0 -1
  217. package/dist/chunk-ORGWWNJG.js +0 -131
  218. package/dist/chunk-ORGWWNJG.js.map +0 -1
  219. package/dist/chunk-QQHIQ7JD.js.map +0 -1
  220. package/dist/chunk-R3PQUPQ4.js.map +0 -1
  221. package/dist/chunk-SPMZZUEJ.js.map +0 -1
  222. /package/dist/{chunk-KJDKZVF3.js.map → chunk-2DSTAWNZ.js.map} +0 -0
  223. /package/dist/{chunk-Y7NWBBHV.js.map → chunk-6CVI6BP6.js.map} +0 -0
  224. /package/dist/{chunk-WTI35CVJ.js.map → chunk-BJA6DQOC.js.map} +0 -0
  225. /package/dist/{chunk-GI45G4BK.js.map → chunk-BP2EV6W5.js.map} +0 -0
  226. /package/dist/{chunk-WLGE6KEO.js.map → chunk-DBM2BD22.js.map} +0 -0
  227. /package/dist/{chunk-IENGGY2C.js.map → chunk-ENV6RDTD.js.map} +0 -0
  228. /package/dist/{chunk-BEMWL2FZ.js.map → chunk-FVRBLJP6.js.map} +0 -0
  229. /package/dist/{chunk-KWM33SPU.js.map → chunk-JMQSYGXS.js.map} +0 -0
  230. /package/dist/{chunk-AJE7FJVE.js.map → chunk-K6X553JB.js.map} +0 -0
  231. /package/dist/{chunk-XMN6MMTU.js.map → chunk-NCGWXCSW.js.map} +0 -0
  232. /package/dist/{chunk-JF7SFXTG.js.map → chunk-QKK64Z6M.js.map} +0 -0
  233. /package/dist/{chunk-IVYSVAC6.js.map → chunk-QW6JZO5P.js.map} +0 -0
  234. /package/dist/{chunk-EHQLDFSH.js.map → chunk-RGPUQ66K.js.map} +0 -0
  235. /package/dist/{chunk-CFOCZPIQ.js.map → chunk-T2C6QJG2.js.map} +0 -0
  236. /package/dist/{chunk-V4UDXYGG.js.map → chunk-XWQ6ERUG.js.map} +0 -0
  237. /package/dist/{chunk-BNFRL6QW.js.map → chunk-Y2RIIF6H.js.map} +0 -0
  238. /package/dist/{chunk-RZOBQ23O.js.map → chunk-Z5MQI7K2.js.map} +0 -0
  239. /package/dist/{chunk-PRQXUSQV.js.map → chunk-ZCORQM74.js.map} +0 -0
  240. /package/dist/{resolution-3SAP4SH2.js.map → resolution-IDTEBJFS.js.map} +0 -0
@@ -5,11 +5,11 @@
5
5
  * reimplement supersession logic here (rule 22: deduplicate resolution).
6
6
  */
7
7
 
8
+ import { log } from "../logger.js";
8
9
  import type { StorageManager } from "../storage.js";
9
10
  import type { MemoryCategory, MemoryFile } from "../types.js";
10
11
  import type { ResolutionVerb } from "./contradiction-review.js";
11
- import { resolvePair, readPair } from "./contradiction-review.js";
12
- import { log } from "../logger.js";
12
+ import { readPair, resolvePair } from "./contradiction-review.js";
13
13
 
14
14
  export interface ResolutionResult {
15
15
  pairId: string;
@@ -29,6 +29,24 @@ export interface ExecuteResolutionOptions {
29
29
  mergedCategory?: MemoryCategory;
30
30
  /** Resolve storage for the pair namespace, or the default namespace for legacy unscoped pairs. */
31
31
  storageForNamespace?: (namespace: string | undefined) => StorageManager | Promise<StorageManager>;
32
+ /**
33
+ * Best-effort hook invoked after a contradiction resolution leaves a durable
34
+ * mutation in the namespace's memory files (issue #1499 sweep, NH1dX / NH3X3).
35
+ * Every mutating verb — `merge` (creates a new memory and supersedes both
36
+ * sources), `keep-a`, and `keep-b` (supersede the losing source + rewrite
37
+ * frontmatter) — writes directly to the pair's (possibly DYNAMIC) namespace
38
+ * storage, bypassing the extraction write path that records catalog writes. So
39
+ * without this the namespace's `lastWriteAt` stays stale and QMD maintenance /
40
+ * `writtenSince` can skip a namespace whose only post-write mutation is
41
+ * resolving a contradiction. It fires after the resolution commits, or after a
42
+ * failed resolution/rollback path when durable memory changes are still left on
43
+ * disk. If a failure rolls back cleanly, this is never called, so the catalog
44
+ * never records a write that did not survive (rule #25). Non-mutating verbs
45
+ * (`both-valid`, `needs-more-context`) never trigger it. Callers wire this to
46
+ * `Orchestrator.recordCatalogWrite(namespace, storageDir)`. Must be
47
+ * failure-tolerant: it is fire-and-forget and must never affect resolution.
48
+ */
49
+ onMergedMemoryWritten?: (namespace: string | undefined, storageDir: string) => void;
32
50
  }
33
51
 
34
52
  const VALID_VERBS: ResolutionVerb[] = ["keep-a", "keep-b", "merge", "both-valid", "needs-more-context"];
@@ -81,6 +99,48 @@ export async function executeResolution(
81
99
  let message = "";
82
100
  let supersedeFailed = false;
83
101
  let rollbackAfterResolveFailure: (() => Promise<boolean>) | null = null;
102
+ // Deferred catalog-write touch for any resolution that leaves durable namespace
103
+ // memory mutations (issue #1499 sweep, NH1dX / NH3X3). Rule #25: never record a
104
+ // catalog touch for a write that is fully rolled back. Successful mutating
105
+ // resolutions invoke it after `resolvePair` persists. Failed paths invoke it
106
+ // only when rollback inspection shows the namespace still differs from the
107
+ // pre-mutation snapshot.
108
+ let recordCatalogWriteTouch: (() => void) | null = null;
109
+ // Returns the deferred touch fn for a mutating verb (or null when the caller
110
+ // wired no catalog hook), so each branch assigns `recordCatalogWriteTouch`
111
+ // directly in the function body — keeping TS control-flow narrowing intact at
112
+ // the post-commit invocation below.
113
+ const buildCatalogTouch = (): (() => void) | null => {
114
+ if (!options.onMergedMemoryWritten) return null;
115
+ const onMergedMemoryWritten = options.onMergedMemoryWritten;
116
+ const namespace = pair.namespace;
117
+ const storageDir = resolutionStorage.dir;
118
+ return () => onMergedMemoryWritten(namespace, storageDir);
119
+ };
120
+ const catalogWriteTouch = buildCatalogTouch();
121
+ const recordCatalogWriteTouchSafely = (context: string, touch = catalogWriteTouch): void => {
122
+ if (!touch) return;
123
+ try {
124
+ touch();
125
+ } catch (err) {
126
+ log.warn(
127
+ "[contradiction-resolution] catalog write touch failed for pair=%s context=%s: %s",
128
+ pairId,
129
+ context,
130
+ err instanceof Error ? err.message : err,
131
+ );
132
+ }
133
+ };
134
+ const touchCatalogIfRollbackLeftChange = async (
135
+ context: string,
136
+ snapshots: MemoryFile[],
137
+ replacement?: Extract<MergeReplacement, { ok: true }>,
138
+ ): Promise<void> => {
139
+ if (!catalogWriteTouch) return;
140
+ if (await rollbackLeftDurableMutation(resolutionStorage, snapshots, replacement)) {
141
+ recordCatalogWriteTouchSafely(context);
142
+ }
143
+ };
84
144
 
85
145
  switch (verb) {
86
146
  case "keep-a": {
@@ -98,6 +158,9 @@ export async function executeResolution(
98
158
  affectedIds.push(idB);
99
159
  rollbackAfterResolveFailure = async () =>
100
160
  restoreMemorySnapshot(resolutionStorage, sourceB!, "contradiction-resolution:keep-a-rollback");
161
+ // keep-a superseded idB in this namespace — record a catalog touch once
162
+ // the resolution durably commits (NH3X3).
163
+ recordCatalogWriteTouch = catalogWriteTouch;
101
164
  message = `Kept ${idA}, superseded ${idB}`;
102
165
  }
103
166
  else {
@@ -105,6 +168,9 @@ export async function executeResolution(
105
168
  const rolledBack = sourceB
106
169
  ? await restoreMemorySnapshot(resolutionStorage, sourceB, "contradiction-resolution:keep-a-rollback")
107
170
  : false;
171
+ if (sourceB && !rolledBack) {
172
+ await touchCatalogIfRollbackLeftChange("keep-a-rollback-incomplete", [sourceB]);
173
+ }
108
174
  message = rolledBack
109
175
  ? `Supersede failed for ${idB}; restored ${idB} and did not resolve`
110
176
  : `Supersede failed for ${idB}; rollback incomplete for ${idB} and pair is not resolved`;
@@ -126,6 +192,9 @@ export async function executeResolution(
126
192
  affectedIds.push(idA);
127
193
  rollbackAfterResolveFailure = async () =>
128
194
  restoreMemorySnapshot(resolutionStorage, sourceA!, "contradiction-resolution:keep-b-rollback");
195
+ // keep-b superseded idA in this namespace — record a catalog touch once
196
+ // the resolution durably commits (NH3X3).
197
+ recordCatalogWriteTouch = catalogWriteTouch;
129
198
  message = `Kept ${idB}, superseded ${idA}`;
130
199
  }
131
200
  else {
@@ -133,6 +202,9 @@ export async function executeResolution(
133
202
  const rolledBack = sourceA
134
203
  ? await restoreMemorySnapshot(resolutionStorage, sourceA, "contradiction-resolution:keep-b-rollback")
135
204
  : false;
205
+ if (sourceA && !rolledBack) {
206
+ await touchCatalogIfRollbackLeftChange("keep-b-rollback-incomplete", [sourceA]);
207
+ }
136
208
  message = rolledBack
137
209
  ? `Supersede failed for ${idA}; restored ${idA} and did not resolve`
138
210
  : `Supersede failed for ${idA}; rollback incomplete for ${idA} and pair is not resolved`;
@@ -157,6 +229,9 @@ export async function executeResolution(
157
229
  if (rolledBackA) {
158
230
  await cleanupCreatedReplacement(resolutionStorage, replacement);
159
231
  }
232
+ else {
233
+ await touchCatalogIfRollbackLeftChange("merge-first-rollback-incomplete", [replacement.sourceA], replacement);
234
+ }
160
235
  break;
161
236
  }
162
237
 
@@ -174,6 +249,13 @@ export async function executeResolution(
174
249
  if (rolledBackA && rolledBackB) {
175
250
  await cleanupCreatedReplacement(resolutionStorage, replacement);
176
251
  }
252
+ else {
253
+ await touchCatalogIfRollbackLeftChange(
254
+ "merge-second-rollback-incomplete",
255
+ [replacement.sourceA, replacement.sourceB],
256
+ replacement,
257
+ );
258
+ }
177
259
  break;
178
260
  }
179
261
 
@@ -186,6 +268,17 @@ export async function executeResolution(
186
268
  }
187
269
  return rolledBackA && rolledBackB;
188
270
  };
271
+ // Catalog write touch (issue #1499 sweep): the merge supersedes BOTH sources
272
+ // (and, when created, writes a fresh merged memory) in the pair's (possibly
273
+ // dynamic) namespace storage — but the resolution is not durable yet
274
+ // (resolvePair persists below, and a failure rolls the merge back). Defer
275
+ // the touch so it fires ONLY after the resolution commits past the rollback
276
+ // point (NH1dX, rule #25). Arm it for EVERY successful merge — even reusing
277
+ // an existing merged-id still supersedes both sources, a namespace mutation
278
+ // that must refresh `lastWriteAt` (NH3X3). Otherwise a dynamic namespace
279
+ // whose only durable mutation is a contradiction merge stays invisible to
280
+ // QMD maintenance / `writtenSince`. Best-effort on the caller side.
281
+ recordCatalogWriteTouch = catalogWriteTouch;
189
282
  message = `Both memories superseded by merged ${replacement.mergedId}`;
190
283
  break;
191
284
  }
@@ -217,10 +310,20 @@ export async function executeResolution(
217
310
  affectedIds.length = 0;
218
311
  message = rolledBack
219
312
  ? `Resolution persistence failed; rolled back memory changes and did not resolve ${pairId}`
220
- : `Resolution persistence failed; rollback incomplete and pair is not resolved`;
313
+ : "Resolution persistence failed; rollback incomplete and pair is not resolved";
314
+ if (!rolledBack && recordCatalogWriteTouch) {
315
+ recordCatalogWriteTouchSafely("resolve-persistence-rollback-incomplete", recordCatalogWriteTouch);
316
+ }
221
317
  } else {
222
- message = `Resolution persistence failed; pair is not resolved`;
318
+ message = "Resolution persistence failed; pair is not resolved";
223
319
  }
320
+ } else if (recordCatalogWriteTouch) {
321
+ // The resolution durably committed (memory mutated AND the resolution
322
+ // persisted past the rollback point). Only now is it safe to record the
323
+ // catalog write for the namespace mutation (NH1dX / NH3X3, rule #25).
324
+ // Best-effort: the caller's callback swallows errors; guard here so a
325
+ // throwing callback never derails a successful resolution.
326
+ recordCatalogWriteTouchSafely("resolved", recordCatalogWriteTouch);
224
327
  }
225
328
  }
226
329
  log.info("[contradiction-resolution] pair=%s verb=%s affected=%d", pairId, verb, affectedIds.length);
@@ -376,6 +479,50 @@ async function restoreMemorySnapshot(
376
479
  }
377
480
  }
378
481
 
482
+ async function rollbackLeftDurableMutation(
483
+ storage: StorageManager,
484
+ snapshots: MemoryFile[],
485
+ replacement?: Extract<MergeReplacement, { ok: true }>,
486
+ ): Promise<boolean> {
487
+ for (const snapshot of snapshots) {
488
+ try {
489
+ const current = await storage.getMemoryById(snapshot.frontmatter.id);
490
+ if (!current) return true;
491
+ if (supersessionStateChanged(current, snapshot)) return true;
492
+ } catch (err) {
493
+ log.warn(
494
+ "[contradiction-resolution] rollback inspection failed for %s: %s",
495
+ snapshot.frontmatter.id,
496
+ err instanceof Error ? err.message : err,
497
+ );
498
+ return true;
499
+ }
500
+ }
501
+
502
+ if (replacement?.created) {
503
+ try {
504
+ return (await storage.getMemoryById(replacement.mergedId)) !== null;
505
+ } catch (err) {
506
+ log.warn(
507
+ "[contradiction-resolution] rollback replacement inspection failed for %s: %s",
508
+ replacement.mergedId,
509
+ err instanceof Error ? err.message : err,
510
+ );
511
+ return true;
512
+ }
513
+ }
514
+
515
+ return false;
516
+ }
517
+
518
+ function supersessionStateChanged(current: MemoryFile, snapshot: MemoryFile): boolean {
519
+ return (
520
+ current.frontmatter.status !== snapshot.frontmatter.status ||
521
+ current.frontmatter.supersededBy !== snapshot.frontmatter.supersededBy ||
522
+ current.frontmatter.supersededAt !== snapshot.frontmatter.supersededAt
523
+ );
524
+ }
525
+
379
526
  async function cleanupCreatedReplacement(storage: StorageManager, replacement: Extract<MergeReplacement, { ok: true }>): Promise<void> {
380
527
  if (!replacement.created) return;
381
528
  await cleanupMemoryId(storage, replacement.mergedId);
@@ -435,6 +435,15 @@ export async function persistExplicitCapture(
435
435
  expiresAt: candidate.expiresAt,
436
436
  source: source === "inline" ? "explicit-inline" : "explicit",
437
437
  });
438
+ // Record the catalog write touch (issue #1499, round 5 codex P2). Explicit
439
+ // captures bypass the extraction write path, so without this their namespace
440
+ // never updates `lastWriteAt`. An undefined namespace means the DEFAULT root
441
+ // (round 6, codex P2), which recordCatalogWrite resolves. The method is an
442
+ // optional best-effort hook — guard so Orchestrator-like callers without it
443
+ // don't break (rule #33). Best-effort and failure-tolerant.
444
+ if (typeof orchestrator.recordCatalogWrite === "function") {
445
+ orchestrator.recordCatalogWrite(resolvedNamespace, storage.dir);
446
+ }
438
447
 
439
448
  const created = new Date().toISOString();
440
449
  const event: MemoryLifecycleEvent = {
@@ -531,16 +540,28 @@ export async function queueExplicitCaptureForReview(
531
540
  entityRef: sanitizeReviewMetadata(input.entityRef),
532
541
  source: source === "inline" ? "explicit-inline-review" : "explicit-review",
533
542
  });
534
- const created = await storage.getMemoryById(id);
535
- if (created) {
536
- await storage.writeMemoryFrontmatter(created, {
537
- status: "pending_review",
538
- updated: new Date().toISOString(),
539
- }, {
540
- actor: explicitCaptureActor(source),
541
- reasonCode: reason,
542
- ruleVersion: "explicit-capture.v1",
543
- });
543
+ try {
544
+ const created = await storage.getMemoryById(id);
545
+ if (created) {
546
+ await storage.writeMemoryFrontmatter(created, {
547
+ status: "pending_review",
548
+ updated: new Date().toISOString(),
549
+ }, {
550
+ actor: explicitCaptureActor(source),
551
+ reasonCode: reason,
552
+ ruleVersion: "explicit-capture.v1",
553
+ });
554
+ }
555
+ } finally {
556
+ // Record the catalog write touch (issue #1499, round 5/6 codex P2; NIhUg).
557
+ // A queued review capture writes memory to the namespace's root (the DEFAULT
558
+ // root when undefined), so its `lastWriteAt` must reflect the write once
559
+ // `writeMemory` returns an id. If the later pending-review frontmatter update
560
+ // fails, the memory file is still durable and must not disappear from
561
+ // writtenSince/maintenance scheduling. Guarded optional hook (rule #33).
562
+ if (typeof orchestrator.recordCatalogWrite === "function") {
563
+ orchestrator.recordCatalogWrite(queueNamespace, storage.dir);
564
+ }
544
565
  }
545
566
  const event: MemoryLifecycleEvent = {
546
567
  eventId: `mle-${randomUUID()}`,
package/src/index.ts CHANGED
@@ -323,6 +323,16 @@ export { MeilisearchBackend } from "./search/meilisearch-backend.js";
323
323
 
324
324
  export { buildEntityRecallSection } from "./entity-retrieval.js";
325
325
  export { resolvePrincipal } from "./namespaces/principal.js";
326
+ export {
327
+ NamespaceCatalog,
328
+ type NamespaceRecord,
329
+ type NamespaceKind,
330
+ type NamespaceDiscoverySource,
331
+ type NamespaceCatalogFilter,
332
+ type NamespaceTouchMetadata,
333
+ type NamespaceCatalogRebuildResult,
334
+ type NamespaceCatalogSkippedRoot,
335
+ } from "./namespaces/catalog.js";
326
336
 
327
337
  // ---------------------------------------------------------------------------
328
338
  // Session identity / transcript pathing (issue #1496)