@remnic/core 10.0.91 → 10.0.93

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 (522) hide show
  1. package/dist/access-admin-ops-surface.js +334 -61
  2. package/dist/access-authorization-probe.js +2 -283
  3. package/dist/access-boundary.d.ts +1 -1
  4. package/dist/access-boundary.d.ts.map +1 -1
  5. package/dist/access-boundary.js +1 -282
  6. package/dist/access-cli.js +288 -304
  7. package/dist/access-cli.js.map +1 -1
  8. package/dist/access-coding-context-resolution.js +3 -3
  9. package/dist/access-extraction-force-flush.js +334 -61
  10. package/dist/access-http-briefing.js +334 -62
  11. package/dist/access-http-query.js +334 -63
  12. package/dist/access-http.js +332 -114
  13. package/dist/access-identity-continuity-surface.js +334 -61
  14. package/dist/access-lcm-surface.js +334 -61
  15. package/dist/access-mcp-frames.d.ts +20 -6
  16. package/dist/access-mcp-frames.d.ts.map +1 -1
  17. package/dist/access-mcp-frames.js +1 -1
  18. package/dist/access-mcp.d.ts +5 -0
  19. package/dist/access-mcp.d.ts.map +1 -1
  20. package/dist/access-mcp.js +332 -89
  21. package/dist/access-namespace-preflight.js +2 -282
  22. package/dist/access-observe-transcript.js +3 -3
  23. package/dist/access-observe-write-category.js +6 -6
  24. package/dist/access-observe-write-surface.js +334 -61
  25. package/dist/access-offline-manifest.js +12 -11
  26. package/dist/access-operations-batch.js +332 -66
  27. package/dist/access-operations.js +332 -75
  28. package/dist/access-recall-concurrency.js +334 -61
  29. package/dist/access-recall-diagnostics.js +6 -5
  30. package/dist/access-recall-response.js +334 -61
  31. package/dist/access-recall-surface.js +334 -61
  32. package/dist/access-schema.js +2 -2
  33. package/dist/access-service.d.ts +1 -0
  34. package/dist/access-service.d.ts.map +1 -1
  35. package/dist/access-service.js +337 -64
  36. package/dist/access-surface-catalog.js +6 -283
  37. package/dist/access-surface-catalog.js.map +1 -1
  38. package/dist/active-context-transform.js +1 -1
  39. package/dist/active-memory-bridge.js +3 -2
  40. package/dist/active-recall.js +36 -37
  41. package/dist/active-recall.js.map +1 -1
  42. package/dist/activity/journal-extract.d.ts +13 -1
  43. package/dist/activity/journal-extract.d.ts.map +1 -1
  44. package/dist/{auto-sync-3AZW7MXI.js → auto-sync-BO5YZKM6.js} +13 -15
  45. package/dist/{auto-sync-3AZW7MXI.js.map → auto-sync-BO5YZKM6.js.map} +1 -1
  46. package/dist/briefing.js +20 -20
  47. package/dist/buffer.js +16 -16
  48. package/dist/calibration.js +10 -10
  49. package/dist/{capsule-merge-2IIXFXN5.js → capsule-merge-6IDAYTKG.js} +6 -6
  50. package/dist/causal-consolidation.js +23 -23
  51. package/dist/{chunk-QL7RHYK6.js → chunk-2LMA22UU.js} +5 -5
  52. package/dist/{chunk-RGUUBAT5.js → chunk-2WCLTWV2.js} +3 -3
  53. package/dist/{chunk-RSK2WIEM.js → chunk-2YODOG2O.js} +2 -2
  54. package/dist/{chunk-WYG5UNDY.js → chunk-3AUDELZL.js} +2 -2
  55. package/dist/{chunk-OFVCFKDH.js → chunk-3CYOCE6U.js} +3 -3
  56. package/dist/{chunk-6HM6HARE.js → chunk-3FBAEPCU.js} +2 -2
  57. package/dist/{chunk-PIITB2YZ.js → chunk-3ZS3NUEI.js} +4 -4
  58. package/dist/{chunk-V4XE263I.js → chunk-43AA5FVD.js} +1 -1
  59. package/dist/chunk-43AA5FVD.js.map +1 -0
  60. package/dist/{chunk-ANZZLRUX.js → chunk-4IVRQMZW.js} +2 -2
  61. package/dist/{chunk-63E37JCN.js → chunk-4RAJT5L2.js} +4 -4
  62. package/dist/{chunk-45VUHDV2.js → chunk-5CO5G4MN.js} +2 -2
  63. package/dist/{chunk-PUEO37QG.js → chunk-5YT2IIFX.js} +729 -17
  64. package/dist/chunk-5YT2IIFX.js.map +1 -0
  65. package/dist/{chunk-P5VZSSYL.js → chunk-622V2AZA.js} +233 -40
  66. package/dist/chunk-622V2AZA.js.map +1 -0
  67. package/dist/{chunk-3UMDRGFS.js → chunk-65HWAQ4F.js} +3 -3
  68. package/dist/{chunk-T67LUYCR.js → chunk-77NTFICT.js} +5 -5
  69. package/dist/chunk-A2WFUMUN.js +86 -0
  70. package/dist/chunk-A2WFUMUN.js.map +1 -0
  71. package/dist/{chunk-UE6RRXMF.js → chunk-A4F7OIC5.js} +2 -2
  72. package/dist/chunk-AHA5VQGP.js +117 -0
  73. package/dist/chunk-AHA5VQGP.js.map +1 -0
  74. package/dist/{chunk-AOFEMUHG.js → chunk-ATG2PQ4V.js} +56 -5
  75. package/dist/chunk-ATG2PQ4V.js.map +1 -0
  76. package/dist/{chunk-63O7LVMN.js → chunk-B33YWUJV.js} +3 -3
  77. package/dist/{chunk-QVMOCJRV.js → chunk-B377OKUP.js} +2 -2
  78. package/dist/{chunk-IUKYFTGW.js → chunk-B3DR2JBZ.js} +140 -125
  79. package/dist/chunk-B3DR2JBZ.js.map +1 -0
  80. package/dist/{chunk-ZT6WPSII.js → chunk-BGHCSLE5.js} +2 -2
  81. package/dist/{chunk-YYOMGOHB.js → chunk-BOK6BN5M.js} +2 -2
  82. package/dist/{chunk-OH7QLI6W.js → chunk-BPPUH5JM.js} +5 -5
  83. package/dist/{chunk-EV76IZ37.js → chunk-BTI4C4MA.js} +2 -2
  84. package/dist/{chunk-O6ZSWHVR.js → chunk-C2KE5DMD.js} +4 -4
  85. package/dist/{chunk-47F6WWLQ.js → chunk-C73N66PR.js} +2 -2
  86. package/dist/{chunk-3XIWW4TB.js → chunk-CGJYL633.js} +2 -2
  87. package/dist/{chunk-WRT7GJ2D.js → chunk-CIWMTNHW.js} +4 -4
  88. package/dist/{chunk-O55EKIEC.js → chunk-D3ZKHLN2.js} +2 -2
  89. package/dist/{chunk-CD4BCCNP.js → chunk-DJ7WVEWX.js} +2 -2
  90. package/dist/{chunk-RO7RUBLD.js → chunk-EFL24KOI.js} +2 -2
  91. package/dist/{chunk-4CJTQHEG.js → chunk-ESLOLCD4.js} +3 -3
  92. package/dist/{chunk-Z4Q6USKJ.js → chunk-ETBVCRVL.js} +4 -4
  93. package/dist/{chunk-VILPVT4S.js → chunk-EZ47HOJ6.js} +4 -4
  94. package/dist/{chunk-EFYN5I7Z.js → chunk-F4QFU5IZ.js} +2 -2
  95. package/dist/{chunk-T6OZW2WR.js → chunk-FA7SLQ5X.js} +7 -1
  96. package/dist/chunk-FA7SLQ5X.js.map +1 -0
  97. package/dist/{chunk-SHUBTAY2.js → chunk-FMKILENU.js} +14 -14
  98. package/dist/{chunk-YLXETSAH.js → chunk-GCZJ7XRI.js} +2 -2
  99. package/dist/{chunk-J4MMPQ7Q.js → chunk-GQXOBKPL.js} +7 -9
  100. package/dist/{chunk-J4MMPQ7Q.js.map → chunk-GQXOBKPL.js.map} +1 -1
  101. package/dist/{chunk-UDKWFDV2.js → chunk-HLUHEMN4.js} +27 -3
  102. package/dist/chunk-HLUHEMN4.js.map +1 -0
  103. package/dist/{chunk-QFEDITXM.js → chunk-JNDMIAJ4.js} +2 -2
  104. package/dist/chunk-JTSSSR4L.js +359 -0
  105. package/dist/chunk-JTSSSR4L.js.map +1 -0
  106. package/dist/{chunk-5KBIOI4G.js → chunk-JVBAPAWZ.js} +217 -13
  107. package/dist/chunk-JVBAPAWZ.js.map +1 -0
  108. package/dist/{chunk-E5PPE4VA.js → chunk-K2VRKCPA.js} +2 -2
  109. package/dist/{chunk-MKRYQROF.js → chunk-LGEHAQHK.js} +4 -4
  110. package/dist/{chunk-7HTUYFR3.js → chunk-LLPMMMWG.js} +27 -10
  111. package/dist/chunk-LLPMMMWG.js.map +1 -0
  112. package/dist/{chunk-XYTQJD7F.js → chunk-LMJVP5CM.js} +2 -2
  113. package/dist/{chunk-JC36FPQ5.js → chunk-LPV72KRQ.js} +2 -2
  114. package/dist/{chunk-2LCRG2YV.js → chunk-M2XR6B6V.js} +2 -2
  115. package/dist/{chunk-GGRRF4PZ.js → chunk-MDPT6QFK.js} +1 -1
  116. package/dist/{chunk-GGRRF4PZ.js.map → chunk-MDPT6QFK.js.map} +1 -1
  117. package/dist/{chunk-VPQFK63H.js → chunk-MH632KPO.js} +2 -2
  118. package/dist/{chunk-EQ4U73UH.js → chunk-MLAMXQAW.js} +14 -2
  119. package/dist/chunk-MLAMXQAW.js.map +1 -0
  120. package/dist/{chunk-ZU5JDLOH.js → chunk-MMRTPH3J.js} +2 -2
  121. package/dist/{chunk-ZZ67UTQX.js → chunk-MUX7IL77.js} +2 -2
  122. package/dist/{chunk-RYWROSL5.js → chunk-NAQ4A5IX.js} +7 -7
  123. package/dist/{chunk-TVIVY7KU.js → chunk-NED7IR7B.js} +4 -4
  124. package/dist/{chunk-ADYP7HXE.js → chunk-NHVAX7MH.js} +3 -3
  125. package/dist/{chunk-TG3R2SID.js → chunk-NMGHGTVR.js} +5 -5
  126. package/dist/{chunk-YCNIGRRF.js → chunk-NS7U4ZYU.js} +13 -117
  127. package/dist/chunk-NS7U4ZYU.js.map +1 -0
  128. package/dist/{chunk-NGHDW6UU.js → chunk-NUQEREIP.js} +2 -2
  129. package/dist/{chunk-5IIQU4JZ.js → chunk-OB72KNMF.js} +4 -4
  130. package/dist/{chunk-7L3TODI7.js → chunk-OJXJWNFY.js} +2 -2
  131. package/dist/{chunk-66L5ZRJC.js → chunk-P3WRNWWM.js} +157 -4
  132. package/dist/chunk-P3WRNWWM.js.map +1 -0
  133. package/dist/{chunk-E6M225BJ.js → chunk-PC5DRBGD.js} +18 -8
  134. package/dist/chunk-PC5DRBGD.js.map +1 -0
  135. package/dist/{chunk-EURPSF4Y.js → chunk-PH5ZQM35.js} +2 -2
  136. package/dist/{chunk-BMWMTAQF.js → chunk-PRAUN6PN.js} +3 -3
  137. package/dist/{chunk-RWGRSKN5.js → chunk-PT4DZDJA.js} +2 -2
  138. package/dist/{chunk-7EPY3IQL.js → chunk-PZJQ46DH.js} +2 -2
  139. package/dist/{chunk-AH637HZ5.js → chunk-QDESY76F.js} +3 -3
  140. package/dist/{chunk-I6QGI65T.js → chunk-QRMG4HK4.js} +3 -3
  141. package/dist/{chunk-ZU3NJNO7.js → chunk-QSF5SJAM.js} +4 -4
  142. package/dist/{chunk-5IYOKDM4.js → chunk-R23JWJD3.js} +5 -5
  143. package/dist/{chunk-EWJZZDRG.js → chunk-R4BBGPI2.js} +219 -219
  144. package/dist/chunk-R4BBGPI2.js.map +1 -0
  145. package/dist/{chunk-LJQFJF4J.js → chunk-RM2YUFH6.js} +6 -6
  146. package/dist/{chunk-IPT2OY4B.js → chunk-RQFXZYTF.js} +35 -33
  147. package/dist/chunk-RQFXZYTF.js.map +1 -0
  148. package/dist/{chunk-AP6NCP3V.js → chunk-S3S72HRM.js} +8 -6
  149. package/dist/chunk-S3S72HRM.js.map +1 -0
  150. package/dist/{chunk-6QMWECO5.js → chunk-SGQPR5SX.js} +7 -7
  151. package/dist/{chunk-ZGFLIK5H.js → chunk-SPAKBUJY.js} +2 -2
  152. package/dist/{chunk-ER7XANP3.js → chunk-SQ3GEIEL.js} +4 -4
  153. package/dist/{chunk-5UCMYPVF.js → chunk-SVT5IA2H.js} +2 -2
  154. package/dist/{chunk-HVKUCT3W.js → chunk-T4KY6UYF.js} +5 -3
  155. package/dist/{chunk-HVKUCT3W.js.map → chunk-T4KY6UYF.js.map} +1 -1
  156. package/dist/{chunk-FRSHF6FU.js → chunk-TJ4F6H4H.js} +3 -3
  157. package/dist/{chunk-R6HMAIKM.js → chunk-TOSPNM4T.js} +2 -2
  158. package/dist/{chunk-NJJMVS5Y.js → chunk-TRQE3RYR.js} +5 -5
  159. package/dist/{chunk-RMXS4I66.js → chunk-TVDJEUV5.js} +2 -2
  160. package/dist/{chunk-PLCYKAWH.js → chunk-TYRWSE4B.js} +2 -2
  161. package/dist/{chunk-YS2GHSWN.js → chunk-UBEKET2V.js} +389 -39
  162. package/dist/chunk-UBEKET2V.js.map +1 -0
  163. package/dist/{chunk-IQWUACVG.js → chunk-UHMHJX5G.js} +3 -3
  164. package/dist/{chunk-YODUDA6R.js → chunk-UN4RVUG2.js} +22 -22
  165. package/dist/{chunk-KZSGBWOJ.js → chunk-V2KGDESV.js} +2 -2
  166. package/dist/{chunk-E6Z4FBX6.js → chunk-V2QSNFCT.js} +2 -2
  167. package/dist/{chunk-BCCG7VWM.js → chunk-V65H7MI6.js} +396 -13
  168. package/dist/chunk-V65H7MI6.js.map +1 -0
  169. package/dist/chunk-W54Z2MOP.js +4500 -0
  170. package/dist/chunk-W54Z2MOP.js.map +1 -0
  171. package/dist/{chunk-N74SGESS.js → chunk-WKSQTMU7.js} +2 -2
  172. package/dist/{chunk-RIZ5NUNN.js → chunk-WQ4XKIOX.js} +4 -4
  173. package/dist/{chunk-WWK6J6SE.js → chunk-WQURALGV.js} +4996 -2748
  174. package/dist/chunk-WQURALGV.js.map +1 -0
  175. package/dist/{chunk-QEYTRFR3.js → chunk-X26YOFGS.js} +2 -2
  176. package/dist/{chunk-ITHZFHST.js → chunk-X2BVB74H.js} +2 -2
  177. package/dist/{chunk-GNXGJBCA.js → chunk-XIYLWEXC.js} +2 -2
  178. package/dist/{chunk-IBJ6U753.js → chunk-XKZBZOKV.js} +4 -4
  179. package/dist/{chunk-7O2MC42R.js → chunk-XNRLRNQF.js} +2 -2
  180. package/dist/{chunk-65AUYLU3.js → chunk-XPXHQI6N.js} +2 -2
  181. package/dist/{chunk-JBCCWVU2.js → chunk-XRDYRWZI.js} +2 -2
  182. package/dist/chunk-XRWWCW4H.js +102 -0
  183. package/dist/chunk-XRWWCW4H.js.map +1 -0
  184. package/dist/chunk-YKJBSMMP.js +67137 -0
  185. package/dist/chunk-YKJBSMMP.js.map +1 -0
  186. package/dist/{chunk-2JSDPUKD.js → chunk-YMFPFXEX.js} +2 -2
  187. package/dist/{chunk-645WOU5I.js → chunk-ZDZJNXL6.js} +4 -4
  188. package/dist/{chunk-6TCXIRP5.js → chunk-ZKE4CO6B.js} +4 -4
  189. package/dist/{chunk-KEPXC4KN.js → chunk-ZSE774RV.js} +2 -2
  190. package/dist/cli.d.ts.map +1 -1
  191. package/dist/cli.js +305 -187
  192. package/dist/coding/export-okf-codegraph.js +29 -29
  193. package/dist/compounding/engine.js +23 -24
  194. package/dist/config.d.ts.map +1 -1
  195. package/dist/config.js +36 -37
  196. package/dist/connectors/codex-materialize-runner.js +20 -20
  197. package/dist/connectors/index.js +21 -21
  198. package/dist/connectors/live/framework.d.ts +7 -0
  199. package/dist/connectors/live/framework.d.ts.map +1 -1
  200. package/dist/connectors/live/google-drive.d.ts +17 -3
  201. package/dist/connectors/live/google-drive.d.ts.map +1 -1
  202. package/dist/consolidation-provenance-check.js +4 -4
  203. package/dist/consolidation-undo.js +5 -5
  204. package/dist/contradiction/index.js +7 -6
  205. package/dist/{contradiction-scan-P2MHYFVG.js → contradiction-scan-IBPYGQZF.js} +4 -3
  206. package/dist/convergence-refresh.js +22 -22
  207. package/dist/corpus-watermark.js +23 -23
  208. package/dist/deep-recall-http-glue.js +2 -2
  209. package/dist/deep-recall-policy-llm.js +4 -4
  210. package/dist/direct-answer-wiring.js +2 -2
  211. package/dist/embedding-fallback.js +1 -1
  212. package/dist/entity-retrieval.js +21 -21
  213. package/dist/eval-trajectory-store.js +2 -2
  214. package/dist/eval-trajectory.js +2 -2
  215. package/dist/event-order-recall.js +2 -2
  216. package/dist/explicit-capture.d.ts +3 -2
  217. package/dist/explicit-capture.d.ts.map +1 -1
  218. package/dist/explicit-capture.js +530 -6
  219. package/dist/explicit-cue-recall.js +2 -2
  220. package/dist/external-wiki-access.js +2 -283
  221. package/dist/extraction-faithfulness.js +4 -4
  222. package/dist/extraction-judge.js +5 -5
  223. package/dist/extraction-normalization.js +1 -1
  224. package/dist/extraction-quote-grounding.d.ts.map +1 -1
  225. package/dist/extraction-quote-grounding.js +1 -1
  226. package/dist/extraction-source-grounding-rules.js +2 -2
  227. package/dist/extraction-source-grounding.js +3 -3
  228. package/dist/extraction.js +43 -44
  229. package/dist/fallback-llm-codex-subscription.js +2 -2
  230. package/dist/fallback-llm.js +3 -3
  231. package/dist/{first-start-migration-QFI7B5SH.js → first-start-migration-FILJH3RO.js} +6 -5
  232. package/dist/{first-start-migration-QFI7B5SH.js.map → first-start-migration-FILJH3RO.js.map} +1 -1
  233. package/dist/focused-list-recall.js +2 -2
  234. package/dist/harmonic-retrieval.js +20 -20
  235. package/dist/index.js +776 -797
  236. package/dist/lcm/index.js +2 -2
  237. package/dist/live-connectors-runner.d.ts +2 -0
  238. package/dist/live-connectors-runner.d.ts.map +1 -1
  239. package/dist/live-connectors-runner.js +3 -4
  240. package/dist/local-llm-config.js +2 -2
  241. package/dist/local-llm-helpers.d.ts +4 -0
  242. package/dist/local-llm-helpers.d.ts.map +1 -1
  243. package/dist/local-llm-helpers.js +3 -1
  244. package/dist/local-llm.d.ts +4 -1
  245. package/dist/local-llm.d.ts.map +1 -1
  246. package/dist/local-llm.js +4 -4
  247. package/dist/location/index.js +27 -29
  248. package/dist/maintenance/memory-governance.js +20 -20
  249. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +20 -20
  250. package/dist/maintenance/rebuild-memory-projection-runner.js +22 -22
  251. package/dist/maintenance/rebuild-memory-projection.js +21 -21
  252. package/dist/memory-browse-operations.js +2 -283
  253. package/dist/memory-browse.js +8 -7
  254. package/dist/memory-controller.js +2 -2
  255. package/dist/memory-subject.js +2 -2
  256. package/dist/namespaces/migrate.js +21 -21
  257. package/dist/namespaces/search.js +5 -5
  258. package/dist/namespaces/storage.js +20 -20
  259. package/dist/offline-sync-changeset-generations.js +1 -1
  260. package/dist/offline-sync-embedding-generation.js +1 -1
  261. package/dist/offline-sync-impression-drain.js +6 -6
  262. package/dist/offline-sync-snapshot-stream.js +1 -1
  263. package/dist/offline-sync.js +1 -1
  264. package/dist/operator-doctor-corpus.js +24 -24
  265. package/dist/operator-toolkit.js +58 -61
  266. package/dist/orchestration/backlog-scheduler.js +279 -295
  267. package/dist/orchestration/backlog-scheduler.js.map +1 -1
  268. package/dist/orchestration/buffer-drain-runner.js +279 -295
  269. package/dist/orchestration/extraction-run.d.ts +4 -0
  270. package/dist/orchestration/extraction-run.d.ts.map +1 -1
  271. package/dist/orchestration/fact-dedup-window.d.ts +1 -1
  272. package/dist/orchestration/fact-dedup-window.d.ts.map +1 -1
  273. package/dist/orchestration/maintenance.js +39 -42
  274. package/dist/orchestrator.d.ts +11 -11
  275. package/dist/orchestrator.d.ts.map +1 -1
  276. package/dist/orchestrator.js +291 -308
  277. package/dist/page-versioning.js +3 -3
  278. package/dist/qmd-daemon-session.js +2 -2
  279. package/dist/qmd.js +3 -3
  280. package/dist/recall-explain-renderer.js +3 -3
  281. package/dist/recall-navigate-access.js +2 -2
  282. package/dist/recall-navigate-disclosure.js +2 -2
  283. package/dist/recall-navigate.js +2 -2
  284. package/dist/recall-navigation-operations.js +2 -283
  285. package/dist/recall-navigation.js +334 -61
  286. package/dist/recall-planner-llm.js +3 -3
  287. package/dist/recall-state-view-admission.js +6 -6
  288. package/dist/recall-why-service.js +10 -9
  289. package/dist/recall-why.js +4 -3
  290. package/dist/recall-xray-cli.js +4 -4
  291. package/dist/recall-xray-renderer.js +3 -3
  292. package/dist/recall-xray.js +2 -2
  293. package/dist/reconcile/manifest.js +3 -3
  294. package/dist/{resolution-N7H3F2E4.js → resolution-OVVIF7ZY.js} +8 -7
  295. package/dist/response-guidance-recall.js +2 -2
  296. package/dist/resume-bundles.js +40 -41
  297. package/dist/salvage-envelope.js +4 -4
  298. package/dist/search/factory.js +4 -4
  299. package/dist/search/index.js +4 -4
  300. package/dist/semantic-consolidation.js +23 -23
  301. package/dist/semantic-rule-promotion.js +20 -20
  302. package/dist/semantic-rule-verifier.js +20 -20
  303. package/dist/shared-context/manager.js +2 -3
  304. package/dist/source-agent-qualifier.js +32 -33
  305. package/dist/standup/build.js +20 -20
  306. package/dist/{state-store-I5HLZTZT.js → state-store-EEAX2MBS.js} +2 -2
  307. package/dist/storage/content-hash-index.d.ts +10 -0
  308. package/dist/storage/content-hash-index.d.ts.map +1 -1
  309. package/dist/storage.js +19 -19
  310. package/dist/subject-config.js +3 -3
  311. package/dist/summarizer.d.ts +4 -1
  312. package/dist/summarizer.d.ts.map +1 -1
  313. package/dist/summarizer.js +6 -6
  314. package/dist/support-passport/index.js +53 -297
  315. package/dist/targeted-fact-recall.js +2 -2
  316. package/dist/tier-routing.js +3 -2
  317. package/dist/{tier-stats-FQGQSFET.js → tier-stats-FXEUJN73.js} +6 -5
  318. package/dist/{tier-stats-FQGQSFET.js.map → tier-stats-FXEUJN73.js.map} +1 -1
  319. package/dist/transcript.js +2 -2
  320. package/dist/transfer/capsule-import.js +4 -4
  321. package/dist/transfer/export-okf.js +20 -20
  322. package/dist/types.d.ts +10 -10
  323. package/dist/types.d.ts.map +1 -1
  324. package/dist/types.js +1 -1
  325. package/dist/verified-recall.js +20 -20
  326. package/dist/wearables/memory-gen.d.ts +5 -1
  327. package/dist/wearables/memory-gen.d.ts.map +1 -1
  328. package/dist/wearables/service.d.ts.map +1 -1
  329. package/dist/who-knows-cli.js +21 -21
  330. package/dist/who-knows.js +20 -20
  331. package/dist/write-envelope.js +3 -3
  332. package/package.json +2 -2
  333. package/src/access-boundary.ts +5 -1
  334. package/src/access-category-alias.test.ts +5 -0
  335. package/src/access-mcp-frames.test.ts +60 -5
  336. package/src/access-mcp-frames.ts +72 -18
  337. package/src/access-mcp.test.ts +66 -2
  338. package/src/access-mcp.ts +19 -13
  339. package/src/access-service-coding-write.test.ts +7 -0
  340. package/src/access-service.ts +3 -0
  341. package/src/activity/journal-extract-storage.test.ts +31 -0
  342. package/src/activity/journal-extract.test.ts +1 -0
  343. package/src/activity/journal-extract.ts +24 -3
  344. package/src/cli.ts +1 -2
  345. package/src/config.test.ts +17 -0
  346. package/src/config.ts +25 -7
  347. package/src/connectors/live/framework.ts +8 -0
  348. package/src/connectors/live/google-drive.test.ts +74 -0
  349. package/src/connectors/live/google-drive.ts +138 -11
  350. package/src/explicit-capture.test.ts +109 -17
  351. package/src/explicit-capture.ts +90 -37
  352. package/src/extraction-quote-grounding.test.ts +20 -0
  353. package/src/extraction-quote-grounding.ts +21 -11
  354. package/src/fact-dedup-window.test.ts +37 -0
  355. package/src/live-connectors-runner.ts +3 -0
  356. package/src/live-connectors-secretref.test.ts +126 -0
  357. package/src/local-llm-helpers.ts +12 -0
  358. package/src/local-llm-shutdown.test.ts +194 -0
  359. package/src/local-llm.ts +32 -30
  360. package/src/orchestration/extraction-run.ts +12 -3
  361. package/src/orchestration/fact-dedup-window.ts +5 -1
  362. package/src/orchestrator-connector-dedup.test.ts +9 -5
  363. package/src/orchestrator.ts +52 -55
  364. package/src/storage/content-hash-index.ts +27 -2
  365. package/src/storage-fact-hash-publish-failure.test.ts +87 -0
  366. package/src/summarizer-shutdown.test.ts +195 -0
  367. package/src/summarizer.ts +15 -2
  368. package/src/types.ts +10 -10
  369. package/src/wearables/memory-gen.test.ts +20 -0
  370. package/src/wearables/memory-gen.ts +14 -7
  371. package/src/wearables/service.ts +14 -1
  372. package/src/wearables/storage-io.test.ts +25 -0
  373. package/dist/chunk-27XYV2XL.js +0 -43
  374. package/dist/chunk-27XYV2XL.js.map +0 -1
  375. package/dist/chunk-5KBIOI4G.js.map +0 -1
  376. package/dist/chunk-66L5ZRJC.js.map +0 -1
  377. package/dist/chunk-7HTUYFR3.js.map +0 -1
  378. package/dist/chunk-AOFEMUHG.js.map +0 -1
  379. package/dist/chunk-AP6NCP3V.js.map +0 -1
  380. package/dist/chunk-APYY5CX3.js +0 -335
  381. package/dist/chunk-APYY5CX3.js.map +0 -1
  382. package/dist/chunk-ATPWMT2E.js +0 -223
  383. package/dist/chunk-ATPWMT2E.js.map +0 -1
  384. package/dist/chunk-BCCG7VWM.js.map +0 -1
  385. package/dist/chunk-E6M225BJ.js.map +0 -1
  386. package/dist/chunk-EQ4U73UH.js.map +0 -1
  387. package/dist/chunk-ETSJD5IV.js +0 -361
  388. package/dist/chunk-ETSJD5IV.js.map +0 -1
  389. package/dist/chunk-EWJZZDRG.js.map +0 -1
  390. package/dist/chunk-FHGWKODN.js +0 -2300
  391. package/dist/chunk-FHGWKODN.js.map +0 -1
  392. package/dist/chunk-GRTKLWYX.js +0 -621
  393. package/dist/chunk-GRTKLWYX.js.map +0 -1
  394. package/dist/chunk-IGLJNHOX.js +0 -62
  395. package/dist/chunk-IGLJNHOX.js.map +0 -1
  396. package/dist/chunk-IPT2OY4B.js.map +0 -1
  397. package/dist/chunk-IUKYFTGW.js.map +0 -1
  398. package/dist/chunk-KUIRDD74.js +0 -3991
  399. package/dist/chunk-KUIRDD74.js.map +0 -1
  400. package/dist/chunk-MVMRUGBH.js +0 -1919
  401. package/dist/chunk-MVMRUGBH.js.map +0 -1
  402. package/dist/chunk-ODL4ZTNI.js +0 -3644
  403. package/dist/chunk-ODL4ZTNI.js.map +0 -1
  404. package/dist/chunk-OGXXGHW6.js +0 -223
  405. package/dist/chunk-OGXXGHW6.js.map +0 -1
  406. package/dist/chunk-P5VZSSYL.js.map +0 -1
  407. package/dist/chunk-PPCLZ4JU.js +0 -28
  408. package/dist/chunk-PPCLZ4JU.js.map +0 -1
  409. package/dist/chunk-PUEO37QG.js.map +0 -1
  410. package/dist/chunk-RRTVGD3J.js +0 -792
  411. package/dist/chunk-RRTVGD3J.js.map +0 -1
  412. package/dist/chunk-STSLB4HT.js +0 -18793
  413. package/dist/chunk-STSLB4HT.js.map +0 -1
  414. package/dist/chunk-T6OZW2WR.js.map +0 -1
  415. package/dist/chunk-TRBGGJKZ.js +0 -32218
  416. package/dist/chunk-TRBGGJKZ.js.map +0 -1
  417. package/dist/chunk-UDKWFDV2.js.map +0 -1
  418. package/dist/chunk-V4XE263I.js.map +0 -1
  419. package/dist/chunk-V7IZKJI3.js +0 -22
  420. package/dist/chunk-V7IZKJI3.js.map +0 -1
  421. package/dist/chunk-VMVVSZTD.js +0 -61
  422. package/dist/chunk-VMVVSZTD.js.map +0 -1
  423. package/dist/chunk-VNUOAXWP.js +0 -203
  424. package/dist/chunk-VNUOAXWP.js.map +0 -1
  425. package/dist/chunk-WLBEZZK5.js +0 -729
  426. package/dist/chunk-WLBEZZK5.js.map +0 -1
  427. package/dist/chunk-WUUES6CU.js +0 -25
  428. package/dist/chunk-WUUES6CU.js.map +0 -1
  429. package/dist/chunk-WWK6J6SE.js.map +0 -1
  430. package/dist/chunk-XICOH5HT.js +0 -346
  431. package/dist/chunk-XICOH5HT.js.map +0 -1
  432. package/dist/chunk-YCNIGRRF.js.map +0 -1
  433. package/dist/chunk-YS2GHSWN.js.map +0 -1
  434. package/dist/chunk-ZCLEYJPB.js +0 -10143
  435. package/dist/chunk-ZCLEYJPB.js.map +0 -1
  436. /package/dist/{capsule-merge-2IIXFXN5.js.map → capsule-merge-6IDAYTKG.js.map} +0 -0
  437. /package/dist/{chunk-QL7RHYK6.js.map → chunk-2LMA22UU.js.map} +0 -0
  438. /package/dist/{chunk-RGUUBAT5.js.map → chunk-2WCLTWV2.js.map} +0 -0
  439. /package/dist/{chunk-RSK2WIEM.js.map → chunk-2YODOG2O.js.map} +0 -0
  440. /package/dist/{chunk-WYG5UNDY.js.map → chunk-3AUDELZL.js.map} +0 -0
  441. /package/dist/{chunk-OFVCFKDH.js.map → chunk-3CYOCE6U.js.map} +0 -0
  442. /package/dist/{chunk-6HM6HARE.js.map → chunk-3FBAEPCU.js.map} +0 -0
  443. /package/dist/{chunk-PIITB2YZ.js.map → chunk-3ZS3NUEI.js.map} +0 -0
  444. /package/dist/{chunk-ANZZLRUX.js.map → chunk-4IVRQMZW.js.map} +0 -0
  445. /package/dist/{chunk-63E37JCN.js.map → chunk-4RAJT5L2.js.map} +0 -0
  446. /package/dist/{chunk-45VUHDV2.js.map → chunk-5CO5G4MN.js.map} +0 -0
  447. /package/dist/{chunk-3UMDRGFS.js.map → chunk-65HWAQ4F.js.map} +0 -0
  448. /package/dist/{chunk-T67LUYCR.js.map → chunk-77NTFICT.js.map} +0 -0
  449. /package/dist/{chunk-UE6RRXMF.js.map → chunk-A4F7OIC5.js.map} +0 -0
  450. /package/dist/{chunk-63O7LVMN.js.map → chunk-B33YWUJV.js.map} +0 -0
  451. /package/dist/{chunk-QVMOCJRV.js.map → chunk-B377OKUP.js.map} +0 -0
  452. /package/dist/{chunk-ZT6WPSII.js.map → chunk-BGHCSLE5.js.map} +0 -0
  453. /package/dist/{chunk-YYOMGOHB.js.map → chunk-BOK6BN5M.js.map} +0 -0
  454. /package/dist/{chunk-OH7QLI6W.js.map → chunk-BPPUH5JM.js.map} +0 -0
  455. /package/dist/{chunk-EV76IZ37.js.map → chunk-BTI4C4MA.js.map} +0 -0
  456. /package/dist/{chunk-O6ZSWHVR.js.map → chunk-C2KE5DMD.js.map} +0 -0
  457. /package/dist/{chunk-47F6WWLQ.js.map → chunk-C73N66PR.js.map} +0 -0
  458. /package/dist/{chunk-3XIWW4TB.js.map → chunk-CGJYL633.js.map} +0 -0
  459. /package/dist/{chunk-WRT7GJ2D.js.map → chunk-CIWMTNHW.js.map} +0 -0
  460. /package/dist/{chunk-O55EKIEC.js.map → chunk-D3ZKHLN2.js.map} +0 -0
  461. /package/dist/{chunk-CD4BCCNP.js.map → chunk-DJ7WVEWX.js.map} +0 -0
  462. /package/dist/{chunk-RO7RUBLD.js.map → chunk-EFL24KOI.js.map} +0 -0
  463. /package/dist/{chunk-4CJTQHEG.js.map → chunk-ESLOLCD4.js.map} +0 -0
  464. /package/dist/{chunk-Z4Q6USKJ.js.map → chunk-ETBVCRVL.js.map} +0 -0
  465. /package/dist/{chunk-VILPVT4S.js.map → chunk-EZ47HOJ6.js.map} +0 -0
  466. /package/dist/{chunk-EFYN5I7Z.js.map → chunk-F4QFU5IZ.js.map} +0 -0
  467. /package/dist/{chunk-SHUBTAY2.js.map → chunk-FMKILENU.js.map} +0 -0
  468. /package/dist/{chunk-YLXETSAH.js.map → chunk-GCZJ7XRI.js.map} +0 -0
  469. /package/dist/{chunk-QFEDITXM.js.map → chunk-JNDMIAJ4.js.map} +0 -0
  470. /package/dist/{chunk-E5PPE4VA.js.map → chunk-K2VRKCPA.js.map} +0 -0
  471. /package/dist/{chunk-MKRYQROF.js.map → chunk-LGEHAQHK.js.map} +0 -0
  472. /package/dist/{chunk-XYTQJD7F.js.map → chunk-LMJVP5CM.js.map} +0 -0
  473. /package/dist/{chunk-JC36FPQ5.js.map → chunk-LPV72KRQ.js.map} +0 -0
  474. /package/dist/{chunk-2LCRG2YV.js.map → chunk-M2XR6B6V.js.map} +0 -0
  475. /package/dist/{chunk-VPQFK63H.js.map → chunk-MH632KPO.js.map} +0 -0
  476. /package/dist/{chunk-ZU5JDLOH.js.map → chunk-MMRTPH3J.js.map} +0 -0
  477. /package/dist/{chunk-ZZ67UTQX.js.map → chunk-MUX7IL77.js.map} +0 -0
  478. /package/dist/{chunk-RYWROSL5.js.map → chunk-NAQ4A5IX.js.map} +0 -0
  479. /package/dist/{chunk-TVIVY7KU.js.map → chunk-NED7IR7B.js.map} +0 -0
  480. /package/dist/{chunk-ADYP7HXE.js.map → chunk-NHVAX7MH.js.map} +0 -0
  481. /package/dist/{chunk-TG3R2SID.js.map → chunk-NMGHGTVR.js.map} +0 -0
  482. /package/dist/{chunk-NGHDW6UU.js.map → chunk-NUQEREIP.js.map} +0 -0
  483. /package/dist/{chunk-5IIQU4JZ.js.map → chunk-OB72KNMF.js.map} +0 -0
  484. /package/dist/{chunk-7L3TODI7.js.map → chunk-OJXJWNFY.js.map} +0 -0
  485. /package/dist/{chunk-EURPSF4Y.js.map → chunk-PH5ZQM35.js.map} +0 -0
  486. /package/dist/{chunk-BMWMTAQF.js.map → chunk-PRAUN6PN.js.map} +0 -0
  487. /package/dist/{chunk-RWGRSKN5.js.map → chunk-PT4DZDJA.js.map} +0 -0
  488. /package/dist/{chunk-7EPY3IQL.js.map → chunk-PZJQ46DH.js.map} +0 -0
  489. /package/dist/{chunk-AH637HZ5.js.map → chunk-QDESY76F.js.map} +0 -0
  490. /package/dist/{chunk-I6QGI65T.js.map → chunk-QRMG4HK4.js.map} +0 -0
  491. /package/dist/{chunk-ZU3NJNO7.js.map → chunk-QSF5SJAM.js.map} +0 -0
  492. /package/dist/{chunk-5IYOKDM4.js.map → chunk-R23JWJD3.js.map} +0 -0
  493. /package/dist/{chunk-LJQFJF4J.js.map → chunk-RM2YUFH6.js.map} +0 -0
  494. /package/dist/{chunk-6QMWECO5.js.map → chunk-SGQPR5SX.js.map} +0 -0
  495. /package/dist/{chunk-ZGFLIK5H.js.map → chunk-SPAKBUJY.js.map} +0 -0
  496. /package/dist/{chunk-ER7XANP3.js.map → chunk-SQ3GEIEL.js.map} +0 -0
  497. /package/dist/{chunk-5UCMYPVF.js.map → chunk-SVT5IA2H.js.map} +0 -0
  498. /package/dist/{chunk-FRSHF6FU.js.map → chunk-TJ4F6H4H.js.map} +0 -0
  499. /package/dist/{chunk-R6HMAIKM.js.map → chunk-TOSPNM4T.js.map} +0 -0
  500. /package/dist/{chunk-NJJMVS5Y.js.map → chunk-TRQE3RYR.js.map} +0 -0
  501. /package/dist/{chunk-RMXS4I66.js.map → chunk-TVDJEUV5.js.map} +0 -0
  502. /package/dist/{chunk-PLCYKAWH.js.map → chunk-TYRWSE4B.js.map} +0 -0
  503. /package/dist/{chunk-IQWUACVG.js.map → chunk-UHMHJX5G.js.map} +0 -0
  504. /package/dist/{chunk-YODUDA6R.js.map → chunk-UN4RVUG2.js.map} +0 -0
  505. /package/dist/{chunk-KZSGBWOJ.js.map → chunk-V2KGDESV.js.map} +0 -0
  506. /package/dist/{chunk-E6Z4FBX6.js.map → chunk-V2QSNFCT.js.map} +0 -0
  507. /package/dist/{chunk-N74SGESS.js.map → chunk-WKSQTMU7.js.map} +0 -0
  508. /package/dist/{chunk-RIZ5NUNN.js.map → chunk-WQ4XKIOX.js.map} +0 -0
  509. /package/dist/{chunk-QEYTRFR3.js.map → chunk-X26YOFGS.js.map} +0 -0
  510. /package/dist/{chunk-ITHZFHST.js.map → chunk-X2BVB74H.js.map} +0 -0
  511. /package/dist/{chunk-GNXGJBCA.js.map → chunk-XIYLWEXC.js.map} +0 -0
  512. /package/dist/{chunk-IBJ6U753.js.map → chunk-XKZBZOKV.js.map} +0 -0
  513. /package/dist/{chunk-7O2MC42R.js.map → chunk-XNRLRNQF.js.map} +0 -0
  514. /package/dist/{chunk-65AUYLU3.js.map → chunk-XPXHQI6N.js.map} +0 -0
  515. /package/dist/{chunk-JBCCWVU2.js.map → chunk-XRDYRWZI.js.map} +0 -0
  516. /package/dist/{chunk-2JSDPUKD.js.map → chunk-YMFPFXEX.js.map} +0 -0
  517. /package/dist/{chunk-645WOU5I.js.map → chunk-ZDZJNXL6.js.map} +0 -0
  518. /package/dist/{chunk-6TCXIRP5.js.map → chunk-ZKE4CO6B.js.map} +0 -0
  519. /package/dist/{chunk-KEPXC4KN.js.map → chunk-ZSE774RV.js.map} +0 -0
  520. /package/dist/{contradiction-scan-P2MHYFVG.js.map → contradiction-scan-IBPYGQZF.js.map} +0 -0
  521. /package/dist/{resolution-N7H3F2E4.js.map → resolution-OVVIF7ZY.js.map} +0 -0
  522. /package/dist/{state-store-I5HLZTZT.js.map → state-store-EEAX2MBS.js.map} +0 -0
@@ -3,7 +3,7 @@
3
3
  // rebuild saves plus the deferred durable-retry machinery. storage.ts imports
4
4
  // these back and re-exports the class + companion types that were previously
5
5
  // part of its public API.
6
- import { mkdir, stat } from "node:fs/promises";
6
+ import { mkdir, stat, utimes } from "node:fs/promises";
7
7
  import path from "node:path";
8
8
  import { computeContentHash, normalizeContent } from "../content-hash.js";
9
9
  import { log } from "../logger.js";
@@ -291,6 +291,25 @@ export class ContentHashIndex {
291
291
  );
292
292
  }
293
293
 
294
+ /**
295
+ * Change the durable file's mtime without touching its content. A publish
296
+ * that throws or stays deferred leaves the file as it was, so a peer that
297
+ * already synced it keeps answering authoritative MISSes for deltas this
298
+ * instance holds only in memory (#3355). A new stamp makes every holder of the
299
+ * old fingerprint, this instance included, fail the freshness check and
300
+ * confirm against the corpus. Best effort: a file that cannot be stamped
301
+ * leaves the callers' existing retry and rebuild paths as the only repair.
302
+ */
303
+ private async invalidatePeerFreshness(): Promise<void> {
304
+ try {
305
+ const { mtimeMs } = await stat(this.filePath);
306
+ const bumped = new Date(Math.max(Date.now(), mtimeMs + 1));
307
+ await utimes(this.filePath, bumped, bumped);
308
+ } catch (err) {
309
+ log.debug(`content-hash index: could not invalidate peer freshness of ${this.filePath}: ${err}`);
310
+ }
311
+ }
312
+
294
313
  /** Check if content already exists in the index. */
295
314
  has(content: string): boolean {
296
315
  return this.hashes.has(ContentHashIndex.computeHash(content));
@@ -473,9 +492,15 @@ export class ContentHashIndex {
473
492
  `content-hash index: lock not acquired for ${this.filePath}; deferring reconcile save (dirty retained, scheduling durable retry)`
474
493
  );
475
494
  this.scheduleReconcileRetry();
495
+ await this.invalidatePeerFreshness();
476
496
  return;
477
497
  }
478
- await publish();
498
+ try {
499
+ await publish();
500
+ } catch (err) {
501
+ await this.invalidatePeerFreshness();
502
+ throw err;
503
+ }
479
504
  if (this.dirty) {
480
505
  // A concurrent add()/remove() landed during publish's disk awaits, so
481
506
  // deltas remain unpersisted (PR #2016 thread SD7Tj). Arm a bounded
@@ -0,0 +1,87 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { StorageManager } from "./storage.js";
7
+ import { ContentHashIndex } from "./storage/content-hash-index.js";
8
+
9
+ const FACT = "the quarterly budget was approved by the board";
10
+ const CONTENDED_LOCK_OPTIONS = { maxWaitMs: 40, pollMs: 10, retryMaxAttempts: 2, retryBaseMs: 10 };
11
+
12
+ async function withMemoryDir(run: (dir: string) => Promise<void>): Promise<void> {
13
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-fact-hash-publish-"));
14
+ try {
15
+ await run(dir);
16
+ } finally {
17
+ await rm(dir, { recursive: true, force: true });
18
+ }
19
+ }
20
+
21
+ /** Hold the fact-hash index lock the way a peer mid-publish does; returns the release. */
22
+ async function holdIndexLock(dir: string): Promise<() => Promise<void>> {
23
+ const lockPath = path.join(dir, "state", "fact-hashes.txt.lock");
24
+ await mkdir(path.dirname(lockPath), { recursive: true });
25
+ await writeFile(lockPath, `99999 peer-owner ${new Date().toISOString()}\n`);
26
+ return () => rm(lockPath, { force: true });
27
+ }
28
+
29
+ test("a publish that throws leaves peers revalidating instead of trusting the old file (#3355)", async () => {
30
+ await withMemoryDir(async (dir) => {
31
+ const stateDir = path.join(dir, "state");
32
+ const peer = new ContentHashIndex(stateDir);
33
+ peer.add("seed fact");
34
+ await peer.saveMergingWithDisk();
35
+ assert.equal(await peer.isDiskFingerprintCurrent(), true, "the peer starts in sync with the file");
36
+
37
+ const writer = new ContentHashIndex(
38
+ stateDir,
39
+ () => null,
40
+ () => {
41
+ throw new Error("write key unavailable");
42
+ },
43
+ );
44
+ await writer.load();
45
+ writer.add("pending fact");
46
+ await assert.rejects(writer.saveMergingWithDisk(), /write key unavailable/);
47
+
48
+ assert.equal(writer.hasPendingChanges, true, "the failed addition stays pending in the writer");
49
+ assert.equal(
50
+ await peer.isDiskFingerprintCurrent(),
51
+ false,
52
+ "the file did not change, yet the peer must stop trusting its snapshot",
53
+ );
54
+ });
55
+ });
56
+
57
+ test("a writeMemory whose publish stays deferred does not leave a peer answering an authoritative MISS (#3355)", async () => {
58
+ await withMemoryDir(async (dir) => {
59
+ const peer = new StorageManager(dir);
60
+ const writer = new StorageManager(dir);
61
+ peer.factHashIndexLockOptions = CONTENDED_LOCK_OPTIONS;
62
+ writer.factHashIndexLockOptions = CONTENDED_LOCK_OPTIONS;
63
+ assert.deepEqual(
64
+ await peer.lookupContentHash(FACT),
65
+ { present: false, authoritative: true },
66
+ "the peer starts with an authoritative, empty index",
67
+ );
68
+
69
+ const release = await holdIndexLock(dir);
70
+ const written = await writer.writeMemory("fact", FACT);
71
+ assert.ok(written.id, "the memory is durable, so the write still succeeds");
72
+
73
+ const duringContention = await peer.lookupContentHash(FACT);
74
+ assert.ok(
75
+ duringContention.present || !duringContention.authoritative,
76
+ `a peer must not report an authoritative MISS for a fact on disk: ${JSON.stringify(duringContention)}`,
77
+ );
78
+
79
+ await release();
80
+ assert.deepEqual(
81
+ await peer.lookupContentHash(FACT),
82
+ { present: true, authoritative: true },
83
+ "once the lock is free the peer rebuilds from the corpus and sees the fact",
84
+ );
85
+ await writer.lookupContentHash(FACT);
86
+ });
87
+ });
@@ -0,0 +1,195 @@
1
+ // Shutdown-signal behavioral tests for HourlySummarizer: an aborted
2
+ // summarizer must abort its in-flight local LLM call (the abort reaches the
3
+ // client stub) and must refuse to start new summary work once aborted.
4
+ import assert from "node:assert/strict";
5
+ import { mkdtemp, rm } from "node:fs/promises";
6
+ import os from "node:os";
7
+ import path from "node:path";
8
+ import test from "node:test";
9
+
10
+ import { parseConfig } from "./config.js";
11
+ import { initLogger, resetLogger } from "./logger.js";
12
+ import { HourlySummarizer } from "./summarizer.js";
13
+ import type { LocalLlmChatCompletionOptions } from "./local-llm-helpers.js";
14
+ import type { TranscriptEntry } from "./types.js";
15
+
16
+ function createConfig(memoryDir: string, extended = false) {
17
+ return parseConfig({
18
+ openaiApiKey: "sk-test",
19
+ memoryDir,
20
+ workspaceDir: path.join(memoryDir, "workspace"),
21
+ localLlmEnabled: true,
22
+ localLlmModel: "test-local-model",
23
+ localLlmUrl: "http://127.0.0.1:9",
24
+ localLlmTimeoutMs: 600_000,
25
+ localLlmRetry5xxCount: 1,
26
+ localLlmRetryBackoffMs: 30_000,
27
+ hourlySummariesEnabled: true,
28
+ hourlySummariesExtendedEnabled: extended,
29
+ debug: false,
30
+ });
31
+ }
32
+
33
+ interface LlmStub {
34
+ calls: { signal: AbortSignal | undefined }[];
35
+ }
36
+
37
+ function stubLocalLlm(summarizer: HourlySummarizer, stub: LlmStub): void {
38
+ // SAFETY: localLlm is a private field, stubbed at the established field
39
+ // seam with the client's real call shape; the stub parks until its caller
40
+ // signal aborts (real transport behavior), mirroring a wedged local LLM.
41
+ (summarizer as any).localLlm = {
42
+ chatCompletion: async (
43
+ messages: Array<{ role: string; content: string }>,
44
+ options: LocalLlmChatCompletionOptions,
45
+ ): Promise<null> => {
46
+ void messages;
47
+ stub.calls.push({ signal: options.signal });
48
+ const { promise, reject } = Promise.withResolvers<null>();
49
+ const onAbort = (): void => {
50
+ reject(new DOMException("This operation was aborted", "AbortError"));
51
+ };
52
+ const signal = options.signal;
53
+ if (signal) {
54
+ if (signal.aborted) onAbort();
55
+ else signal.addEventListener("abort", onAbort, { once: true });
56
+ }
57
+ return await promise;
58
+ },
59
+ };
60
+ }
61
+
62
+ async function withTestDeadline<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {
63
+ const { promise: deadline, reject } = Promise.withResolvers<never>();
64
+ const timer = setTimeout(() => reject(new Error(`${label}: test deadline exceeded (${timeoutMs}ms)`)), timeoutMs);
65
+ timer.unref?.();
66
+ try {
67
+ return await Promise.race([promise, deadline]);
68
+ } finally {
69
+ clearTimeout(timer);
70
+ }
71
+ }
72
+
73
+ function quietLogger(): void {
74
+ initLogger({ info() {}, warn() {}, error() {}, debug() {} }, false);
75
+ }
76
+
77
+ function entriesFor(): TranscriptEntry[] {
78
+ return [
79
+ {
80
+ role: "user",
81
+ content: "bounded shutdown summarizer test entry",
82
+ timestamp: new Date(Date.now() - 3_600_000).toISOString(),
83
+ sessionKey: "sess-summary-shutdown",
84
+ turnId: "turn-shutdown-1",
85
+ },
86
+ ];
87
+ }
88
+
89
+ test("aborting the shutdown signal aborts an in-flight generateSummary and reaches the LLM stub", async () => {
90
+ quietLogger();
91
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-summary-shutdown-inflight-"));
92
+ const stub: LlmStub = { calls: [] };
93
+ try {
94
+ const controller = new AbortController();
95
+ const summarizer = new HourlySummarizer(
96
+ createConfig(memoryDir),
97
+ undefined,
98
+ undefined,
99
+ undefined,
100
+ controller.signal,
101
+ );
102
+ stubLocalLlm(summarizer, stub);
103
+
104
+ const hourStart = new Date(Date.now() - 3_600_000);
105
+ hourStart.setMinutes(0, 0, 0);
106
+ const pending = summarizer.generateSummary("sess-summary-shutdown", hourStart, entriesFor());
107
+
108
+ await withTestDeadline((async () => {
109
+ while (stub.calls.length === 0) {
110
+ await new Promise((resolve) => setTimeout(resolve, 10));
111
+ }
112
+ })(), 5_000, "LLM stub to be entered");
113
+ controller.abort();
114
+
115
+ const result = await withTestDeadline(pending, 2_000, "in-flight summarizer abort");
116
+ assert.equal(result, null);
117
+ assert.equal(stub.calls.length, 1, "no second LLM attempt may start after abort");
118
+ assert.equal(stub.calls[0]?.signal?.aborted, true, "the shutdown abort must reach the LLM stub");
119
+ } finally {
120
+ resetLogger();
121
+ await rm(memoryDir, { recursive: true, force: true });
122
+ }
123
+ });
124
+
125
+ test("an already-aborted summarizer refuses to start new work and never contacts the LLM", async () => {
126
+ quietLogger();
127
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-summary-shutdown-refuse-"));
128
+ const stub: LlmStub = { calls: [] };
129
+ try {
130
+ const controller = new AbortController();
131
+ controller.abort();
132
+ const summarizer = new HourlySummarizer(
133
+ createConfig(memoryDir),
134
+ undefined,
135
+ undefined,
136
+ undefined,
137
+ controller.signal,
138
+ );
139
+ stubLocalLlm(summarizer, stub);
140
+
141
+ const hourStart = new Date(Date.now() - 3_600_000);
142
+ hourStart.setMinutes(0, 0, 0);
143
+ await assert.rejects(
144
+ summarizer.generateSummary("sess-summary-shutdown", hourStart, entriesFor()),
145
+ );
146
+ assert.equal(stub.calls.length, 0, "aborted summarizer must not start new LLM work");
147
+ } finally {
148
+ resetLogger();
149
+ await rm(memoryDir, { recursive: true, force: true });
150
+ }
151
+ });
152
+
153
+ for (const extended of [false, true]) {
154
+ test(`a shutdown abort during the local call does not start a gateway call (${extended ? "extended" : "standard"} summary)`, async () => {
155
+ quietLogger();
156
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-summary-shutdown-gateway-"));
157
+ const stub: LlmStub = { calls: [] };
158
+ let gatewayCalls = 0;
159
+ try {
160
+ const controller = new AbortController();
161
+ const summarizer = new HourlySummarizer(
162
+ createConfig(memoryDir, extended),
163
+ undefined,
164
+ undefined,
165
+ undefined,
166
+ controller.signal,
167
+ );
168
+ stubLocalLlm(summarizer, stub);
169
+ // SAFETY: fallbackLlm is a private field, stubbed at the same field seam
170
+ // as localLlm; the stub counts gateway calls and returns no result.
171
+ (summarizer as any).fallbackLlm = {
172
+ parseWithSchema: async (): Promise<null> => {
173
+ gatewayCalls += 1;
174
+ return null;
175
+ },
176
+ };
177
+
178
+ const hourStart = new Date(Date.now() - 3_600_000);
179
+ hourStart.setMinutes(0, 0, 0);
180
+ const pending = summarizer.generateSummary("sess-summary-shutdown", hourStart, entriesFor());
181
+ await withTestDeadline((async () => {
182
+ while (stub.calls.length === 0) {
183
+ await new Promise((resolve) => setTimeout(resolve, 10));
184
+ }
185
+ })(), 5_000, "LLM stub to be entered");
186
+ controller.abort();
187
+
188
+ assert.equal(await withTestDeadline(pending, 2_000, "in-flight summarizer abort"), null);
189
+ assert.equal(gatewayCalls, 0, "no gateway call may start after the shutdown abort");
190
+ } finally {
191
+ resetLogger();
192
+ await rm(memoryDir, { recursive: true, force: true });
193
+ }
194
+ });
195
+ }
package/src/summarizer.ts CHANGED
@@ -114,20 +114,25 @@ export class HourlySummarizer {
114
114
  private fallbackLlm: FallbackLlmClient;
115
115
  private modelRegistry: ModelRegistry;
116
116
  private transcript?: TranscriptManager;
117
+ /** Orchestrator-owned stop signal: refuses new summaries and aborts the
118
+ * in-flight local LLM call once teardown begins. */
119
+ private readonly shutdownSignal: AbortSignal | undefined;
117
120
 
118
121
  constructor(
119
122
  config: PluginConfig,
120
123
  gatewayConfig?: GatewayConfig,
121
124
  modelRegistry?: ModelRegistry,
122
- transcript?: TranscriptManager
125
+ transcript?: TranscriptManager,
126
+ shutdownSignal?: AbortSignal
123
127
  ) {
124
128
  this.config = config;
125
129
  this.summariesDir = path.join(config.memoryDir, "summaries", "hourly");
126
130
  this.modelRegistry = modelRegistry ?? new ModelRegistry(config.memoryDir);
127
131
  this.transcript = transcript;
132
+ this.shutdownSignal = shutdownSignal;
128
133
 
129
134
  // Initialize local LLM client with shared model registry
130
- this.localLlm = new LocalLlmClient(config, this.modelRegistry);
135
+ this.localLlm = new LocalLlmClient(config, this.modelRegistry, shutdownSignal);
131
136
 
132
137
  // Initialize fallback client with gateway config
133
138
  this.fallbackLlm = new FallbackLlmClient(gatewayConfig, fallbackLlmRuntimeContextFromConfig(config));
@@ -190,6 +195,7 @@ export class HourlySummarizer {
190
195
  entries: TranscriptEntry[]
191
196
  ): Promise<HourlySummary | null> {
192
197
  if (entries.length === 0) return null;
198
+ this.shutdownSignal?.throwIfAborted();
193
199
 
194
200
  // Format entries for the LLM
195
201
  const conversation = entries.map((e) => `[${e.role}] ${e.content}`).join("\n\n");
@@ -275,6 +281,8 @@ export class HourlySummarizer {
275
281
  }
276
282
  }
277
283
 
284
+ if (this.shutdownSignal?.aborted) return null;
285
+
278
286
  // Fall back to gateway's default AI
279
287
  log.info("summary generation: falling back to gateway default AI");
280
288
 
@@ -399,6 +407,7 @@ Respond with valid JSON matching this schema:
399
407
  maxTokens: contextSizes.maxOutputTokens,
400
408
  operation: "hourly_summary_extended",
401
409
  priority: "background",
410
+ signal: this.shutdownSignal,
402
411
  }
403
412
  );
404
413
  if (response?.content) {
@@ -422,6 +431,8 @@ Respond with valid JSON matching this schema:
422
431
  }
423
432
  }
424
433
 
434
+ if (this.shutdownSignal?.aborted) return null;
435
+
425
436
  try {
426
437
  const result = await this.fallbackLlm.parseWithSchema(
427
438
  [
@@ -489,6 +500,7 @@ Respond with valid JSON matching this schema:
489
500
  maxTokens: contextSizes.maxOutputTokens,
490
501
  operation: "hourly_summary",
491
502
  priority: "background",
503
+ signal: this.shutdownSignal,
492
504
  }
493
505
  );
494
506
 
@@ -775,6 +787,7 @@ Respond with valid JSON matching this schema:
775
787
 
776
788
  // Main entry point for cron job
777
789
  async runHourly(): Promise<HourlySummarizerRunStats> {
790
+ this.shutdownSignal?.throwIfAborted();
778
791
  log.debug("running hourly summary generation");
779
792
 
780
793
  // Get active sessions from transcript. The scan outcome is kept LOCAL to
package/src/types.ts CHANGED
@@ -273,8 +273,9 @@ export interface NativeKnowledgeOpenClawWorkspaceConfig {
273
273
  * OpenClaw resolves these at runtime via its built-in secret resolver
274
274
  * (e.g. exec providers like `kc_*` for macOS Keychain). Plugins receive
275
275
  * the raw object in `pluginConfig` and must call the gateway's resolver
276
- * before using the value. Standalone Remnic does NOT resolve SecretRefs;
277
- * operators must use plain strings or `${ENV_VAR}` expansion instead.
276
+ * before using the value. A live connector credential field resolves a
277
+ * SecretRef at poll time through the host resolver. Other surfaces still
278
+ * need a plain string or `${ENV_VAR}` expansion.
278
279
  */
279
280
  export interface SecretRef {
280
281
  source: string;
@@ -2539,21 +2540,20 @@ export interface LiveConnectorsConfig {
2539
2540
  * shape (frozen, post-validation). This interface is the pre-validation
2540
2541
  * shape that `parseConfig` round-trips through.
2541
2542
  *
2542
- * `clientId` / `clientSecret` / `refreshToken` are stored as strings here so
2543
- * the schema can ship in `openclaw.plugin.json` and operators can populate
2544
- * them from a secret store (e.g. an env-substituted plist or systemd
2545
- * EnvironmentFile). They MUST NEVER be committed to source. The repo-wide
2546
- * privacy policy in CLAUDE.md applies.
2543
+ * `clientId` / `clientSecret` / `refreshToken` are a plain string or a
2544
+ * SecretRef. A SecretRef is kept verbatim here and resolved at poll time
2545
+ * through the host resolver. They MUST NEVER be committed to source. The
2546
+ * repo-wide privacy policy in CLAUDE.md applies.
2547
2547
  */
2548
2548
  export interface GoogleDriveLiveConnectorConfig {
2549
2549
  /** Master gate. Default false — operators must opt in explicitly. */
2550
2550
  enabled: boolean;
2551
2551
  /** OAuth2 client id. Populate from a secret store; never commit. */
2552
- clientId: string;
2552
+ clientId: string | SecretRef;
2553
2553
  /** OAuth2 client secret. Populate from a secret store; never commit. */
2554
- clientSecret: string;
2554
+ clientSecret: string | SecretRef;
2555
2555
  /** OAuth2 refresh token. Populate from a secret store; never commit. */
2556
- refreshToken: string;
2556
+ refreshToken: string | SecretRef;
2557
2557
  /** Poll interval in ms. Default 300000 (5 min); min 1000; max 86400000 (24h). */
2558
2558
  pollIntervalMs: number;
2559
2559
  /** Optional folder-id scope. Empty array = all accessible files. */
@@ -224,6 +224,26 @@ test("auto mode writes active status", async () => {
224
224
  assert.equal(writes[0].options.status, "active");
225
225
  });
226
226
 
227
+ test("a fact write the dedup window suppressed counts as skipped, not created", async () => {
228
+ const { writer } = makeWriter();
229
+ writer.writeSealedMemory = async () => ({ duplicate: true });
230
+ const result = await generateWearableMemories(
231
+ "limitless",
232
+ "2026-06-10",
233
+ [LONG_CONVERSATION],
234
+ settings({ memoryMode: "auto" }),
235
+ REGISTRY,
236
+ {
237
+ extract: extractionReturning([
238
+ { category: "fact", content: "Vendor call happens tomorrow morning.", confidence: 0.8, tags: [] },
239
+ ]),
240
+ writer,
241
+ },
242
+ );
243
+ assert.equal(result.created, 0);
244
+ assert.equal(result.skippedByReason["duplicate-existing"], 1);
245
+ });
246
+
227
247
  test("auto mode still refuses to write a high-impact personal claim active (#2294)", async () => {
228
248
  // `auto` has no trust scoring, and an operator may lower minConfidence to or
229
249
  // below the speculative ceiling the ambient clamp writes — which would let a
@@ -92,6 +92,11 @@ export function memoryStatusForMode(
92
92
  return mode === "auto" || mode === "smart" ? "active" : "pending_review";
93
93
  }
94
94
 
95
+ /** A fact write the fact-dedup window suppressed because a concurrent writer committed the same fact first. */
96
+ export interface WearableDuplicateWrite {
97
+ readonly duplicate: true;
98
+ }
99
+
95
100
  /** Narrow writer interface satisfied by `StorageManager`. */
96
101
  export interface WearableMemoryWriter {
97
102
  /**
@@ -106,7 +111,7 @@ export interface WearableMemoryWriter {
106
111
  status?: MemoryStatus;
107
112
  memoryKind?: "episode" | "note" | "box" | "dream" | "procedural";
108
113
  },
109
- ): Promise<MemoryWriteResult>;
114
+ ): Promise<MemoryWriteResult | WearableDuplicateWrite>;
110
115
  hasFactContentHash(content: string): Promise<boolean>;
111
116
  /**
112
117
  * Locate an earlier wearable write of the same content (any status).
@@ -663,7 +668,9 @@ export async function generateWearableMemories(
663
668
  // #1645: a tombstone-blocked wearable write lands pending_review (no active
664
669
  // copy) — count it as blocked, NOT created, so the sync summary and callers
665
670
  // don't report blocked content as successfully created active memories.
666
- if (writeResult.tombstoneBlocked) {
671
+ if ("duplicate" in writeResult) {
672
+ skip("duplicate-existing");
673
+ } else if (writeResult.tombstoneBlocked) {
667
674
  result.tombstoneBlocked += 1;
668
675
  } else {
669
676
  result.created += 1;
@@ -872,11 +879,11 @@ export async function importNativeMemories(
872
879
  // #1645: a tombstone-blocked native import lands pending_review (no active
873
880
  // copy) — count it as blocked, NOT imported. Still record the provider id in
874
881
  // importedIds so the next sync doesn't re-import and duplicate the pending
875
- // row; the block is permanent for this content.
876
- if (nativeWriteResult.tombstoneBlocked) {
877
- tombstoneBlocked += 1;
878
- } else {
879
- imported += 1;
882
+ // row; the block is permanent for this content. A write the dedup window
883
+ // suppressed (a concurrent writer committed the fact first) counts as neither.
884
+ if (!("duplicate" in nativeWriteResult)) {
885
+ if (nativeWriteResult.tombstoneBlocked) tombstoneBlocked += 1;
886
+ else imported += 1;
880
887
  }
881
888
  importedIds.push(memory.id);
882
889
  }
@@ -13,6 +13,7 @@ import {
13
13
  } from "./corrections.js";
14
14
  import { describeErrorForOperator, WearablesInputError } from "./errors.js";
15
15
  import { inferMemoryStatus } from "../memory-lifecycle-ledger-utils.js";
16
+ import { persistUnderFactDedupWindow } from "../orchestration/fact-dedup-window.js";
16
17
  import type { LocationConfig } from "../location/types.js";
17
18
  import { isSupportPassportPrivateMemory } from "../support-passport/card-projection.js";
18
19
  import {
@@ -194,7 +195,19 @@ export function createWearableMemoryWriter(
194
195
  storage: WearableStorageIo,
195
196
  ): WearableMemoryGenDeps["writer"] {
196
197
  return {
197
- writeSealedMemory: storage.writeSealedMemory.bind(storage),
198
+ writeSealedMemory: async (envelope, extras) => {
199
+ // Facts register in the fact hash index, so extraction and a wearable sync
200
+ // can commit the same new fact. Commit it under the window extraction uses
201
+ // and recheck the index there. Other categories keep the direct write: the
202
+ // index does not cover them, and the corpus scan stays outside the window.
203
+ if (envelope.category !== "fact") return storage.writeSealedMemory(envelope, extras);
204
+ const outcome = await persistUnderFactDedupWindow(
205
+ storage.dir,
206
+ () => storage.hasFactContentHash(extras.contentHashSource ?? envelope.content),
207
+ () => storage.writeSealedMemory(envelope, extras),
208
+ );
209
+ return outcome.suppressed ? { duplicate: true } : outcome.result;
210
+ },
198
211
  findWearableMemoryByContent: async (content: string) =>
199
212
  (await storage.findWearableMemoryByContent(content)) as
200
213
  | { id: string; status: import("../types.js").MemoryStatus | undefined }
@@ -5,7 +5,10 @@ import { tmpdir } from "node:os";
5
5
  import * as path from "node:path";
6
6
  import { test } from "node:test";
7
7
 
8
+ import { persistUnderFactDedupWindow } from "../orchestration/fact-dedup-window.js";
8
9
  import { StorageManager } from "../storage.js";
10
+ import { composeMemoryEnvelope } from "../write-envelope.js";
11
+ import { createWearableMemoryWriter } from "./service.js";
9
12
 
10
13
  function makeStorage(): { storage: StorageManager; dir: string } {
11
14
  const dir = mkdtempSync(path.join(tmpdir(), "remnic-wearable-io-"));
@@ -330,3 +333,25 @@ test("a symlinked _fusion dir that resolves INSIDE the memory dir is refused, no
330
333
  rmSync(dir, { recursive: true, force: true });
331
334
  }
332
335
  });
336
+
337
+ test("a wearable fact write loses to a fact another writer committed first and writes nothing", async () => {
338
+ const { storage, dir } = makeStorage();
339
+ try {
340
+ await storage.ensureDirectories();
341
+ const fact = "Dana moved the review meeting to Thursday afternoon.";
342
+ const writer = createWearableMemoryWriter(storage);
343
+ const envelope = composeMemoryEnvelope(
344
+ { content: fact, category: "fact", tags: ["wearable"] },
345
+ { source: "wearable:limitless" },
346
+ );
347
+ await persistUnderFactDedupWindow(storage.dir, async () => false, () => storage.writeMemory("fact", fact));
348
+
349
+ const written = await writer.writeSealedMemory(envelope, { contentHashSource: fact, status: "active" });
350
+
351
+ assert.deepEqual(written, { duplicate: true });
352
+ const copies = (await storage.readAllMemories()).filter((memory) => memory.content.includes(fact));
353
+ assert.equal(copies.length, 1, "the corpus holds one copy of the fact");
354
+ } finally {
355
+ rmSync(dir, { recursive: true, force: true });
356
+ }
357
+ });
@@ -1,43 +0,0 @@
1
- import {
2
- isNamespaceKind
3
- } from "./chunk-PUEO37QG.js";
4
- import {
5
- EngramAccessInputError
6
- } from "./chunk-CJCTKL6Y.js";
7
-
8
- // src/access-http-query.ts
9
- function optionalQueryString(value, label) {
10
- if (value === void 0 || value === null || value === "") return void 0;
11
- if (typeof value !== "string") {
12
- throw new EngramAccessInputError(`${label} must be a string (got ${JSON.stringify(value)})`);
13
- }
14
- return value;
15
- }
16
- function nonEmptyQueryParam(value) {
17
- return value !== null && value.length > 0 ? value : void 0;
18
- }
19
- function positiveIntQueryParam(value, label) {
20
- if (value === null || value.length === 0) return void 0;
21
- const parsed = Number(value);
22
- if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {
23
- throw new EngramAccessInputError(`${label} expects a positive integer`);
24
- }
25
- return parsed;
26
- }
27
- function optionalNamespaceKindQueryParam(value) {
28
- if (value === null || value.length === 0) return void 0;
29
- if (!isNamespaceKind(value)) {
30
- throw new EngramAccessInputError(
31
- "kind must be one of: default, shared, project, branch, team-project, explicit"
32
- );
33
- }
34
- return value;
35
- }
36
-
37
- export {
38
- optionalQueryString,
39
- nonEmptyQueryParam,
40
- positiveIntQueryParam,
41
- optionalNamespaceKindQueryParam
42
- };
43
- //# sourceMappingURL=chunk-27XYV2XL.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/access-http-query.ts"],"sourcesContent":["import { EngramAccessInputError } from \"./access-service.js\";\nimport { isNamespaceKind, type NamespaceKind } from \"./namespaces/catalog.js\";\n\n/** Optional string field from a JSON body: absent/null/\"\" → undefined. */\nexport function optionalQueryString(value: unknown, label: string): string | undefined {\n if (value === undefined || value === null || value === \"\") return undefined;\n if (typeof value !== \"string\") {\n throw new EngramAccessInputError(`${label} must be a string (got ${JSON.stringify(value)})`);\n }\n return value;\n}\n\n/** Optional non-empty query param: null/\"\" → undefined. */\nexport function nonEmptyQueryParam(value: string | null): string | undefined {\n return value !== null && value.length > 0 ? value : undefined;\n}\n\n/** Optional positive-integer query param; rejects invalid values. */\nexport function positiveIntQueryParam(value: string | null, label: string): number | undefined {\n if (value === null || value.length === 0) return undefined;\n const parsed = Number(value);\n if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {\n throw new EngramAccessInputError(`${label} expects a positive integer`);\n }\n return parsed;\n}\n\n/** Optional namespace-kind query param; rejects dead or unknown kinds. */\nexport function optionalNamespaceKindQueryParam(value: string | null): NamespaceKind | undefined {\n if (value === null || value.length === 0) return undefined;\n if (!isNamespaceKind(value)) {\n throw new EngramAccessInputError(\n \"kind must be one of: default, shared, project, branch, team-project, explicit\",\n );\n }\n return value;\n}\n"],"mappings":";;;;;;;;AAIO,SAAS,oBAAoB,OAAgB,OAAmC;AACrF,MAAI,UAAU,UAAa,UAAU,QAAQ,UAAU,GAAI,QAAO;AAClE,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,IAAI,uBAAuB,GAAG,KAAK,0BAA0B,KAAK,UAAU,KAAK,CAAC,GAAG;AAAA,EAC7F;AACA,SAAO;AACT;AAGO,SAAS,mBAAmB,OAA0C;AAC3E,SAAO,UAAU,QAAQ,MAAM,SAAS,IAAI,QAAQ;AACtD;AAGO,SAAS,sBAAsB,OAAsB,OAAmC;AAC7F,MAAI,UAAU,QAAQ,MAAM,WAAW,EAAG,QAAO;AACjD,QAAM,SAAS,OAAO,KAAK;AAC3B,MAAI,CAAC,OAAO,SAAS,MAAM,KAAK,UAAU,KAAK,CAAC,OAAO,UAAU,MAAM,GAAG;AACxE,UAAM,IAAI,uBAAuB,GAAG,KAAK,6BAA6B;AAAA,EACxE;AACA,SAAO;AACT;AAGO,SAAS,gCAAgC,OAAiD;AAC/F,MAAI,UAAU,QAAQ,MAAM,WAAW,EAAG,QAAO;AACjD,MAAI,CAAC,gBAAgB,KAAK,GAAG;AAC3B,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;","names":[]}