@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
@@ -18,9 +18,9 @@ import { a as SemanticDedupHit } from './semantic-DJR8_DMQ.js';
18
18
  import { ReplayTurn } from './replay/types.js';
19
19
  import { I as ImportTurn } from './types-ByK7T3L6.js';
20
20
  import { JudgeCandidate, JudgeBatchResult } from './extraction-judge.js';
21
- import { Y as BufferTurn, af as ExtractionResult, M as MemoryCategory, I as ImportanceScore, h as MemoryStatus, aw as WearableConversation, ax as WearableSourceSettings, ay as WearableNativeMemory, az as WearableMemoryMode, aA as WearableSourceConnector, aB as WearablesConfig, aC as WearableSyncSummary, aD as WearableSourceStatus, aE as WearableDayTranscript, aF as WearableCorrectionRule, P as PluginConfig, aG as CodingContext, Z as QmdSearchResult, e as BehaviorLoopPolicyState, aH as BootstrapOptions, aI as BootstrapResult, aJ as ConsolidationObservation, g as MemoryFile, ah as DaySummaryResult, m as MemoryActionEvent, H as RecallPlanMode, ai as MemoryIntent, aK as EntityTimelineEntry, j as MemoryFrontmatter, J as IdentityInjectionMode } from './types-D8yUmSik.js';
21
+ import { Y as BufferTurn, af as ExtractionResult, M as MemoryCategory, I as ImportanceScore, h as MemoryStatus, aw as WearableConversation, ax as WearableSourceSettings, ay as WearableNativeMemory, az as WearableMemoryMode, aA as WearableSourceConnector, aB as WearablesConfig, aC as WearableSyncSummary, aD as WearableSourceStatus, aE as WearableDayTranscript, aF as WearableCorrectionRule, P as PluginConfig, aG as CodingContext, Z as QmdSearchResult, e as BehaviorLoopPolicyState, aH as BootstrapOptions, aI as BootstrapResult, aJ as ConsolidationObservation, g as MemoryFile, ah as DaySummaryResult, m as MemoryActionEvent, H as RecallPlanMode, ai as MemoryIntent, aK as EntityTimelineEntry, j as MemoryFrontmatter, J as IdentityInjectionMode } from './types-BgChEr0M.js';
22
22
  import { LcmEngine } from './lcm/engine.js';
23
- import { S as SemanticConsolidationResult } from './semantic-consolidation-DKdYzQOg.js';
23
+ import { S as SemanticConsolidationResult } from './semantic-consolidation-BKd0Pype.js';
24
24
  import { ConversationIndexBackendInspection } from './conversation-index/backend.js';
25
25
  import { NamespaceSearchHealth } from './namespaces/search.js';
26
26
  import { SharedContextManager } from './shared-context/manager.js';
@@ -527,6 +527,459 @@ declare function locateTranscriptPath(hitPath: string): {
527
527
  date: string;
528
528
  } | null;
529
529
 
530
+ /**
531
+ * Rebuildable namespace catalog (issue #1499).
532
+ *
533
+ * Purpose: a downstream, rebuildable metadata index that lets Remnic ENUMERATE
534
+ * the configured and dynamically-created namespaces that exist or should be
535
+ * maintained. Filesystem memory remains the single source of truth; the catalog
536
+ * is derived metadata and can always be reconstructed from disk.
537
+ *
538
+ * Storage format: `<memoryDir>/state/namespaces.jsonl` — an append-and-compact
539
+ * JSON-lines log. We chose this over per-namespace sidecar files because:
540
+ * - touches (markRead/markWrite/markMaintenance) are cheap single appends;
541
+ * - it is naturally audit-friendly (the raw log preserves touch history);
542
+ * - a single file makes enumeration trivial (no directory walk per call);
543
+ * - last-record-wins compaction folds the log into the current state on read,
544
+ * and `rebuildFromDisk` rewrites it atomically (temp file + rename).
545
+ *
546
+ * SECURITY:
547
+ * - The catalog stores ONLY metadata (namespace names, kinds, timestamps,
548
+ * resolved storage dirs). It NEVER holds raw memory content or secrets.
549
+ * - Catalog presence grants NO authorization. Read/write access still flows
550
+ * through the namespace policies in `principal.ts`; this module never makes
551
+ * an access decision.
552
+ * - All namespace tokens are validated with `isSafeRouteNamespace` (except the
553
+ * configured default namespace, which is exempt at the routing layer) and
554
+ * every storage dir is contained under `<memoryDir>/namespaces`.
555
+ * - `rebuildFromDisk` rejects/reports symlinked roots that escape the memory
556
+ * root rather than trusting them.
557
+ *
558
+ * LIFECYCLE: catalog write failures must NEVER crash a primary memory op.
559
+ * Callers should wrap touch calls in try/catch (or rely on the internal
560
+ * failure-tolerant append). The internal serialized write chain recovers from
561
+ * rejection so one failed append cannot poison subsequent writes.
562
+ */
563
+ type NamespaceKind = "default" | "self" | "shared" | "project" | "branch" | "team-project" | "explicit" | "legacy";
564
+ type NamespaceDiscoverySource = "config" | "write" | "read" | "scan" | "migration";
565
+ interface NamespaceRecord {
566
+ namespace: string;
567
+ identityToken: string;
568
+ kind: NamespaceKind;
569
+ principal?: string;
570
+ projectId?: string;
571
+ branch?: string;
572
+ parentNamespace?: string;
573
+ createdAt: string;
574
+ lastReadAt?: string;
575
+ lastWriteAt?: string;
576
+ lastMaintenanceAt?: Record<string, string>;
577
+ storageDir: string;
578
+ discoveredBy: NamespaceDiscoverySource;
579
+ }
580
+ interface NamespaceCatalogFilter {
581
+ kind?: NamespaceKind;
582
+ discoveredBy?: NamespaceDiscoverySource;
583
+ /** Only include namespaces written since this instant (inclusive lower bound). */
584
+ writtenSince?: Date;
585
+ }
586
+ interface NamespaceTouchMetadata {
587
+ discoveredBy?: NamespaceDiscoverySource;
588
+ kind?: NamespaceKind;
589
+ principal?: string;
590
+ projectId?: string;
591
+ branch?: string;
592
+ parentNamespace?: string;
593
+ /** Explicit storage dir (when the caller already resolved it). */
594
+ storageDir?: string;
595
+ /** Override the touch timestamp (mainly for tests / migration replay). */
596
+ at?: Date;
597
+ }
598
+ interface NamespaceCatalogSkippedRoot {
599
+ token: string;
600
+ reason: "symlink" | "escape" | "unsafe" | "error";
601
+ detail?: string;
602
+ }
603
+ interface NamespaceCatalogRebuildResult {
604
+ dryRun: boolean;
605
+ records: NamespaceRecord[];
606
+ /** Roots reported as ambiguous/unsafe rather than silently misclassified. */
607
+ skipped: NamespaceCatalogSkippedRoot[];
608
+ /**
609
+ * Whether the rebuild actually rewrote the on-disk catalog (round 6, codex P2
610
+ * / cursor Medium — NBn3n/NBsGG). `false` for a dry-run, AND for an `--apply`
611
+ * that could NOT acquire the cross-process rebuild lock within the bounded wait
612
+ * (it ran compute-only to avoid clobbering a concurrent lock holder). Callers
613
+ * (CLI) must NOT report unqualified success when `applied` is false for a
614
+ * non-dry-run — the catalog was left unchanged and a retry is needed.
615
+ */
616
+ applied: boolean;
617
+ }
618
+ declare class NamespaceCatalog {
619
+ private readonly config;
620
+ private readonly memoryDir;
621
+ private readonly stateDir;
622
+ private readonly catalogPath;
623
+ private readonly rebuildLockPath;
624
+ private readonly lockOwnerId;
625
+ private writeChain;
626
+ protected onTouchCriticalSectionForTest?: () => Promise<void>;
627
+ protected onRebuildBeforeRenameForTest?: () => Promise<void>;
628
+ protected onRebuildAfterScanForTest?: () => Promise<void>;
629
+ protected onBeforeBreakStaleUnlinkForTest?: () => Promise<void>;
630
+ private readonly defaultNamespaceIdentity;
631
+ constructor(config: PluginConfig);
632
+ /** Whether the catalog is active (namespaces enabled and catalog not opted out). */
633
+ get enabled(): boolean;
634
+ /**
635
+ * Sanitize a record at the enumeration boundary (round 5, cursor Medium + codex
636
+ * P2; round 6 — NDXHe). Reads return whatever is in `namespaces.jsonl` after
637
+ * schema checks only, so a tampered or pre-fix row could surface unsafe data to
638
+ * maintenance/QMD until a rewrite occurs. Two distinct defenses:
639
+ *
640
+ * 1. UNSAFE NAMESPACE NAME (NGZqr, codex P2): an unsafe non-default namespace
641
+ * (e.g. `../evil`, a name with separators, or >64 chars) is REJECTED outright
642
+ * — return `null` so the caller drops it. The disk SCAN and the hot touch
643
+ * path both reject such names with the SAME default-exempt `isSafeRouteNamespace`
644
+ * gate, so the read boundary MUST agree, or `listNamespaces()`/`getNamespaceRecord()`
645
+ * would expose a namespace those paths reject (note `isStorageDirForNamespace`
646
+ * can still build a tokenized root even for `../evil`, so storageDir sanitation
647
+ * alone does not catch it). The default namespace is exempt (it may be a
648
+ * non-route literal), matching every other validation site.
649
+ *
650
+ * 2. UNSAFE storageDir: for an otherwise-valid namespace, apply the SAME contract
651
+ * as the write path — full containment (`isContainedStorageDir`: lexical +
652
+ * symlink/realpath) AND namespace ownership (`isStorageDirForNamespace`). When
653
+ * a record fails EITHER check we substitute the trusted resolved-and-safe root
654
+ * for that namespace (rule 42: read and write stay symmetric).
655
+ */
656
+ private sanitizeRecordForRead;
657
+ private storageRootOwnershipRank;
658
+ private configuredNamespaceIdentities;
659
+ private preferStorageRootOwner;
660
+ private dropDuplicateStorageRootAliases;
661
+ private loadSanitizedRecords;
662
+ listNamespaces(filter?: NamespaceCatalogFilter): Promise<NamespaceRecord[]>;
663
+ getNamespaceRecord(namespace: string): Promise<NamespaceRecord | null>;
664
+ markRead(namespace: string, metadata?: NamespaceTouchMetadata): Promise<void>;
665
+ markWrite(namespace: string, metadata?: NamespaceTouchMetadata): Promise<void>;
666
+ markMaintenance(namespace: string, jobName: string, at?: Date): Promise<void>;
667
+ /**
668
+ * Register namespaces known purely from config (default, shared, explicit
669
+ * policies). Source `config`. Cheap and idempotent.
670
+ */
671
+ registerConfiguredNamespaces(): Promise<void>;
672
+ /**
673
+ * Register a namespace whose storage was just resolved by the router. Used as
674
+ * the router's integration hook (`discoveredBy: config`). Storage dir is
675
+ * provided so we do not re-resolve it. Failure-tolerant. Returns whether the
676
+ * registration actually APPENDED (round 6, codex P2 — NEFoX), so the router's
677
+ * resolve-hook dedup only marks a namespace notified when it truly persisted —
678
+ * a dropped append (disabled catalog or rebuild-lock-timeout drop) returns
679
+ * `false` and is retried on the next resolve.
680
+ */
681
+ registerResolved(namespace: string, storageDir: string): Promise<boolean>;
682
+ /**
683
+ * Generic register/touch without changing read/write timestamps unless the
684
+ * source implies it. Validates the namespace and resolves a storage dir.
685
+ * Returns whether the touch actually appended.
686
+ */
687
+ private register;
688
+ private validateNamespace;
689
+ /**
690
+ * Resolve the on-disk storage dir for a namespace WITHOUT trusting caller
691
+ * input. The default namespace may use the legacy memoryDir root; everything
692
+ * else lives under `<memoryDir>/namespaces/<token>`. Containment is enforced
693
+ * by rejecting separators/parent-refs in the token.
694
+ */
695
+ private resolveStorageDir;
696
+ private namespaceTokenDir;
697
+ /**
698
+ * Whether a candidate storage dir is LEXICALLY contained: it is either the
699
+ * legacy default root (`memoryDir`) or a strict descendant of
700
+ * `<memoryDir>/namespaces/`. The router legitimately resolves a namespace to
701
+ * EITHER the tokenized dir or a legacy raw-name dir under `namespaces/`, so we
702
+ * accept any contained child rather than a single exact token path. This is a
703
+ * pure string check — symlink escape is checked separately via realpath.
704
+ */
705
+ private isLexicallyContained;
706
+ /**
707
+ * Whether a candidate storage dir satisfies the catalog containment contract,
708
+ * including SYMLINK-escape rejection (round 5, codex P2). A lexically-contained
709
+ * path that is actually a symlink to an outside directory would let maintenance
710
+ * or QMD follow it outside `memoryDir`. We mirror `rebuildFromDisk`'s posture:
711
+ * the path must be lexically contained AND, if it exists on disk, neither the
712
+ * path itself a symlink nor its realpath escaping the memory root. Non-existent
713
+ * paths pass the realpath stage (nothing to follow yet) but still must be
714
+ * lexically contained.
715
+ */
716
+ private isContainedStorageDir;
717
+ /**
718
+ * Reject a candidate whose path crosses a SYMLINKED ancestor strictly between
719
+ * memoryDir and the leaf (codex NVuq5). `realpath`-based containment accepts a
720
+ * symlinked `<memoryDir>/namespaces` that currently resolves back inside
721
+ * memoryDir, but the disk scanner rejects such a root and a later retarget would
722
+ * escape the memory tree — so refuse it here too. The leaf itself is
723
+ * symlink-checked by the caller; this walks only the intermediate ancestors.
724
+ */
725
+ private hasSymlinkedAncestor;
726
+ /**
727
+ * Walk up from a not-yet-existing candidate to the nearest ancestor that exists
728
+ * on disk and verify its realpath stays inside `memoryReal` (round 6, codex P2
729
+ * — NDo79). Rejects a non-existent leaf whose existing parent chain escapes
730
+ * memoryDir via a symlink. Stops at memoryDir's resolved root.
731
+ *
732
+ * The nearest existing ancestor must also be a DIRECTORY (NHIdt, codex P2): if
733
+ * an existing parent such as `<memoryDir>/namespaces` is a regular FILE (or
734
+ * socket/fifo), `realpath(parent)` still succeeds and resolves inside memoryDir,
735
+ * so a containment-only check would ACCEPT a leaf that can never be created — you
736
+ * cannot mkdir a child under a file. We `lstat` the nearest existing ancestor and
737
+ * reject when it is not a directory, mirroring the leaf non-directory rejection
738
+ * (NF21i) and the disk scan, so every containment consumer agrees.
739
+ */
740
+ private isNearestExistingAncestorContained;
741
+ /**
742
+ * Resolve the storage dir to persist for a touch, validating any caller-
743
+ * provided `metadata.storageDir` against the catalog containment contract
744
+ * (round 4 + round 5, codex P2). `markWrite`/`registerResolved` accept an
745
+ * explicit storageDir, but persisting it verbatim would let a bad hook or
746
+ * external consumer write an arbitrary path — including one outside `memoryDir`
747
+ * or a symlink that escapes it — into the catalog, handing maintenance/QMD an
748
+ * unsafe root. We accept an explicit (or previously-stored) dir ONLY when it
749
+ * stays contained under memoryDir (lexically AND via realpath); otherwise we
750
+ * drop it and fall back to the trusted resolved dir.
751
+ */
752
+ private resolveTouchStorageDir;
753
+ /**
754
+ * Whether `candidate` is a legitimate storage root FOR `namespace` (round 6,
755
+ * codex P2 — NDATT). Accepts the namespace's router-resolved root, its canonical
756
+ * lexical tokenized dir, and (for the default namespace only) memoryDir. This
757
+ * prevents a contained-but-CROSS-NAMESPACE path — another namespace's tree, or
758
+ * memoryDir for a non-default namespace — from being persisted as this
759
+ * namespace's root. Compared on resolved (absolute) paths.
760
+ */
761
+ private isStorageDirForNamespace;
762
+ /**
763
+ * Resolve the canonical storage dir for a namespace as the LIVE ROUTER would,
764
+ * but NEVER return a path that escapes the memory root.
765
+ *
766
+ * Router alignment (round 4, cursor Medium): a read/register touch with no
767
+ * explicit storageDir previously used the lexical `resolveStorageDir`, which
768
+ * always picks `<memoryDir>/namespaces/<token>` (or `memoryDir` for the
769
+ * default). That diverges from `NamespaceStorageRouter`, which can route to a
770
+ * legacy raw-name dir or a migrated default root — so a recall touch could
771
+ * record a contained-but-WRONG root that maintenance/rebuild then targets. We
772
+ * now delegate to the shared `resolveNamespaceStorageRoot` (the very helper the
773
+ * router uses) so the catalog records the same on-disk root the router serves.
774
+ *
775
+ * Containment (round 5, codex P2): the resolved path can still be a symlink
776
+ * escaping memoryDir, so we run the full (lexical + realpath) containment
777
+ * contract. When it FAILS we fall back to a NAMESPACE-SPECIFIC safe root, NOT
778
+ * a blanket `memoryDir`. Recording `memoryDir` for a non-default namespace
779
+ * would point enumeration/maintenance at the DEFAULT namespace's tree (round 5,
780
+ * cursor/codex Medium/P2) — a cross-namespace fanout error. The correct safe
781
+ * root is the namespace's own lexical tokenized dir
782
+ * (`<memoryDir>/namespaces/<token>`), which is always contained and is that
783
+ * namespace's canonical location (we record the lexical PATH as metadata; we do
784
+ * not follow the escaping symlink). Only the default namespace — or a token so
785
+ * unsafe even the lexical dir cannot be built — falls back to `memoryDir`.
786
+ */
787
+ private resolveSafeStorageDir;
788
+ /**
789
+ * The namespace-specific contained fallback root, used when the router-resolved
790
+ * root fails containment (round 5, cursor/codex Medium/P2).
791
+ *
792
+ * Preference order:
793
+ * 1. The namespace's OWN lexical tokenized dir (`namespaces/<token>`) — so a
794
+ * non-default namespace is NOT pointed at the DEFAULT namespace's `memoryDir`
795
+ * tree (which would misdirect maintenance fanout). Returned only when the
796
+ * token dir itself stays CONTAINED (it is not a symlink, and its realpath
797
+ * does not escape memoryDir — e.g. via a symlinked `namespaces/` parent).
798
+ * 2. `memoryDir` as a LAST resort — for the default namespace, an unsafe token
799
+ * that cannot build a contained path, OR the irreparable case where the
800
+ * token dir's realpath escapes the root (so even its lexical path resolves
801
+ * outside). NF21m note (codex P2): we deliberately do NOT record the lexical
802
+ * token dir in that irreparable case — its realpath escapes memoryDir, and
803
+ * the NDo79 contract REQUIRES that an escaping path is never persisted (a
804
+ * later mkdir/maintenance/QMD op would follow it outside the root). Since no
805
+ * contained namespace-specific path exists, containment wins: `memoryDir` is
806
+ * the only safe root left. A namespace whose token dir's realpath escapes is
807
+ * an irreparable on-disk state; recording the contained default root is
808
+ * strictly safer than persisting an escaping one. The common case where the
809
+ * token dir IS contained is handled by branch 1, so a healthy non-default
810
+ * namespace never reaches `memoryDir`.
811
+ */
812
+ private safeFallbackStorageDir;
813
+ /**
814
+ * Re-check, NOW, whether a namespace's storage root currently EXISTS on disk
815
+ * with the SAME safety the directory scan uses (NFJV8, codex P2).
816
+ *
817
+ * The rebuild's final re-merge runs under the held lock and folds the freshly
818
+ * re-read log (`latest`) into the scanned `rebuilt` set. A namespace present in
819
+ * `latest` (a live touch row) but ABSENT from `rebuilt` is normally PURGED as
820
+ * deleted (the NATqU "disk scan is authoritative" rule). But there is a TOCTOU
821
+ * window: a dynamic namespace can be CREATED on disk AFTER `rebuildFromDisk()`
822
+ * already enumerated `namespaces/` but BEFORE this re-merge. The scan snapshot
823
+ * missed its new root, yet a gateway `markWrite` already appended a row for it.
824
+ * Blindly purging that row would rewrite the catalog WITHOUT a live namespace
825
+ * that now has data on disk, so `writtenSince`/maintenance/QMD consumers miss
826
+ * it until another touch or rebuild.
827
+ *
828
+ * So before purging, we re-resolve the namespace's safe storage root (the same
829
+ * router-aligned, containment-checked path the scan would have catalogued) and
830
+ * confirm it is a real, contained, non-symlink directory that actually holds
831
+ * memory data RIGHT NOW. If so the namespace was created-after-scan and is LIVE
832
+ * — KEEP its row. This is the precise inverse of NATqU and does NOT reintroduce
833
+ * it: a touch on a REMOVED root re-checks as ABSENT (no data on disk) and is
834
+ * still purged; only a root that EXISTS on a fresh re-check is kept.
835
+ *
836
+ * Mirrors the per-entry scan checks (symlink rejection + realpath containment +
837
+ * `hasMemoryData`) so a symlinked/escaping root is never resurrected.
838
+ */
839
+ private liveStorageRootExistsForRebuild;
840
+ /**
841
+ * Record a namespace touch. Returns whether the touch actually APPENDED to the
842
+ * log (round 6, codex P2 — NEFoX): a disabled catalog or a dropped append (the
843
+ * NAUf7 rebuild-lock-timeout drop) returns `false`, so callers (e.g. the router
844
+ * resolve-hook dedup) can avoid marking a dropped registration as completed and
845
+ * suppressing its retry.
846
+ */
847
+ private touch;
848
+ rebuildFromDisk(options?: {
849
+ dryRun?: boolean;
850
+ }): Promise<NamespaceCatalogRebuildResult>;
851
+ /**
852
+ * Body of `rebuildFromDisk`, run inside a single `queueCritical` turn. MUST
853
+ * only be invoked from within the serialized chain so the load and the
854
+ * rewrite are atomic with respect to concurrent touches (in-process).
855
+ *
856
+ * `wantMutate` is true for an `--apply` (the caller intends to rewrite). The
857
+ * cross-process file lock is acquired LATE — only around the final
858
+ * load→merge→rename window (NFgCT, codex P2) — never across the disk scan, so a
859
+ * long scan does not force concurrent gateway touches to wait (and drop their
860
+ * append). Whether the rewrite actually happened is reported via the result's
861
+ * `applied`: true only when `wantMutate` AND the lock was acquired.
862
+ */
863
+ private rebuildInsideChain;
864
+ /**
865
+ * Final load→merge→rename window of a rebuild, factored out so the caller can
866
+ * run it WITHIN the cross-process file lock (NFgCT, codex P2) without holding
867
+ * that lock across the preceding disk scan. Re-reads the latest on-disk state,
868
+ * folds concurrent touches, then (when `canMutate`) atomically rewrites the log.
869
+ *
870
+ * `canMutate` records that the cross-process lock was actually held. The
871
+ * re-merge + rewrite run only when it is true — a dry-run, or an unlocked apply
872
+ * (lock-acquisition timeout), computes records but does NOT rename, so it can
873
+ * never clobber a concurrent lock holder's window. `applied` mirrors `canMutate`.
874
+ */
875
+ private finishRebuild;
876
+ /**
877
+ * Run `fn` while HOLDING the shared cross-process advisory lock (round 5, codex
878
+ * P2; generalized round 7 — NEZkA). This is the SINGLE mutex shared by BOTH the
879
+ * touch read→merge→append window AND the rebuild final load→merge→rename window,
880
+ * so a touch and a rebuild in different processes are mutually exclusive over
881
+ * their respective critical sections — closing the check-then-append gap where a
882
+ * polled-only touch could append into a rebuild's load→rename window.
883
+ *
884
+ * Acquisition is atomic via `open(..., "wx")`. A lock older than
885
+ * `REBUILD_LOCK_STALE_MS` is treated as a crashed holder and broken. After
886
+ * `REBUILD_LOCK_MAX_WAIT_MS` of contention we proceed best-effort WITHOUT the
887
+ * lock rather than block forever. The lock is always released in `finally`.
888
+ *
889
+ * IN-PROCESS SAFETY: every caller invokes this from inside (or wrapping) the
890
+ * per-process `queueCritical` chain, which serializes all catalog mutations in
891
+ * THIS process. So within one process only one logical holder attempts OS-lock
892
+ * acquisition at a time — the file lock is never self-contended in-process, and
893
+ * the lock is acquired and released within a single in-process turn. The file
894
+ * lock adds only the missing CROSS-process exclusion.
895
+ *
896
+ * HEARTBEAT (round 5, cursor/codex Medium/P2): while WE hold the lock a timer
897
+ * refreshes its mtime every `REBUILD_LOCK_HEARTBEAT_MS`, so a legitimately long
898
+ * holder (> `REBUILD_LOCK_STALE_MS`) is not treated as a crashed holder and
899
+ * unlinked by another process — which would let overlapping windows lose
900
+ * appends. Heartbeat failures are swallowed; the timer is always cleared in
901
+ * `finally`.
902
+ *
903
+ * ACQUISITION RESULT (round 6, codex P2 — NBPmY): `fn` receives whether WE
904
+ * actually hold the lock. When acquisition TIMED OUT (another holder is active),
905
+ * a MUTATING rebuild must NOT perform its load/rename window unlocked, and a
906
+ * touch must NOT append unlocked — both would recreate the lost-append race. The
907
+ * caller uses `acquired` to run compute-only (rebuild) or DROP the append
908
+ * (touch) when unlocked.
909
+ */
910
+ private withHeldCatalogLock;
911
+ /** Try to acquire the rebuild lock; returns true if WE created it. */
912
+ private acquireRebuildLock;
913
+ /**
914
+ * Remove the lock file if its mtime is older than the stale threshold.
915
+ *
916
+ * REPLACEMENT-SAFE (NG7Bg, codex P2): a plain `stat` → `unlink` has a TOCTOU
917
+ * window — two processes can both observe the SAME stale lock; one removes it and
918
+ * creates a FRESH lock, and the other's later `unlink` then deletes that fresh
919
+ * holder's ACTIVE lock based on the stale identity it read earlier, leaving the
920
+ * fresh holder running its critical section with no visible lock and reopening the
921
+ * lost-update race the mutex prevents. We therefore capture the lock's IDENTITY
922
+ * (its full content line: `<pid> <owner-uuid> <iso>`) when we judge it stale, then
923
+ * RE-READ immediately before unlinking and only remove it when the content is
924
+ * byte-identical AND still stale. A replacement lock has a different owner id /
925
+ * timestamp, so its content differs and we leave it untouched. We never unlink a
926
+ * lock whose mtime is now fresh (a heartbeat refreshed it) or whose identity
927
+ * changed (a replacement was created). This is best-effort: any mismatch/vanish
928
+ * simply skips the break and the caller polls again.
929
+ */
930
+ private breakStaleRebuildLock;
931
+ /**
932
+ * Whether the rebuild lock file was written by THIS instance (round 6, codex
933
+ * P2 — NBsGP). Matches the per-instance owner id, NOT just `process.pid`: two
934
+ * NamespaceCatalog instances in the same process share a PID, so a PID-only
935
+ * check would wrongly treat instance A's lock as self-held by instance B and
936
+ * let B's touch skip the wait and append into A's rebuild window. Falls back to
937
+ * the legacy PID-only form for lock files written before owner ids existed.
938
+ */
939
+ private rebuildLockHeldBySelf;
940
+ /**
941
+ * Merge a prior record's preserved metadata (timestamps, principal hints)
942
+ * onto a freshly-discovered record. Disk-derived fields (storageDir, kind)
943
+ * take precedence from the new record.
944
+ *
945
+ * PROVENANCE (round 3, cursor Low): `discoveredBy` and `createdAt` are
946
+ * CREATION-ONLY — identical to the touch path's invariant. A rebuild must NOT
947
+ * reset a namespace first seen via a `write`/`read` touch back to `config`
948
+ * just because it is also listed in policies. So when a prior record exists we
949
+ * carry its `discoveredBy` forward; only brand-new records keep the fresh
950
+ * (config/scan) provenance.
951
+ */
952
+ private mergeForRebuild;
953
+ /** Load the JSONL log and fold it into current state (last-record-wins). */
954
+ private loadCompacted;
955
+ /**
956
+ * Serialize an arbitrary read-modify-write critical section through the single
957
+ * write chain. Every catalog mutation (touch read+merge+append, full rewrite)
958
+ * runs through this so they are mutually exclusive: a touch always reads the
959
+ * latest persisted state before appending, and a rebuild rewrite cannot
960
+ * interleave with a touch's append. The chain recovers from rejection
961
+ * (CLAUDE.md rule #40) — one failed section never poisons subsequent ones —
962
+ * while still surfacing the error to that section's awaited promise.
963
+ */
964
+ private queueCritical;
965
+ /**
966
+ * Append a single record to the JSONL log WITHOUT re-serializing through the
967
+ * write chain. MUST only be called from inside a `queueCritical(...)` section
968
+ * (which already holds the serialized turn); calling it directly would bypass
969
+ * the read-before-append ordering that prevents lost-field races.
970
+ */
971
+ private appendUnchained;
972
+ /**
973
+ * Atomic temp-file + rename rewrite (CLAUDE.md rule #54: write temp, then
974
+ * rename — never delete-before-write) WITHOUT re-entering the write chain.
975
+ * MUST only be called from inside a `queueCritical(...)` turn (e.g. the
976
+ * rebuild critical section, which already holds the serialized turn so its
977
+ * load and rewrite are atomic against concurrent touches). Re-entering the
978
+ * chain from within a held turn would deadlock.
979
+ */
980
+ private rewriteUnchained;
981
+ }
982
+
530
983
  interface BulkImportBatchIngestResult {
531
984
  attemptedTurnCount: number;
532
985
  extractionCount: number;
@@ -825,6 +1278,10 @@ declare function resolvePersistedMemoryRelativePath(options: {
825
1278
  declare class Orchestrator {
826
1279
  readonly storage: StorageManager;
827
1280
  private readonly storageRouter;
1281
+ /** Rebuildable namespace catalog (issue #1499). Inert unless namespaces enabled. */
1282
+ readonly namespaceCatalog: NamespaceCatalog;
1283
+ private readonly namespaceStorageDirHints;
1284
+ private namespaceStorageDirHintsLoaded;
828
1285
  private readonly namespaceSearchRouter;
829
1286
  qmd: SearchBackend;
830
1287
  private readonly conversationQmd?;
@@ -1124,6 +1581,26 @@ declare class Orchestrator {
1124
1581
  } | null;
1125
1582
  getStorageForNamespace(namespace?: string): Promise<StorageManager>;
1126
1583
  private configuredNamespaces;
1584
+ private rememberNamespaceStorageDirHint;
1585
+ private storageDirMatchesNamespaceHint;
1586
+ private namespaceStorageDirHintOwnershipRank;
1587
+ private preferNamespaceStorageDirHintOwner;
1588
+ private loadNamespaceStorageDirHintsFromCatalog;
1589
+ /**
1590
+ * Namespaces that QMD maintenance (update/embed) must cover: the CONFIGURED set
1591
+ * PLUS every dynamic namespace recorded in the catalog (NGnei, codex P2). An
1592
+ * extraction that writes to a coding-scoped/dynamic namespace (not in
1593
+ * defaultNamespace/sharedNamespace/namespacePolicies) is only made discoverable
1594
+ * via the catalog; if maintenance embeds only `configuredNamespaces()`, that
1595
+ * namespace's QMD collection is never updated/embedded after writes and
1596
+ * recall/search stays stale or empty until it is manually configured. We union in
1597
+ * the catalog's namespaces so maintenance keeps dynamic namespaces fresh.
1598
+ * `updateNamespaces`/`embedNamespaces` already trim, dedup, and skip
1599
+ * unavailable/missing collections, so extra names are filtered safely. A catalog
1600
+ * read failure must never break maintenance — fall back to the configured set.
1601
+ */
1602
+ private maintenanceNamespaces;
1603
+ private isCatalogedMaintenanceRootLive;
1127
1604
  private buildConfiguredQmdSearchOptions;
1128
1605
  searchAcrossNamespaces(options: {
1129
1606
  query: string;
@@ -1761,8 +2238,31 @@ declare class Orchestrator {
1761
2238
  private graphSeedPathsWithinDeadline;
1762
2239
  private namespaceFromPath;
1763
2240
  private namespaceFromStorageDir;
2241
+ /**
2242
+ * Record a namespace write in the catalog (issue #1499). Best-effort and
2243
+ * failure-tolerant: a catalog write error MUST NOT crash the primary memory
2244
+ * write (CLAUDE.md gotcha #13, rule #40). Fire-and-forget by design.
2245
+ */
2246
+ private markCatalogWrite;
2247
+ /**
2248
+ * Public best-effort catalog write touch (issue #1499). User-facing explicit
2249
+ * captures (`memory_store`) and review-queue approvals persist via
2250
+ * `persistExplicitCapture()` → `storage.writeMemory()`, which bypasses the
2251
+ * extraction write path that calls `markCatalogWrite`. Without this their
2252
+ * namespaces never record `lastWriteAt`, so the catalog under-reports write
2253
+ * recency (round 5, codex P2). Fire-and-forget and failure-tolerant — a
2254
+ * catalog error must never affect the explicit write (gotcha #13, rule #40).
2255
+ *
2256
+ * An undefined/empty `namespace` means the write targeted the DEFAULT namespace
2257
+ * (`getStorage(undefined)` routes there), so we record it under the configured
2258
+ * default rather than skipping it (round 6, codex P2 — default `memory_store`
2259
+ * and inline-note writes were missing from `writtenSince`/maintenance).
2260
+ */
2261
+ recordCatalogWrite(namespace?: string, storageDir?: string): void;
2262
+ /** Record a namespace read in the catalog. Best-effort, failure-tolerant. */
2263
+ private markCatalogRead;
1764
2264
  private readAllMemoriesForNamespaces;
1765
2265
  private readArchivedMemoriesForNamespaces;
1766
2266
  }
1767
2267
 
1768
- export { computeArtifactCandidateFetchLimit as $, sanitizeSessionKeyForFilename as A, saveSpeakerRegistry as B, speakerRegistryKey as C, DEFAULT_SELF_NAME as D, speakersFilePath as E, syncWearableSource as F, type GraphRecallSnapshot as G, wearableDayTag as H, type IntentDebugSnapshot as I, wearableSourceLabel as J, writeDailyDigestMemory as K, type BulkImportBatchIngestResult as L, BulkImportBatchPartialFailureError as M, type GraphRecallRankedResult as N, Orchestrator as O, type PatternReinforcementResult as P, type GraphRecallShadowComparison as Q, type ResolvedSpeaker as R, type SpeakerRegistry as S, type QmdRecallSnapshot as T, type RecallInvocationOptions as U, type RecallModeDecision as V, WearablesService as W, appendMemoryToGraphContext as X, blendGraphExpandedRecallScore as Y, buildCompressionGuidelinesMarkdown as Z, buildMemoryPathById as _, type SpeakerOverride as a, computeArtifactRecallLimit as a0, computeQmdHybridFetchLimit as a1, dedupeEntitySynthesisEvidenceEntries as a2, deriveTopicsFromExtraction as a3, filterRecallCandidates as a4, formatCompressionGuidelinesForRecall as a5, graphPathRelativeToStorage as a6, hasIdentityRecoveryIntent as a7, isArtifactMemoryPath as a8, lifecycleRecallScoreAdjustment as a9, mergeArtifactRecallCandidates as aa, mergeGraphExpandedResults as ab, resolveEffectiveIdentityInjectionMode as ac, resolveEffectiveRecallMode as ad, resolvePersistedMemoryRelativePath as ae, resolveRecallModeDecision as af, resolveRecallModeDecisionAsync as ag, resolveRecentThreadMemoryPaths as ah, shouldFilterLifecycleRecallCandidate as ai, summarizeGraphShadowComparison as aj, WEARABLE_SOURCE_PREFIX as b, type WearableDayTranscriptView as c, type WearableMemoryGenDeps as d, type WearableMemoryGenResult as e, type WearableMemorySearchResult as f, type WearableMemoryWriter as g, type WearableSearchBackend as h, type WearableStorageIo as i, type WearableSyncDeps as j, type WearableSyncOptions as k, type WearableTranscriptSearchResult as l, type WearablesServiceDeps as m, buildExtractionTurns as n, dateInTimezone as o, defaultTimezone as p, defaultWorkspaceDir as q, distinctSpeakerLabels as r, emptySpeakerRegistry as s, generateWearableMemories as t, importNativeMemories as u, loadSpeakerRegistry as v, locateTranscriptPath as w, memoryStatusForMode as x, resolveSpeaker as y, resolveSyncDates as z };
2268
+ export { type GraphRecallShadowComparison as $, generateWearableMemories as A, importNativeMemories as B, loadSpeakerRegistry as C, DEFAULT_SELF_NAME as D, locateTranscriptPath as E, memoryStatusForMode as F, type GraphRecallSnapshot as G, resolveSpeaker as H, type IntentDebugSnapshot as I, resolveSyncDates as J, sanitizeSessionKeyForFilename as K, saveSpeakerRegistry as L, speakerRegistryKey as M, NamespaceCatalog as N, Orchestrator as O, type PatternReinforcementResult as P, speakersFilePath as Q, type ResolvedSpeaker as R, type SpeakerRegistry as S, syncWearableSource as T, wearableDayTag as U, wearableSourceLabel as V, WearablesService as W, writeDailyDigestMemory as X, type BulkImportBatchIngestResult as Y, BulkImportBatchPartialFailureError as Z, type GraphRecallRankedResult as _, type NamespaceCatalogFilter as a, type QmdRecallSnapshot as a0, type RecallInvocationOptions as a1, type RecallModeDecision as a2, appendMemoryToGraphContext as a3, blendGraphExpandedRecallScore as a4, buildCompressionGuidelinesMarkdown as a5, buildMemoryPathById as a6, computeArtifactCandidateFetchLimit as a7, computeArtifactRecallLimit as a8, computeQmdHybridFetchLimit as a9, dedupeEntitySynthesisEvidenceEntries as aa, deriveTopicsFromExtraction as ab, filterRecallCandidates as ac, formatCompressionGuidelinesForRecall as ad, graphPathRelativeToStorage as ae, hasIdentityRecoveryIntent as af, isArtifactMemoryPath as ag, lifecycleRecallScoreAdjustment as ah, mergeArtifactRecallCandidates as ai, mergeGraphExpandedResults as aj, resolveEffectiveIdentityInjectionMode as ak, resolveEffectiveRecallMode as al, resolvePersistedMemoryRelativePath as am, resolveRecallModeDecision as an, resolveRecallModeDecisionAsync as ao, resolveRecentThreadMemoryPaths as ap, shouldFilterLifecycleRecallCandidate as aq, summarizeGraphShadowComparison as ar, type NamespaceCatalogRebuildResult as b, type NamespaceCatalogSkippedRoot as c, type NamespaceDiscoverySource as d, type NamespaceKind as e, type NamespaceRecord as f, type NamespaceTouchMetadata as g, type SpeakerOverride as h, WEARABLE_SOURCE_PREFIX as i, type WearableDayTranscriptView as j, type WearableMemoryGenDeps as k, type WearableMemoryGenResult as l, type WearableMemorySearchResult as m, type WearableMemoryWriter as n, type WearableSearchBackend as o, type WearableStorageIo as p, type WearableSyncDeps as q, type WearableSyncOptions as r, type WearableTranscriptSearchResult as s, type WearablesServiceDeps as t, buildExtractionTurns as u, dateInTimezone as v, defaultTimezone as w, defaultWorkspaceDir as x, distinctSpeakerLabels as y, emptySpeakerRegistry as z };
@@ -7,7 +7,7 @@ import './summarizer.js';
7
7
  import './local-llm.js';
8
8
  import './fallback-llm.js';
9
9
  import './live-connectors-runner.js';
10
- export { L as BulkImportBatchIngestResult, M as BulkImportBatchPartialFailureError, N as GraphRecallRankedResult, Q as GraphRecallShadowComparison, G as GraphRecallSnapshot, I as IntentDebugSnapshot, O as Orchestrator, T as QmdRecallSnapshot, U as RecallInvocationOptions, V as RecallModeDecision, X as appendMemoryToGraphContext, Y as blendGraphExpandedRecallScore, Z as buildCompressionGuidelinesMarkdown, _ as buildMemoryPathById, $ as computeArtifactCandidateFetchLimit, a0 as computeArtifactRecallLimit, a1 as computeQmdHybridFetchLimit, a2 as dedupeEntitySynthesisEvidenceEntries, q as defaultWorkspaceDir, a3 as deriveTopicsFromExtraction, a4 as filterRecallCandidates, a5 as formatCompressionGuidelinesForRecall, a6 as graphPathRelativeToStorage, a7 as hasIdentityRecoveryIntent, a8 as isArtifactMemoryPath, a9 as lifecycleRecallScoreAdjustment, aa as mergeArtifactRecallCandidates, ab as mergeGraphExpandedResults, ac as resolveEffectiveIdentityInjectionMode, ad as resolveEffectiveRecallMode, ae as resolvePersistedMemoryRelativePath, af as resolveRecallModeDecision, ag as resolveRecallModeDecisionAsync, ah as resolveRecentThreadMemoryPaths, A as sanitizeSessionKeyForFilename, ai as shouldFilterLifecycleRecallCandidate, aj as summarizeGraphShadowComparison } from './orchestrator-8fTZsa0y.js';
10
+ export { Y as BulkImportBatchIngestResult, Z as BulkImportBatchPartialFailureError, _ as GraphRecallRankedResult, $ as GraphRecallShadowComparison, G as GraphRecallSnapshot, I as IntentDebugSnapshot, O as Orchestrator, a0 as QmdRecallSnapshot, a1 as RecallInvocationOptions, a2 as RecallModeDecision, a3 as appendMemoryToGraphContext, a4 as blendGraphExpandedRecallScore, a5 as buildCompressionGuidelinesMarkdown, a6 as buildMemoryPathById, a7 as computeArtifactCandidateFetchLimit, a8 as computeArtifactRecallLimit, a9 as computeQmdHybridFetchLimit, aa as dedupeEntitySynthesisEvidenceEntries, x as defaultWorkspaceDir, ab as deriveTopicsFromExtraction, ac as filterRecallCandidates, ad as formatCompressionGuidelinesForRecall, ae as graphPathRelativeToStorage, af as hasIdentityRecoveryIntent, ag as isArtifactMemoryPath, ah as lifecycleRecallScoreAdjustment, ai as mergeArtifactRecallCandidates, aj as mergeGraphExpandedResults, ak as resolveEffectiveIdentityInjectionMode, al as resolveEffectiveRecallMode, am as resolvePersistedMemoryRelativePath, an as resolveRecallModeDecision, ao as resolveRecallModeDecisionAsync, ap as resolveRecentThreadMemoryPaths, K as sanitizeSessionKeyForFilename, aq as shouldFilterLifecycleRecallCandidate, ar as summarizeGraphShadowComparison } from './orchestrator-B4Y4sWQH.js';
11
11
  import './model-registry.js';
12
12
  import './relevance.js';
13
13
  import './negative.js';
@@ -18,9 +18,9 @@ import './embedding-fallback.js';
18
18
  import './semantic-DJR8_DMQ.js';
19
19
  import './replay/types.js';
20
20
  import './types-ByK7T3L6.js';
21
- import './types-D8yUmSik.js';
21
+ import './types-BgChEr0M.js';
22
22
  import './lcm/engine.js';
23
- import './semantic-consolidation-DKdYzQOg.js';
23
+ import './semantic-consolidation-BKd0Pype.js';
24
24
  import './conversation-index/backend.js';
25
25
  import './namespaces/search.js';
26
26
  import './shared-context/manager.js';