@remnic/core 9.66.10 → 9.66.12

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 (255) hide show
  1. package/dist/access-admin-ops-surface.d.ts +1 -1
  2. package/dist/access-admin-ops-surface.js +24 -23
  3. package/dist/access-authorization-probe.d.ts +1 -1
  4. package/dist/access-authorization-probe.js +25 -24
  5. package/dist/access-boundary.d.ts +1 -1
  6. package/dist/access-boundary.js +24 -23
  7. package/dist/access-cli.js +82 -81
  8. package/dist/access-cli.js.map +1 -1
  9. package/dist/access-extraction-force-flush.d.ts +1 -1
  10. package/dist/access-extraction-force-flush.js +24 -23
  11. package/dist/access-http-lcm-compaction.d.ts +1 -1
  12. package/dist/access-http-lifecycle-flush.d.ts +1 -1
  13. package/dist/access-http-offline-stream.d.ts +1 -1
  14. package/dist/access-http-query.js +26 -25
  15. package/dist/access-http.d.ts +2 -2
  16. package/dist/access-http.js +42 -41
  17. package/dist/access-identity-continuity-surface.js +24 -23
  18. package/dist/access-lcm-surface.d.ts +1 -1
  19. package/dist/access-lcm-surface.js +24 -23
  20. package/dist/access-mcp.d.ts +1 -1
  21. package/dist/access-mcp.js +33 -32
  22. package/dist/access-namespace-preflight.js +24 -23
  23. package/dist/access-observe-write-surface.d.ts +1 -1
  24. package/dist/access-observe-write-surface.js +24 -23
  25. package/dist/access-operations-batch.js +27 -26
  26. package/dist/access-operations.d.ts +6 -6
  27. package/dist/access-operations.js +32 -31
  28. package/dist/access-recall-concurrency.d.ts +1 -1
  29. package/dist/access-recall-concurrency.js +24 -23
  30. package/dist/access-recall-response.d.ts +1 -1
  31. package/dist/access-recall-response.js +24 -23
  32. package/dist/access-recall-surface.d.ts +1 -1
  33. package/dist/access-recall-surface.js +24 -23
  34. package/dist/access-schema.d.ts +89 -89
  35. package/dist/access-schema.js +4 -4
  36. package/dist/access-service-helpers.d.ts +1 -1
  37. package/dist/access-service.d.ts +1 -1
  38. package/dist/access-service.js +24 -23
  39. package/dist/access-surface-catalog.d.ts +1 -1
  40. package/dist/access-surface-catalog.js +26 -25
  41. package/dist/access-surface-catalog.js.map +1 -1
  42. package/dist/adapters/index.js +4 -4
  43. package/dist/adapters/registry.js +2 -2
  44. package/dist/briefing.js +10 -10
  45. package/dist/buffer.js +4 -4
  46. package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-GWVG7LGC.js} +2 -2
  47. package/dist/causal-consolidation.js +12 -11
  48. package/dist/causal-consolidation.js.map +1 -1
  49. package/dist/chunk-3UXOZBHV.js +20 -0
  50. package/dist/chunk-3UXOZBHV.js.map +1 -0
  51. package/dist/{chunk-XN33JLRF.js → chunk-46XRYYOJ.js} +3 -3
  52. package/dist/{chunk-WD424KYT.js → chunk-4L7OKRGP.js} +2 -2
  53. package/dist/{chunk-ZPZLGN4Y.js → chunk-6KGBI54C.js} +5 -21
  54. package/dist/chunk-6KGBI54C.js.map +1 -0
  55. package/dist/{chunk-245QROHZ.js → chunk-6SRUPUTY.js} +7 -7
  56. package/dist/{chunk-ZIGSNLYI.js → chunk-A6HVSRGM.js} +3 -3
  57. package/dist/{chunk-JBPKEARU.js → chunk-AU7Q3LSC.js} +4 -4
  58. package/dist/{chunk-7BS5UVXA.js → chunk-AWMS3IFB.js} +3 -3
  59. package/dist/{chunk-HJQZQUPC.js → chunk-BE4Y6GK6.js} +6 -6
  60. package/dist/{chunk-D3AUQXTJ.js → chunk-BT7AQZQX.js} +2 -2
  61. package/dist/{chunk-IPLYGWQF.js → chunk-BUKK5SWA.js} +6 -6
  62. package/dist/{chunk-CEH2AFXG.js → chunk-C24UZSFW.js} +6 -6
  63. package/dist/{chunk-HCERYDRI.js → chunk-CJTZO5MW.js} +2 -2
  64. package/dist/{chunk-4QKWBVEE.js → chunk-CR73YTWC.js} +13 -13
  65. package/dist/{chunk-LTWFU6VJ.js → chunk-DDAHG5ZD.js} +2 -2
  66. package/dist/{chunk-S47XNAWD.js → chunk-EU5PDHU2.js} +2 -2
  67. package/dist/{chunk-KT3ERTF6.js → chunk-F3H7VNM4.js} +2 -2
  68. package/dist/{chunk-EG4FHU5C.js → chunk-FJP6MVRK.js} +2 -2
  69. package/dist/{chunk-7FLINMUE.js → chunk-GEHEBLVY.js} +2 -2
  70. package/dist/{chunk-KWVXCRFZ.js → chunk-GHSJBVVQ.js} +95 -55
  71. package/dist/chunk-GHSJBVVQ.js.map +1 -0
  72. package/dist/{chunk-G3B3LP6A.js → chunk-GRZYF6KK.js} +14 -14
  73. package/dist/{chunk-E2PORO7O.js → chunk-H6VYVOVP.js} +2 -2
  74. package/dist/{chunk-QKTGCL6M.js → chunk-HGRRJDUH.js} +2 -2
  75. package/dist/{chunk-J2R275FN.js → chunk-HQJM3BKH.js} +2 -2
  76. package/dist/{chunk-ZRQPX2DW.js → chunk-I3X5NWDU.js} +2 -2
  77. package/dist/{chunk-TGKLVAML.js → chunk-I7FVQMAA.js} +2 -2
  78. package/dist/{chunk-NR36NW24.js → chunk-IDPFBRPJ.js} +72 -75
  79. package/dist/{chunk-NR36NW24.js.map → chunk-IDPFBRPJ.js.map} +1 -1
  80. package/dist/{chunk-CNZVEU4X.js → chunk-IE7N2YK6.js} +2 -2
  81. package/dist/{chunk-M2BY5IZL.js → chunk-IYFL3534.js} +6 -3
  82. package/dist/chunk-IYFL3534.js.map +1 -0
  83. package/dist/{chunk-X5SDZMO7.js → chunk-JGXMA6HL.js} +2 -2
  84. package/dist/{chunk-SQEIPTDV.js → chunk-JKXPFIPC.js} +2 -2
  85. package/dist/{chunk-Y2ECPAJI.js → chunk-K5JFIA44.js} +7 -7
  86. package/dist/{chunk-JNZSNJ43.js → chunk-K7VTD5AZ.js} +2 -2
  87. package/dist/{chunk-E62M4H4M.js → chunk-KDCPIXIY.js} +2 -2
  88. package/dist/{chunk-TS37MXC7.js → chunk-KZAN4JXY.js} +2 -2
  89. package/dist/{chunk-227UFSEQ.js → chunk-LEPOP46W.js} +4 -4
  90. package/dist/{chunk-5E7KXID5.js → chunk-M3BFW3VX.js} +1 -1
  91. package/dist/{chunk-VSQG4AXL.js → chunk-MUYJQ273.js} +2 -2
  92. package/dist/{chunk-5KVMT25I.js → chunk-MWLBRFLY.js} +2 -2
  93. package/dist/{chunk-2OXBS2LV.js → chunk-NROLO2HY.js} +2 -2
  94. package/dist/{chunk-YYDOUOYM.js → chunk-O2HDDAUM.js} +17 -12
  95. package/dist/chunk-O2HDDAUM.js.map +1 -0
  96. package/dist/{chunk-RLEJULGN.js → chunk-OMA2B7GY.js} +2 -2
  97. package/dist/{chunk-TXCLDIXJ.js → chunk-OQT34EVH.js} +2 -2
  98. package/dist/{chunk-2NLLXCJG.js → chunk-OWHERGF2.js} +2 -2
  99. package/dist/{chunk-4PWANX5V.js → chunk-PKRYQ222.js} +6 -6
  100. package/dist/{chunk-DJLH3XWM.js → chunk-QMIKXIAM.js} +2 -2
  101. package/dist/{chunk-Y6KDJQLK.js → chunk-QRHKJT7F.js} +7 -7
  102. package/dist/{chunk-7MOYAT4Y.js → chunk-QXDICB4J.js} +3 -3
  103. package/dist/{chunk-KWLDB64F.js → chunk-QZPSIQRB.js} +2 -2
  104. package/dist/{chunk-54BRW3BL.js → chunk-REJCULWA.js} +2 -2
  105. package/dist/{chunk-ISX7JODR.js → chunk-SBUQ4KIE.js} +2 -2
  106. package/dist/{chunk-HPGXB56F.js → chunk-STUPJFG7.js} +2 -2
  107. package/dist/{chunk-LPSTQR26.js → chunk-SV3MDTJI.js} +2 -2
  108. package/dist/{chunk-MK7EJLB4.js → chunk-UJICQ77Y.js} +2 -2
  109. package/dist/{chunk-4PHT7KE2.js → chunk-UUGU26P2.js} +2 -2
  110. package/dist/{chunk-X7Y7WX73.js → chunk-UVYI6VIX.js} +1 -1
  111. package/dist/{chunk-QB66LBXA.js → chunk-WGVAEVT7.js} +2 -2
  112. package/dist/{chunk-2L5FSNPC.js → chunk-X2NFFBTO.js} +7 -7
  113. package/dist/{chunk-EMAEDM2J.js → chunk-YWD5UIWY.js} +2 -2
  114. package/dist/{chunk-C6TMLF3A.js → chunk-ZFE7NXB7.js} +4 -4
  115. package/dist/{cli-CFpMhqz5.d.ts → cli-Bvy4jdBu.d.ts} +1 -1
  116. package/dist/cli.d.ts +2 -2
  117. package/dist/cli.js +58 -57
  118. package/dist/coding/export-okf-codegraph.js +10 -10
  119. package/dist/compounding/engine.js +10 -10
  120. package/dist/connectors/codex-materialize-runner.js +11 -10
  121. package/dist/connectors/index.js +11 -10
  122. package/dist/convergence-refresh.js +12 -12
  123. package/dist/conversation-index/backend.js +2 -2
  124. package/dist/corpus-watermark.js +12 -12
  125. package/dist/entity-retrieval.js +10 -10
  126. package/dist/external-wiki-access.d.ts +1 -1
  127. package/dist/external-wiki-access.js +25 -24
  128. package/dist/external-wiki-mcp-tools.d.ts +1 -1
  129. package/dist/extraction-span-provenance.d.ts +28 -0
  130. package/dist/extraction-span-provenance.js +24 -0
  131. package/dist/extraction-span-provenance.js.map +1 -0
  132. package/dist/extraction.js +14 -13
  133. package/dist/harmonic-retrieval.js +1 -1
  134. package/dist/index.d.ts +700 -681
  135. package/dist/index.js +116 -109
  136. package/dist/lcm/engine.js +3 -3
  137. package/dist/lcm/index.js +6 -6
  138. package/dist/location/index.js +12 -12
  139. package/dist/maintenance/memory-governance.js +10 -10
  140. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -10
  141. package/dist/maintenance/rebuild-memory-projection.js +11 -11
  142. package/dist/mcp-memory-inspector-app.d.ts +1 -1
  143. package/dist/memory-subject-cli.d.ts +1 -1
  144. package/dist/memory-subject.d.ts +1 -1
  145. package/dist/namespaces/migrate.js +11 -11
  146. package/dist/namespaces/search.js +8 -8
  147. package/dist/namespaces/storage.js +10 -10
  148. package/dist/operator-doctor-corpus.js +13 -13
  149. package/dist/operator-toolkit.js +18 -18
  150. package/dist/orchestration/maintenance.js +18 -18
  151. package/dist/orchestrator.js +83 -82
  152. package/dist/{public-http-C9-CHc40.d.ts → public-http-2MyshWxl.d.ts} +1 -1
  153. package/dist/qmd.js +4 -4
  154. package/dist/recall-deep-rewrite.d.ts +23 -0
  155. package/dist/recall-deep-rewrite.js +29 -0
  156. package/dist/recall-deep-rewrite.js.map +1 -0
  157. package/dist/schemas.d.ts +176 -176
  158. package/dist/search/factory.js +7 -7
  159. package/dist/search/index.js +11 -11
  160. package/dist/semantic-consolidation.js +12 -11
  161. package/dist/semantic-rule-promotion.js +10 -10
  162. package/dist/semantic-rule-verifier.js +10 -10
  163. package/dist/shared-context/manager.d.ts +6 -6
  164. package/dist/source-agent-qualifier.js +13 -12
  165. package/dist/standup/build.js +10 -10
  166. package/dist/storage.js +9 -9
  167. package/dist/support-passport/index.d.ts +4 -4
  168. package/dist/support-passport/index.js +27 -26
  169. package/dist/transfer/autodetect.js +1 -1
  170. package/dist/transfer/backup.js +2 -2
  171. package/dist/transfer/capsule-export.js +3 -3
  172. package/dist/transfer/capsule-import.js +2 -2
  173. package/dist/transfer/export-okf.d.ts +8 -0
  174. package/dist/transfer/export-okf.js +12 -11
  175. package/dist/transfer/types.d.ts +78 -78
  176. package/dist/verified-recall.js +10 -10
  177. package/dist/who-knows-cli.d.ts +1 -1
  178. package/dist/who-knows-cli.js +11 -11
  179. package/dist/who-knows.d.ts +1 -1
  180. package/dist/who-knows.js +10 -10
  181. package/package.json +2 -2
  182. package/src/activity/timeline/analysis-failure.test.ts +104 -0
  183. package/src/activity/timeline/analysis-failure.ts +60 -0
  184. package/src/activity/timeline/index.ts +1 -0
  185. package/src/cli/okf-commands.ts +2 -5
  186. package/src/dedup/index.ts +7 -0
  187. package/src/dedup/merge-verdict.test.ts +74 -0
  188. package/src/dedup/merge-verdict.ts +31 -0
  189. package/src/extraction-span-provenance.test.ts +85 -0
  190. package/src/extraction-span-provenance.ts +41 -0
  191. package/src/okf/render.ts +11 -2
  192. package/src/recall-deep-rewrite.test.ts +199 -0
  193. package/src/recall-deep-rewrite.ts +50 -0
  194. package/src/transfer/export-okf.test.ts +77 -1
  195. package/src/transfer/export-okf.ts +25 -9
  196. package/dist/chunk-KWVXCRFZ.js.map +0 -1
  197. package/dist/chunk-M2BY5IZL.js.map +0 -1
  198. package/dist/chunk-YYDOUOYM.js.map +0 -1
  199. package/dist/chunk-ZPZLGN4Y.js.map +0 -1
  200. package/dist/{access-service-DcfM0Vf0.d.ts → access-service-LbwWl05g.d.ts} +64 -64
  201. /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-GWVG7LGC.js.map} +0 -0
  202. /package/dist/{chunk-XN33JLRF.js.map → chunk-46XRYYOJ.js.map} +0 -0
  203. /package/dist/{chunk-WD424KYT.js.map → chunk-4L7OKRGP.js.map} +0 -0
  204. /package/dist/{chunk-245QROHZ.js.map → chunk-6SRUPUTY.js.map} +0 -0
  205. /package/dist/{chunk-ZIGSNLYI.js.map → chunk-A6HVSRGM.js.map} +0 -0
  206. /package/dist/{chunk-JBPKEARU.js.map → chunk-AU7Q3LSC.js.map} +0 -0
  207. /package/dist/{chunk-7BS5UVXA.js.map → chunk-AWMS3IFB.js.map} +0 -0
  208. /package/dist/{chunk-HJQZQUPC.js.map → chunk-BE4Y6GK6.js.map} +0 -0
  209. /package/dist/{chunk-D3AUQXTJ.js.map → chunk-BT7AQZQX.js.map} +0 -0
  210. /package/dist/{chunk-IPLYGWQF.js.map → chunk-BUKK5SWA.js.map} +0 -0
  211. /package/dist/{chunk-CEH2AFXG.js.map → chunk-C24UZSFW.js.map} +0 -0
  212. /package/dist/{chunk-HCERYDRI.js.map → chunk-CJTZO5MW.js.map} +0 -0
  213. /package/dist/{chunk-4QKWBVEE.js.map → chunk-CR73YTWC.js.map} +0 -0
  214. /package/dist/{chunk-LTWFU6VJ.js.map → chunk-DDAHG5ZD.js.map} +0 -0
  215. /package/dist/{chunk-S47XNAWD.js.map → chunk-EU5PDHU2.js.map} +0 -0
  216. /package/dist/{chunk-KT3ERTF6.js.map → chunk-F3H7VNM4.js.map} +0 -0
  217. /package/dist/{chunk-EG4FHU5C.js.map → chunk-FJP6MVRK.js.map} +0 -0
  218. /package/dist/{chunk-7FLINMUE.js.map → chunk-GEHEBLVY.js.map} +0 -0
  219. /package/dist/{chunk-G3B3LP6A.js.map → chunk-GRZYF6KK.js.map} +0 -0
  220. /package/dist/{chunk-E2PORO7O.js.map → chunk-H6VYVOVP.js.map} +0 -0
  221. /package/dist/{chunk-QKTGCL6M.js.map → chunk-HGRRJDUH.js.map} +0 -0
  222. /package/dist/{chunk-J2R275FN.js.map → chunk-HQJM3BKH.js.map} +0 -0
  223. /package/dist/{chunk-ZRQPX2DW.js.map → chunk-I3X5NWDU.js.map} +0 -0
  224. /package/dist/{chunk-TGKLVAML.js.map → chunk-I7FVQMAA.js.map} +0 -0
  225. /package/dist/{chunk-CNZVEU4X.js.map → chunk-IE7N2YK6.js.map} +0 -0
  226. /package/dist/{chunk-X5SDZMO7.js.map → chunk-JGXMA6HL.js.map} +0 -0
  227. /package/dist/{chunk-SQEIPTDV.js.map → chunk-JKXPFIPC.js.map} +0 -0
  228. /package/dist/{chunk-Y2ECPAJI.js.map → chunk-K5JFIA44.js.map} +0 -0
  229. /package/dist/{chunk-JNZSNJ43.js.map → chunk-K7VTD5AZ.js.map} +0 -0
  230. /package/dist/{chunk-E62M4H4M.js.map → chunk-KDCPIXIY.js.map} +0 -0
  231. /package/dist/{chunk-TS37MXC7.js.map → chunk-KZAN4JXY.js.map} +0 -0
  232. /package/dist/{chunk-227UFSEQ.js.map → chunk-LEPOP46W.js.map} +0 -0
  233. /package/dist/{chunk-5E7KXID5.js.map → chunk-M3BFW3VX.js.map} +0 -0
  234. /package/dist/{chunk-VSQG4AXL.js.map → chunk-MUYJQ273.js.map} +0 -0
  235. /package/dist/{chunk-5KVMT25I.js.map → chunk-MWLBRFLY.js.map} +0 -0
  236. /package/dist/{chunk-2OXBS2LV.js.map → chunk-NROLO2HY.js.map} +0 -0
  237. /package/dist/{chunk-RLEJULGN.js.map → chunk-OMA2B7GY.js.map} +0 -0
  238. /package/dist/{chunk-TXCLDIXJ.js.map → chunk-OQT34EVH.js.map} +0 -0
  239. /package/dist/{chunk-2NLLXCJG.js.map → chunk-OWHERGF2.js.map} +0 -0
  240. /package/dist/{chunk-4PWANX5V.js.map → chunk-PKRYQ222.js.map} +0 -0
  241. /package/dist/{chunk-DJLH3XWM.js.map → chunk-QMIKXIAM.js.map} +0 -0
  242. /package/dist/{chunk-Y6KDJQLK.js.map → chunk-QRHKJT7F.js.map} +0 -0
  243. /package/dist/{chunk-7MOYAT4Y.js.map → chunk-QXDICB4J.js.map} +0 -0
  244. /package/dist/{chunk-KWLDB64F.js.map → chunk-QZPSIQRB.js.map} +0 -0
  245. /package/dist/{chunk-54BRW3BL.js.map → chunk-REJCULWA.js.map} +0 -0
  246. /package/dist/{chunk-ISX7JODR.js.map → chunk-SBUQ4KIE.js.map} +0 -0
  247. /package/dist/{chunk-HPGXB56F.js.map → chunk-STUPJFG7.js.map} +0 -0
  248. /package/dist/{chunk-LPSTQR26.js.map → chunk-SV3MDTJI.js.map} +0 -0
  249. /package/dist/{chunk-MK7EJLB4.js.map → chunk-UJICQ77Y.js.map} +0 -0
  250. /package/dist/{chunk-4PHT7KE2.js.map → chunk-UUGU26P2.js.map} +0 -0
  251. /package/dist/{chunk-X7Y7WX73.js.map → chunk-UVYI6VIX.js.map} +0 -0
  252. /package/dist/{chunk-QB66LBXA.js.map → chunk-WGVAEVT7.js.map} +0 -0
  253. /package/dist/{chunk-2L5FSNPC.js.map → chunk-X2NFFBTO.js.map} +0 -0
  254. /package/dist/{chunk-EMAEDM2J.js.map → chunk-YWD5UIWY.js.map} +0 -0
  255. /package/dist/{chunk-C6TMLF3A.js.map → chunk-ZFE7NXB7.js.map} +0 -0
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Validate a deep-recall REFINE rewrite (issue #2332).
3
+ *
4
+ * Pure. The budget check runs before the rewrite is inspected, so an
5
+ * exhausted budget cannot be bypassed by a valid-looking query. An empty
6
+ * or no-progress rewrite is rejected and treated as STOP.
7
+ */
8
+ export const MAX_REFINES_PER_INVOCATION = 2;
9
+
10
+ export type RefineRewriteResult =
11
+ | { ok: true; refinedQuery: string }
12
+ | {
13
+ ok: false;
14
+ stop: true;
15
+ reason: "empty_rewrite" | "identical_rewrite" | "refine_budget_spent";
16
+ };
17
+
18
+ export function validateRefineRewrite(input: {
19
+ currentQuery: string;
20
+ refinedQuery: unknown;
21
+ refinesUsed: number;
22
+ }): RefineRewriteResult {
23
+ if (!Number.isInteger(input.refinesUsed) || input.refinesUsed < 0) {
24
+ throw new RangeError(
25
+ `refinesUsed must be a non-negative integer; got ${JSON.stringify(input.refinesUsed)}`,
26
+ );
27
+ }
28
+ if (input.refinesUsed >= MAX_REFINES_PER_INVOCATION) {
29
+ return { ok: false, stop: true, reason: "refine_budget_spent" };
30
+ }
31
+ if (
32
+ typeof input.refinedQuery !== "string" ||
33
+ input.refinedQuery.trim() === ""
34
+ ) {
35
+ return { ok: false, stop: true, reason: "empty_rewrite" };
36
+ }
37
+ const trimmed = input.refinedQuery.trim();
38
+ // Comparison key: case-folded with internal whitespace runs collapsed, so
39
+ // pure case/spacing drift is not progress. The returned rewrite keeps
40
+ // internal whitespace as the model wrote it.
41
+ const currentKey = input.currentQuery
42
+ .trim()
43
+ .toLowerCase()
44
+ .replace(/\s+/g, " ");
45
+ const refinedKey = trimmed.toLowerCase().replace(/\s+/g, " ");
46
+ if (refinedKey === currentKey) {
47
+ return { ok: false, stop: true, reason: "identical_rewrite" };
48
+ }
49
+ return { ok: true, refinedQuery: trimmed };
50
+ }
@@ -1,5 +1,5 @@
1
1
  import assert from "node:assert/strict";
2
- import { mkdtemp, readFile, rm, writeFile, symlink } from "node:fs/promises";
2
+ import { mkdir, mkdtemp, readFile, rm, writeFile, symlink } from "node:fs/promises";
3
3
  import { existsSync, lstatSync, readdirSync } from "node:fs";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
@@ -162,3 +162,79 @@ function collectFiles(root: string): string[] {
162
162
  walk(root);
163
163
  return out.sort();
164
164
  }
165
+
166
+ test("--namespace cannot escape the namespace root", async () => {
167
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-okf-ns-"));
168
+ const outDir = path.join(await mkdtemp(path.join(os.tmpdir(), "remnic-okf-nsout-")), "bundle");
169
+ try {
170
+ await seedStore(memoryDir);
171
+ // A raw path.join on the operator value used to resolve outside
172
+ // <memoryDir>/namespaces and export an arbitrary tree.
173
+ for (const namespace of ["../../..", "../sibling", path.resolve(memoryDir)]) {
174
+ await assert.rejects(
175
+ () => exportOkfBundle({ memoryDir, namespace, outDir }),
176
+ /invalid --namespace/,
177
+ `namespace ${namespace} must be rejected`,
178
+ );
179
+ }
180
+ assert.equal(existsSync(outDir), false, "a rejected namespace must not create the bundle");
181
+ } finally {
182
+ await rm(memoryDir, { recursive: true, force: true });
183
+ }
184
+ });
185
+
186
+ test("a plain --namespace still exports from the namespace subtree", async () => {
187
+ const root = await mkdtemp(path.join(os.tmpdir(), "remnic-okf-nsok-"));
188
+ const outDir = path.join(root, "bundle");
189
+ try {
190
+ await seedStore(path.join(root, "namespaces", "team-a"));
191
+ const result = await exportOkfBundle({ memoryDir: root, namespace: "team-a", outDir });
192
+ assert.ok(result.exported > 0, "namespaced export must find its own memories");
193
+ assert.ok(existsSync(path.join(outDir, "index.md")));
194
+ } finally {
195
+ await rm(root, { recursive: true, force: true });
196
+ }
197
+ });
198
+
199
+ test("a leftover backup directory does not block a forced export", async () => {
200
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-okf-bk-"));
201
+ const root = await mkdtemp(path.join(os.tmpdir(), "remnic-okf-bkout-"));
202
+ const outDir = path.join(root, "bundle");
203
+ try {
204
+ await seedStore(memoryDir);
205
+ await exportOkfBundle({ memoryDir, outDir });
206
+ // Simulate a crash between the two publish renames, or any directory a
207
+ // user happens to own at the old fixed backup path.
208
+ const stale = `${outDir}.okf-prev`;
209
+ await mkdir(stale, { recursive: true });
210
+ await writeFile(path.join(stale, "keep.md"), "stale\n", "utf8");
211
+
212
+ const again = await exportOkfBundle({ memoryDir, outDir, force: true });
213
+ assert.ok(again.exported > 0);
214
+ assert.ok(existsSync(path.join(outDir, "index.md")), "forced export must republish");
215
+ assert.equal(existsSync(path.join(stale, "keep.md")), true, "an unrelated directory is left alone");
216
+ const leftovers = readdirSync(root).filter((name) => name.startsWith("bundle.okf-prev-"));
217
+ assert.deepEqual(leftovers, [], "each attempt removes its own backup");
218
+ } finally {
219
+ await rm(memoryDir, { recursive: true, force: true });
220
+ await rm(root, { recursive: true, force: true });
221
+ }
222
+ });
223
+
224
+ test("--out that is a file is rejected before anything is written", async () => {
225
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-okf-file-"));
226
+ const root = await mkdtemp(path.join(os.tmpdir(), "remnic-okf-fileout-"));
227
+ const outDir = path.join(root, "bundle");
228
+ try {
229
+ await seedStore(memoryDir);
230
+ await writeFile(outDir, "not a directory\n", "utf8");
231
+ await assert.rejects(
232
+ () => exportOkfBundle({ memoryDir, outDir, force: true }),
233
+ /--out exists and is not a directory/,
234
+ );
235
+ assert.equal(await readFile(outDir, "utf8"), "not a directory\n", "the file is left untouched");
236
+ } finally {
237
+ await rm(memoryDir, { recursive: true, force: true });
238
+ await rm(root, { recursive: true, force: true });
239
+ }
240
+ });
@@ -1,6 +1,5 @@
1
- import { rmSync } from "node:fs";
1
+ import { mkdirSync, rmSync } from "node:fs";
2
2
  import { mkdtemp } from "node:fs/promises";
3
- import os from "node:os";
4
3
  import path from "node:path";
5
4
 
6
5
  import { parseOaiMemCitation } from "../citations.js";
@@ -8,6 +7,7 @@ import { inferMemoryStatus } from "../memory-lifecycle-ledger-utils.js";
8
7
  import { normalizeProjectionPreview } from "../memory-projection-format.js";
9
8
  import { lintOkfDir } from "../okf/lint.js";
10
9
  import { okfTypeForMemory, OKF_PROFILE_TYPE } from "../okf/type-mapping.js";
10
+ import { resolveNamespaceChildRoot } from "../namespaces/path.js";
11
11
  import {
12
12
  OKF_EXPORT_VERSION,
13
13
  publishBundle,
@@ -38,6 +38,14 @@ const MEMORY_STATUSES: readonly MemoryStatus[] = [
38
38
 
39
39
  export interface ExportOkfOptions {
40
40
  memoryDir: string;
41
+ /**
42
+ * Optional namespace segment. Resolved here, once, through the shared
43
+ * containment guard: both CLI entry points used to join the raw operator
44
+ * value onto `memoryDir/namespaces`, so `--namespace ../../..` escaped the
45
+ * namespace root and exported an arbitrary tree (rule 9 — one resolver, not
46
+ * a per-caller guard).
47
+ */
48
+ namespace?: string;
41
49
  outDir: string;
42
50
  includeStatus?: readonly string[];
43
51
  includeCategories?: readonly string[];
@@ -71,15 +79,19 @@ export function parseIncludeStatus(raw: unknown): MemoryStatus[] {
71
79
  export async function exportOkfBundle(opts: ExportOkfOptions): Promise<ExportOkfResult> {
72
80
  const outDir = path.resolve(opts.outDir);
73
81
  rejectSymlinkPath(outDir);
82
+ const namespace = (opts.namespace ?? "").trim();
83
+ const memoryDir = namespace
84
+ ? resolveNamespaceChildRoot(opts.memoryDir, namespace, "--namespace")
85
+ : opts.memoryDir;
74
86
  const includeStatus = new Set(parseIncludeStatus(opts.includeStatus));
75
87
  const includeCategories = opts.includeCategories?.length ? new Set(opts.includeCategories) : null;
76
88
  const excludeTags = new Set(opts.excludeTags ?? []);
77
- const storage = new StorageManager(opts.memoryDir);
89
+ const storage = new StorageManager(memoryDir);
78
90
  const memories = await storage.readAllMemories();
79
91
  const included: MemoryFile[] = [];
80
92
  let excluded = 0;
81
93
  for (const memory of memories) {
82
- const rel = toRel(memory.path, opts.memoryDir);
94
+ const rel = toRel(memory.path, memoryDir);
83
95
  if (!opts.includeWearables && rel.startsWith("wearables/")) {
84
96
  excluded += 1;
85
97
  continue;
@@ -99,17 +111,21 @@ export async function exportOkfBundle(opts: ExportOkfOptions): Promise<ExportOkf
99
111
  }
100
112
  included.push(memory);
101
113
  }
102
- included.sort((a, b) => toRel(a.path, opts.memoryDir).localeCompare(toRel(b.path, opts.memoryDir)));
114
+ included.sort((a, b) => toRel(a.path, memoryDir).localeCompare(toRel(b.path, memoryDir)));
103
115
 
104
- const staging = await mkdtemp(path.join(os.tmpdir(), "remnic-okf-export-"));
116
+ // Stage inside the destination's parent, not the OS temp dir: publishBundle
117
+ // finishes with a rename, and a rename across filesystems fails with EXDEV
118
+ // whenever --out lives on a different mount than /tmp.
119
+ mkdirSync(path.dirname(outDir), { recursive: true });
120
+ const staging = await mkdtemp(path.join(path.dirname(outDir), ".remnic-okf-export-"));
105
121
  const byCategory: Record<string, number> = {};
106
122
  const idToRel = new Map<string, string>();
107
123
  for (const memory of included) {
108
- const rel = toRel(memory.path, opts.memoryDir);
124
+ const rel = toRel(memory.path, memoryDir);
109
125
  idToRel.set(memory.frontmatter.id, rel);
110
126
  }
111
127
  for (const memory of included) {
112
- const rel = toRel(memory.path, opts.memoryDir);
128
+ const rel = toRel(memory.path, memoryDir);
113
129
  const rendered = renderMemory(memory, rel, idToRel);
114
130
  writeBundleFile(staging, rel, rendered);
115
131
  const category = memory.frontmatter.category;
@@ -140,7 +156,7 @@ export async function exportOkfBundle(opts: ExportOkfOptions): Promise<ExportOkf
140
156
  writeBundleFile(staging, "log.md", renderLog(events, idToRel, opts.logMaxEntries ?? DEFAULT_OKF_LOG_MAX_ENTRIES));
141
157
  }
142
158
 
143
- writeIndexes(staging, included, opts.memoryDir);
159
+ writeIndexes(staging, included, memoryDir);
144
160
  const lint = lintOkfDir(staging);
145
161
  const blocking = lint.findings.filter((f) => f.code !== "skipped_encrypted" && f.code !== "reserved_basename");
146
162
  if (blocking.length > 0) {