@remnic/core 10.0.50 → 10.0.52

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 (272) hide show
  1. package/dist/access-admin-ops-surface.js +22 -22
  2. package/dist/access-authorization-probe.js +23 -23
  3. package/dist/access-boundary.d.ts +6 -1
  4. package/dist/access-boundary.d.ts.map +1 -1
  5. package/dist/access-boundary.js +22 -22
  6. package/dist/access-cli.js +68 -68
  7. package/dist/access-errors.d.ts.map +1 -1
  8. package/dist/access-errors.js +1 -1
  9. package/dist/access-extraction-force-flush.js +22 -22
  10. package/dist/access-http-briefing.js +23 -23
  11. package/dist/access-http-query.js +24 -24
  12. package/dist/access-http.js +42 -42
  13. package/dist/access-identity-continuity-surface.js +22 -22
  14. package/dist/access-lcm-surface.js +22 -22
  15. package/dist/access-mcp.js +32 -32
  16. package/dist/access-memory-search-fanout.js +2 -2
  17. package/dist/access-namespace-preflight.js +22 -22
  18. package/dist/access-observe-write-category.js +2 -2
  19. package/dist/access-observe-write-surface.js +22 -22
  20. package/dist/access-offline-manifest.js +2 -2
  21. package/dist/access-operations-batch.js +23 -23
  22. package/dist/access-operations.js +30 -30
  23. package/dist/access-recall-concurrency.js +22 -22
  24. package/dist/access-recall-diagnostics.js +2 -2
  25. package/dist/access-recall-response.js +22 -22
  26. package/dist/access-recall-surface.js +22 -22
  27. package/dist/access-schema.d.ts +245 -773
  28. package/dist/access-schema.d.ts.map +1 -1
  29. package/dist/access-schema.js +1 -1
  30. package/dist/access-service.js +22 -22
  31. package/dist/access-surface-catalog.js +24 -24
  32. package/dist/access-token-capabilities.js +2 -2
  33. package/dist/active-recall.js +5 -5
  34. package/dist/briefing.js +3 -3
  35. package/dist/causal-consolidation.js +4 -4
  36. package/dist/{chunk-7YVER7ZO.js → chunk-2EXF6IIB.js} +2 -2
  37. package/dist/{chunk-HPNXS6W6.js → chunk-2ONKXEC7.js} +3 -3
  38. package/dist/{chunk-DMKTOCC7.js → chunk-2S6JADBB.js} +2 -2
  39. package/dist/{chunk-ZSEFGDCI.js → chunk-2TGIKMMZ.js} +2 -2
  40. package/dist/{chunk-Q3PNQYAC.js → chunk-44EUCSG4.js} +2 -2
  41. package/dist/{chunk-PJCVI44H.js → chunk-4FM35YIA.js} +3 -3
  42. package/dist/{chunk-M6TP6UYD.js → chunk-6ZAU7LAR.js} +2 -2
  43. package/dist/{chunk-SJTSWG2Z.js → chunk-72CBJ5VP.js} +3 -3
  44. package/dist/{chunk-EKYWZYZJ.js → chunk-75BK4XGV.js} +17 -17
  45. package/dist/{chunk-HKOMHP6D.js → chunk-A6JDT6QF.js} +2 -2
  46. package/dist/{chunk-TFWJXWL6.js → chunk-AHGOOZUC.js} +2 -2
  47. package/dist/{chunk-A7AISUJI.js → chunk-AJG2TAMR.js} +2 -2
  48. package/dist/{chunk-VCOMACR6.js → chunk-AZLD4CKG.js} +19 -19
  49. package/dist/{chunk-GXVXBYRA.js → chunk-B35EQQ7L.js} +3 -3
  50. package/dist/{chunk-E6UNM67F.js → chunk-B6SPPYGJ.js} +2 -2
  51. package/dist/{chunk-TXKQBQYW.js → chunk-CK6GCGMX.js} +3 -3
  52. package/dist/{chunk-NPLM7AFU.js → chunk-DF6ZF2BN.js} +3 -3
  53. package/dist/{chunk-6ZQX3R66.js → chunk-DJFULNXK.js} +4 -4
  54. package/dist/{chunk-JBWD3J5U.js → chunk-DQOGZZJA.js} +3 -3
  55. package/dist/{chunk-K5QQXVG6.js → chunk-EC2KVA27.js} +7 -7
  56. package/dist/{chunk-N74WJ7B2.js → chunk-EZS6QJNC.js} +2 -2
  57. package/dist/{chunk-MWURUSRE.js → chunk-FNNGWN3O.js} +2 -2
  58. package/dist/{chunk-5FUDSK6Z.js → chunk-FT75AQ74.js} +3 -3
  59. package/dist/{chunk-ZMO2S5RT.js → chunk-G6A3SGCE.js} +3 -3
  60. package/dist/chunk-G6A3SGCE.js.map +1 -0
  61. package/dist/{chunk-IMKOJVD4.js → chunk-GBGU5OZP.js} +52 -51
  62. package/dist/chunk-GBGU5OZP.js.map +1 -0
  63. package/dist/{chunk-5X2H3X4J.js → chunk-GE3ZX6YK.js} +2 -2
  64. package/dist/{chunk-UNOAVHV4.js → chunk-HCIRR3BA.js} +3 -3
  65. package/dist/{chunk-MLNXATFG.js → chunk-HEF4BRMT.js} +3 -3
  66. package/dist/{chunk-Z5277JWF.js → chunk-HQUYENKK.js} +7 -10
  67. package/dist/chunk-HQUYENKK.js.map +1 -0
  68. package/dist/{chunk-6UDLN4IA.js → chunk-I6ORDT54.js} +2 -2
  69. package/dist/{chunk-M4HRYLOA.js → chunk-IUQXRRJB.js} +3 -3
  70. package/dist/{chunk-GQIYL7OD.js → chunk-JIJAWTRK.js} +3 -3
  71. package/dist/{chunk-4KS4IHKH.js → chunk-JX3E4XCW.js} +2 -2
  72. package/dist/{chunk-ZFNXJEUG.js → chunk-KAJYSO3U.js} +2 -2
  73. package/dist/{chunk-QS765SBL.js → chunk-KETS5A62.js} +2 -2
  74. package/dist/{chunk-N7FNCE7F.js → chunk-KIWO4327.js} +2 -2
  75. package/dist/{chunk-FC3ZFEXF.js → chunk-KJ3MDX4V.js} +2 -2
  76. package/dist/{chunk-BNJAVRNJ.js → chunk-L3EFYRYK.js} +8 -8
  77. package/dist/{chunk-3MUKQQ7A.js → chunk-LGEH473S.js} +24 -29
  78. package/dist/chunk-LGEH473S.js.map +1 -0
  79. package/dist/{chunk-P7OVFGMU.js → chunk-LMB47UM6.js} +2 -2
  80. package/dist/{chunk-7SAE6T36.js → chunk-LWMLBVGB.js} +2 -2
  81. package/dist/{chunk-ZNRTHYYW.js → chunk-MC7PXJDP.js} +7 -7
  82. package/dist/{chunk-M2DOJBH7.js → chunk-MUNYUSBU.js} +2 -2
  83. package/dist/{chunk-LMMBV7QC.js → chunk-OCRLJJOV.js} +4 -4
  84. package/dist/{chunk-7J6A5MV2.js → chunk-ON77FQXH.js} +2 -2
  85. package/dist/{chunk-WV4UYDP6.js → chunk-POJRFSJM.js} +3 -3
  86. package/dist/{chunk-LKRXGOQW.js → chunk-QA3Y2O6H.js} +2 -2
  87. package/dist/{chunk-23ZLN5X6.js → chunk-QT5MGENX.js} +14 -6
  88. package/dist/chunk-QT5MGENX.js.map +1 -0
  89. package/dist/{chunk-YECEEJUU.js → chunk-RA7YRL6H.js} +2 -2
  90. package/dist/{chunk-UALXWWGN.js → chunk-RJDYDF5K.js} +4 -4
  91. package/dist/{chunk-WXJC2L66.js → chunk-ROAKLYOL.js} +2 -2
  92. package/dist/{chunk-NWIT3U2N.js → chunk-S5CQI2KF.js} +2 -2
  93. package/dist/{chunk-P6P35FY4.js → chunk-SDESKFKT.js} +2 -2
  94. package/dist/{chunk-UXGXJUZ5.js → chunk-SFRBUBUZ.js} +7 -7
  95. package/dist/{chunk-CRN3EPE2.js → chunk-SHNM7RIT.js} +3 -3
  96. package/dist/{chunk-XC3H7KON.js → chunk-SPTPBKZX.js} +2 -2
  97. package/dist/{chunk-IUYXCTA7.js → chunk-TC55M5PO.js} +2 -2
  98. package/dist/{chunk-EWBXMGXR.js → chunk-UCLJTGP3.js} +2 -2
  99. package/dist/{chunk-UZVQFGAY.js → chunk-UM3ZLT2X.js} +206 -165
  100. package/dist/chunk-UM3ZLT2X.js.map +1 -0
  101. package/dist/{chunk-754XN6LX.js → chunk-V27FK6D6.js} +2 -2
  102. package/dist/{chunk-LIBVANOF.js → chunk-V6Z4NAJZ.js} +2 -2
  103. package/dist/{chunk-XTXWR6KQ.js → chunk-VUMWVYMR.js} +7 -7
  104. package/dist/{chunk-ZSMA67FV.js → chunk-WY66J4PI.js} +2 -2
  105. package/dist/{chunk-5G53JSRO.js → chunk-XWJUFER3.js} +3 -3
  106. package/dist/{chunk-NSP5PIQH.js → chunk-Y3T5KQCR.js} +2 -2
  107. package/dist/{chunk-QTDZ2NYC.js → chunk-Y77FXULH.js} +2 -2
  108. package/dist/{chunk-IG747LRN.js → chunk-YO2JEX2Z.js} +2 -2
  109. package/dist/{chunk-KW5YZELC.js → chunk-ZKK3VMWU.js} +2 -2
  110. package/dist/cli.js +55 -55
  111. package/dist/coding/export-okf-codegraph.js +2 -2
  112. package/dist/compounding/engine.js +4 -4
  113. package/dist/config.js +5 -5
  114. package/dist/connectors/codex-materialize-runner.js +3 -3
  115. package/dist/connectors/index.js +7 -7
  116. package/dist/convergence-refresh.js +5 -5
  117. package/dist/corpus-watermark.js +7 -7
  118. package/dist/deep-recall-config.js +2 -2
  119. package/dist/deep-recall-http-glue.js +4 -4
  120. package/dist/{entity-canonical-id-migration-ZBNRGUNV.js → entity-canonical-id-migration-F3JVG6K6.js} +2 -2
  121. package/dist/entity-retrieval.js +3 -3
  122. package/dist/external-wiki-access.js +23 -23
  123. package/dist/extraction-span-wire.d.ts.map +1 -1
  124. package/dist/extraction-span-wire.js +1 -1
  125. package/dist/extraction.js +11 -11
  126. package/dist/harmonic-retrieval.js +3 -3
  127. package/dist/index.js +68 -68
  128. package/dist/location/index.js +6 -6
  129. package/dist/maintenance/memory-governance.js +3 -3
  130. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  131. package/dist/maintenance/rebuild-memory-projection-runner.js +5 -5
  132. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  133. package/dist/memory-browse-http-glue.js +2 -2
  134. package/dist/memory-browse-operations.js +23 -23
  135. package/dist/namespaces/migrate.js +4 -4
  136. package/dist/namespaces/storage.js +3 -3
  137. package/dist/offline-sync-impression-drain.js +2 -2
  138. package/dist/operator-doctor-corpus.js +10 -10
  139. package/dist/operator-doctor-replica.js +4 -4
  140. package/dist/operator-toolkit.js +17 -17
  141. package/dist/orchestration/extraction-persist-deps.d.ts +2 -0
  142. package/dist/orchestration/extraction-persist-deps.d.ts.map +1 -1
  143. package/dist/orchestration/extraction-persist-promotion.d.ts +40 -10
  144. package/dist/orchestration/extraction-persist-promotion.d.ts.map +1 -1
  145. package/dist/orchestration/extraction-persist.d.ts.map +1 -1
  146. package/dist/orchestration/maintenance.js +15 -15
  147. package/dist/orchestrator.d.ts.map +1 -1
  148. package/dist/orchestrator.js +68 -68
  149. package/dist/recall-navigation-http-glue.js +2 -2
  150. package/dist/recall-navigation-operations.js +23 -23
  151. package/dist/recall-navigation.js +22 -22
  152. package/dist/relay/mission.d.ts +731 -3403
  153. package/dist/relay/mission.d.ts.map +1 -1
  154. package/dist/replica-divergence.js +3 -3
  155. package/dist/resume-bundles.js +6 -6
  156. package/dist/schemas.d.ts +346 -1771
  157. package/dist/schemas.d.ts.map +1 -1
  158. package/dist/semantic-consolidation.js +4 -4
  159. package/dist/semantic-rule-promotion.js +3 -3
  160. package/dist/semantic-rule-verifier.js +3 -3
  161. package/dist/shared-context/manager.d.ts +13 -33
  162. package/dist/shared-context/manager.d.ts.map +1 -1
  163. package/dist/source-agent-qualifier.js +9 -9
  164. package/dist/standup/build.js +4 -4
  165. package/dist/storage.js +2 -2
  166. package/dist/support-passport/access-operations.d.ts +51 -51
  167. package/dist/support-passport/contracts.d.ts +85 -126
  168. package/dist/support-passport/contracts.d.ts.map +1 -1
  169. package/dist/support-passport/generated-batch.d.ts +3 -17
  170. package/dist/support-passport/generated-batch.d.ts.map +1 -1
  171. package/dist/support-passport/grant-contracts.d.ts +75 -381
  172. package/dist/support-passport/grant-contracts.d.ts.map +1 -1
  173. package/dist/support-passport/index.js +25 -25
  174. package/dist/support-passport/model-audit.d.ts +17 -82
  175. package/dist/support-passport/model-audit.d.ts.map +1 -1
  176. package/dist/support-passport/model-bridge.d.ts +12 -51
  177. package/dist/support-passport/model-bridge.d.ts.map +1 -1
  178. package/dist/support-passport/model-contracts.d.ts +53 -192
  179. package/dist/support-passport/model-contracts.d.ts.map +1 -1
  180. package/dist/tokens.js +3 -3
  181. package/dist/transfer/export-okf.js +3 -3
  182. package/dist/transfer/types.d.ts +32 -737
  183. package/dist/transfer/types.d.ts.map +1 -1
  184. package/dist/verified-recall.js +3 -3
  185. package/dist/who-knows-cli.js +5 -5
  186. package/dist/who-knows.js +4 -4
  187. package/package.json +3 -4
  188. package/src/access-boundary.test.ts +14 -0
  189. package/src/access-boundary.ts +48 -42
  190. package/src/access-category-alias.test.ts +9 -7
  191. package/src/access-errors.test.ts +21 -9
  192. package/src/access-errors.ts +45 -31
  193. package/src/access-http.test.ts +1 -1
  194. package/src/access-schema.ts +13 -17
  195. package/src/extraction-scope-source-agent.test.ts +6 -6
  196. package/src/extraction-span-wire.test.ts +28 -0
  197. package/src/extraction-span-wire.ts +34 -6
  198. package/src/orchestration/extraction-persist-deps.ts +3 -0
  199. package/src/orchestration/extraction-persist-promotion.ts +104 -16
  200. package/src/orchestration/extraction-persist.ts +66 -77
  201. package/src/orchestrator-entity-write-failure.test.ts +370 -0
  202. package/src/orchestrator.ts +1 -0
  203. package/src/storage/entity-canonical-id-migration.ts +2 -2
  204. package/dist/chunk-23ZLN5X6.js.map +0 -1
  205. package/dist/chunk-3MUKQQ7A.js.map +0 -1
  206. package/dist/chunk-IMKOJVD4.js.map +0 -1
  207. package/dist/chunk-UZVQFGAY.js.map +0 -1
  208. package/dist/chunk-Z5277JWF.js.map +0 -1
  209. package/dist/chunk-ZMO2S5RT.js.map +0 -1
  210. /package/dist/{chunk-7YVER7ZO.js.map → chunk-2EXF6IIB.js.map} +0 -0
  211. /package/dist/{chunk-HPNXS6W6.js.map → chunk-2ONKXEC7.js.map} +0 -0
  212. /package/dist/{chunk-DMKTOCC7.js.map → chunk-2S6JADBB.js.map} +0 -0
  213. /package/dist/{chunk-ZSEFGDCI.js.map → chunk-2TGIKMMZ.js.map} +0 -0
  214. /package/dist/{chunk-Q3PNQYAC.js.map → chunk-44EUCSG4.js.map} +0 -0
  215. /package/dist/{chunk-PJCVI44H.js.map → chunk-4FM35YIA.js.map} +0 -0
  216. /package/dist/{chunk-M6TP6UYD.js.map → chunk-6ZAU7LAR.js.map} +0 -0
  217. /package/dist/{chunk-SJTSWG2Z.js.map → chunk-72CBJ5VP.js.map} +0 -0
  218. /package/dist/{chunk-EKYWZYZJ.js.map → chunk-75BK4XGV.js.map} +0 -0
  219. /package/dist/{chunk-HKOMHP6D.js.map → chunk-A6JDT6QF.js.map} +0 -0
  220. /package/dist/{chunk-TFWJXWL6.js.map → chunk-AHGOOZUC.js.map} +0 -0
  221. /package/dist/{chunk-A7AISUJI.js.map → chunk-AJG2TAMR.js.map} +0 -0
  222. /package/dist/{chunk-VCOMACR6.js.map → chunk-AZLD4CKG.js.map} +0 -0
  223. /package/dist/{chunk-GXVXBYRA.js.map → chunk-B35EQQ7L.js.map} +0 -0
  224. /package/dist/{chunk-E6UNM67F.js.map → chunk-B6SPPYGJ.js.map} +0 -0
  225. /package/dist/{chunk-TXKQBQYW.js.map → chunk-CK6GCGMX.js.map} +0 -0
  226. /package/dist/{chunk-NPLM7AFU.js.map → chunk-DF6ZF2BN.js.map} +0 -0
  227. /package/dist/{chunk-6ZQX3R66.js.map → chunk-DJFULNXK.js.map} +0 -0
  228. /package/dist/{chunk-JBWD3J5U.js.map → chunk-DQOGZZJA.js.map} +0 -0
  229. /package/dist/{chunk-K5QQXVG6.js.map → chunk-EC2KVA27.js.map} +0 -0
  230. /package/dist/{chunk-N74WJ7B2.js.map → chunk-EZS6QJNC.js.map} +0 -0
  231. /package/dist/{chunk-MWURUSRE.js.map → chunk-FNNGWN3O.js.map} +0 -0
  232. /package/dist/{chunk-5FUDSK6Z.js.map → chunk-FT75AQ74.js.map} +0 -0
  233. /package/dist/{chunk-5X2H3X4J.js.map → chunk-GE3ZX6YK.js.map} +0 -0
  234. /package/dist/{chunk-UNOAVHV4.js.map → chunk-HCIRR3BA.js.map} +0 -0
  235. /package/dist/{chunk-MLNXATFG.js.map → chunk-HEF4BRMT.js.map} +0 -0
  236. /package/dist/{chunk-6UDLN4IA.js.map → chunk-I6ORDT54.js.map} +0 -0
  237. /package/dist/{chunk-M4HRYLOA.js.map → chunk-IUQXRRJB.js.map} +0 -0
  238. /package/dist/{chunk-GQIYL7OD.js.map → chunk-JIJAWTRK.js.map} +0 -0
  239. /package/dist/{chunk-4KS4IHKH.js.map → chunk-JX3E4XCW.js.map} +0 -0
  240. /package/dist/{chunk-ZFNXJEUG.js.map → chunk-KAJYSO3U.js.map} +0 -0
  241. /package/dist/{chunk-QS765SBL.js.map → chunk-KETS5A62.js.map} +0 -0
  242. /package/dist/{chunk-N7FNCE7F.js.map → chunk-KIWO4327.js.map} +0 -0
  243. /package/dist/{chunk-FC3ZFEXF.js.map → chunk-KJ3MDX4V.js.map} +0 -0
  244. /package/dist/{chunk-BNJAVRNJ.js.map → chunk-L3EFYRYK.js.map} +0 -0
  245. /package/dist/{chunk-P7OVFGMU.js.map → chunk-LMB47UM6.js.map} +0 -0
  246. /package/dist/{chunk-7SAE6T36.js.map → chunk-LWMLBVGB.js.map} +0 -0
  247. /package/dist/{chunk-ZNRTHYYW.js.map → chunk-MC7PXJDP.js.map} +0 -0
  248. /package/dist/{chunk-M2DOJBH7.js.map → chunk-MUNYUSBU.js.map} +0 -0
  249. /package/dist/{chunk-LMMBV7QC.js.map → chunk-OCRLJJOV.js.map} +0 -0
  250. /package/dist/{chunk-7J6A5MV2.js.map → chunk-ON77FQXH.js.map} +0 -0
  251. /package/dist/{chunk-WV4UYDP6.js.map → chunk-POJRFSJM.js.map} +0 -0
  252. /package/dist/{chunk-LKRXGOQW.js.map → chunk-QA3Y2O6H.js.map} +0 -0
  253. /package/dist/{chunk-YECEEJUU.js.map → chunk-RA7YRL6H.js.map} +0 -0
  254. /package/dist/{chunk-UALXWWGN.js.map → chunk-RJDYDF5K.js.map} +0 -0
  255. /package/dist/{chunk-WXJC2L66.js.map → chunk-ROAKLYOL.js.map} +0 -0
  256. /package/dist/{chunk-NWIT3U2N.js.map → chunk-S5CQI2KF.js.map} +0 -0
  257. /package/dist/{chunk-P6P35FY4.js.map → chunk-SDESKFKT.js.map} +0 -0
  258. /package/dist/{chunk-UXGXJUZ5.js.map → chunk-SFRBUBUZ.js.map} +0 -0
  259. /package/dist/{chunk-CRN3EPE2.js.map → chunk-SHNM7RIT.js.map} +0 -0
  260. /package/dist/{chunk-XC3H7KON.js.map → chunk-SPTPBKZX.js.map} +0 -0
  261. /package/dist/{chunk-IUYXCTA7.js.map → chunk-TC55M5PO.js.map} +0 -0
  262. /package/dist/{chunk-EWBXMGXR.js.map → chunk-UCLJTGP3.js.map} +0 -0
  263. /package/dist/{chunk-754XN6LX.js.map → chunk-V27FK6D6.js.map} +0 -0
  264. /package/dist/{chunk-LIBVANOF.js.map → chunk-V6Z4NAJZ.js.map} +0 -0
  265. /package/dist/{chunk-XTXWR6KQ.js.map → chunk-VUMWVYMR.js.map} +0 -0
  266. /package/dist/{chunk-ZSMA67FV.js.map → chunk-WY66J4PI.js.map} +0 -0
  267. /package/dist/{chunk-5G53JSRO.js.map → chunk-XWJUFER3.js.map} +0 -0
  268. /package/dist/{chunk-NSP5PIQH.js.map → chunk-Y3T5KQCR.js.map} +0 -0
  269. /package/dist/{chunk-QTDZ2NYC.js.map → chunk-Y77FXULH.js.map} +0 -0
  270. /package/dist/{chunk-IG747LRN.js.map → chunk-YO2JEX2Z.js.map} +0 -0
  271. /package/dist/{chunk-KW5YZELC.js.map → chunk-ZKK3VMWU.js.map} +0 -0
  272. /package/dist/{entity-canonical-id-migration-ZBNRGUNV.js.map → entity-canonical-id-migration-F3JVG6K6.js.map} +0 -0
@@ -0,0 +1,370 @@
1
+ import assert from "node:assert/strict";
2
+ import path from "node:path";
3
+ import test from "node:test";
4
+
5
+ import { SmartBuffer } from "./buffer.js";
6
+ import { resetExtractionLlmHealthForTests } from "./extraction-llm-health.js";
7
+ import { Orchestrator } from "./orchestrator.js";
8
+ import { ContentHashIndex } from "./storage.js";
9
+ import {
10
+ cleanupDir,
11
+ harmonicNodeStoreStatus,
12
+ makeHarmonicLifecycleConfig,
13
+ makeLifecycleConfig,
14
+ mkTempMemoryDir,
15
+ stubExtraction,
16
+ } from "./testing/orchestrator-lite.js";
17
+ import type { ExtractionResult } from "./types.js";
18
+
19
+ /** Owner contract for the private Orchestrator temporal/tag batch hook. */
20
+ const FACT = "Project Alpha uses a queue for background processing.";
21
+
22
+ for (const includeFact of [false, true]) {
23
+ test(`#3228: entity write failure retains input${includeFact ? " after a committed fact" : ""}`, async () => {
24
+ const memoryDir = await mkTempMemoryDir("entity-write-failure");
25
+ const config = makeLifecycleConfig(memoryDir, {
26
+ chunkingEnabled: false,
27
+ multiGraphMemoryEnabled: false,
28
+ factDeduplicationEnabled: true,
29
+ extractionMinImportanceLevel: "trivial",
30
+ entityActivityLogEnabled: false,
31
+ });
32
+ const orchestrator = new Orchestrator(config);
33
+ const storage = await orchestrator.getStorageForNamespace();
34
+ const writeEntity = storage.writeEntity.bind(storage);
35
+ try {
36
+ await storage.ensureDirectories();
37
+ const entityName = storage.normalizeEntityName("Alpha", "project");
38
+ await orchestrator.buffer.load();
39
+ const bufferKey = "entity-write-failure";
40
+ await orchestrator.buffer.addTurn(bufferKey, {
41
+ role: "user",
42
+ content: FACT,
43
+ sessionKey: bufferKey,
44
+ timestamp: "2026-10-01T12:00:00.000Z",
45
+ sourceValidAt: "2026-10-01T12:00:00.000Z",
46
+ persistProcessedFingerprint: true,
47
+ });
48
+ await orchestrator.buffer.flushPendingSave({ throwOnFailure: true });
49
+ const turns = orchestrator.buffer.getTurns(bufferKey);
50
+ assert.equal(turns.length, 1, "the source turn must be durably buffered before extraction");
51
+ const before = await storage.loadMeta();
52
+ const extractionCalls = stubExtraction(orchestrator, (): ExtractionResult => ({
53
+ facts: includeFact
54
+ ? [{ content: FACT, category: "fact", confidence: 0.9, tags: [] }]
55
+ : [],
56
+ entities: [{ name: "Alpha", type: "project", facts: ["Uses a queue"] }],
57
+ questions: [],
58
+ profileUpdates: [],
59
+ }));
60
+ const writeFailure = Object.assign(new Error("synthetic entity write denied"), { code: "EACCES" });
61
+ let entityWriteAttempts = 0;
62
+ storage.writeEntity = async (...args: Parameters<typeof writeEntity>) => {
63
+ entityWriteAttempts += 1;
64
+ assert.deepEqual(args.slice(0, 3), ["Alpha", "project", ["Uses a queue"]]);
65
+ throw writeFailure;
66
+ };
67
+ let commits = 0;
68
+ const options = { bufferKey, onDurableCommit: () => { commits += 1; } };
69
+
70
+ await assert.rejects(
71
+ orchestrator.runExtraction(turns, options),
72
+ (error: Error) => error === writeFailure,
73
+ "the original entity I/O failure must reach the caller",
74
+ );
75
+ assert.equal(entityWriteAttempts, 1, "the failure must come from the real entity persistence path");
76
+ assert.equal(commits, 0, "a failed batch must not signal durable completion");
77
+ assert.deepEqual(orchestrator.buffer.getTurns(bufferKey), turns);
78
+ const retainedBuffer = new SmartBuffer(config, storage);
79
+ await retainedBuffer.load();
80
+ assert.deepEqual(retainedBuffer.getTurns(bufferKey), turns, "source turns must remain on disk too");
81
+ const failedMeta = await storage.loadMeta();
82
+ assert.equal(failedMeta.processedExtractionFingerprints?.length ?? 0, 0);
83
+ assert.equal(failedMeta.extractionCount, before.extractionCount);
84
+ assert.equal(await storage.readEntity(entityName), "", "the failed entity was not written");
85
+
86
+ const factsBeforeRetry = (await storage.readAllMemories()).filter((m) => m.frontmatter.category === "fact");
87
+ assert.equal(factsBeforeRetry.length, includeFact ? 1 : 0);
88
+ if (includeFact) {
89
+ const hashes = new ContentHashIndex(path.join(storage.dir, "state"));
90
+ await hashes.load();
91
+ assert.equal(hashes.has(FACT), true, "the earlier fact's deferred hash must be durable before rejection");
92
+ }
93
+
94
+ storage.writeEntity = writeEntity;
95
+ const retried = await orchestrator.runExtraction(turns, options);
96
+ assert.equal(retried.status, "completed");
97
+ assert.equal(retried.persistedCount, 1, "retry writes the entity, not another copy of an earlier fact");
98
+ assert.equal(commits, 1);
99
+ assert.match(await storage.readEntity(entityName), /Uses a queue/);
100
+ assert.equal((await storage.listEntityNames()).length, 1);
101
+ const factsAfterRetry = (await storage.readAllMemories()).filter((m) => m.frontmatter.category === "fact");
102
+ assert.deepEqual(
103
+ factsAfterRetry.map((m) => m.frontmatter.id),
104
+ factsBeforeRetry.map((m) => m.frontmatter.id),
105
+ "retry must preserve the already committed fact without duplicating it",
106
+ );
107
+ assert.equal((await storage.loadMeta()).processedExtractionFingerprints?.length, 1);
108
+ assert.equal(orchestrator.buffer.getTurns(bufferKey).length, 0);
109
+ const clearedBuffer = new SmartBuffer(config, storage);
110
+ await clearedBuffer.load();
111
+ assert.equal(clearedBuffer.getTurns(bufferKey).length, 0);
112
+ assert.equal((await orchestrator.runExtraction(turns, options)).reason, "processed_fingerprint");
113
+ assert.equal(extractionCalls.length, 2, "only the failed attempt and successful retry call the extractor");
114
+ assert.equal(commits, 1, "processed replay must not emit a second commit");
115
+ } finally {
116
+ storage.writeEntity = writeEntity;
117
+ try {
118
+ await orchestrator.destroy();
119
+ } finally {
120
+ resetExtractionLlmHealthForTests();
121
+ await cleanupDir(memoryDir);
122
+ }
123
+ }
124
+ });
125
+ }
126
+
127
+ test("#3231: entity failure after a committed fact still runs deferred post-write maintenance", async () => {
128
+ // Partial-success path: fact (preference) is durable, later writeEntity throws.
129
+ // Batch-end finalization in persistExtraction (hash flush, behavior ledger,
130
+ // harmonic construction, temporal/tag index) and runExtraction's
131
+ // requestQmdMaintenance must still cover the earlier durable write. Retry
132
+ // exact-dedupes the fact (`continue`), so those effects are not re-queued.
133
+ const memoryDir = await mkTempMemoryDir("entity-write-failure-maintenance");
134
+ const preference =
135
+ "Prefers terse queue names for background jobs in Project Alpha.";
136
+ const config = makeHarmonicLifecycleConfig(memoryDir, {
137
+ chunkingEnabled: false,
138
+ multiGraphMemoryEnabled: false,
139
+ factDeduplicationEnabled: true,
140
+ extractionMinImportanceLevel: "trivial",
141
+ entityActivityLogEnabled: false,
142
+ queryAwareIndexingEnabled: true,
143
+ });
144
+ const orchestrator = new Orchestrator(config);
145
+ const storage = await orchestrator.getStorageForNamespace();
146
+ const writeEntity = storage.writeEntity.bind(storage);
147
+ const temporalIdBatches: string[][] = [];
148
+ // Bracket access is TypeScript's typed escape hatch for the private method; the spy only wraps and restores it.
149
+ const originalTemporal = orchestrator["updateTemporalTagIndexes"].bind(orchestrator);
150
+ orchestrator["updateTemporalTagIndexes"] = async (target, persistedIds) => {
151
+ temporalIdBatches.push([...persistedIds]);
152
+ return originalTemporal(target, persistedIds);
153
+ };
154
+ let qmdMaintenanceCalls = 0;
155
+ const originalQmd = orchestrator.requestQmdMaintenance.bind(orchestrator);
156
+ orchestrator.requestQmdMaintenance = () => {
157
+ qmdMaintenanceCalls += 1;
158
+ originalQmd();
159
+ };
160
+ try {
161
+ await storage.ensureDirectories();
162
+ await orchestrator.buffer.load();
163
+ const bufferKey = "entity-write-failure-maintenance";
164
+ await orchestrator.buffer.addTurn(bufferKey, {
165
+ role: "user",
166
+ content: preference,
167
+ sessionKey: bufferKey,
168
+ timestamp: "2026-10-01T12:00:00.000Z",
169
+ sourceValidAt: "2026-10-01T12:00:00.000Z",
170
+ persistProcessedFingerprint: true,
171
+ });
172
+ await orchestrator.buffer.flushPendingSave({ throwOnFailure: true });
173
+ const turns = orchestrator.buffer.getTurns(bufferKey);
174
+ assert.equal(turns.length, 1);
175
+ stubExtraction(orchestrator, (): ExtractionResult => ({
176
+ facts: [
177
+ {
178
+ content: preference,
179
+ category: "preference",
180
+ confidence: 0.95,
181
+ tags: ["queue-naming"],
182
+ },
183
+ ],
184
+ entities: [{ name: "Alpha", type: "project", facts: ["Uses a queue"] }],
185
+ questions: [],
186
+ profileUpdates: [],
187
+ }));
188
+ const writeFailure = Object.assign(new Error("synthetic entity write denied"), {
189
+ code: "EACCES",
190
+ });
191
+ storage.writeEntity = async () => {
192
+ throw writeFailure;
193
+ };
194
+
195
+ await assert.rejects(
196
+ orchestrator.runExtraction(turns, { bufferKey }),
197
+ (error: Error) => error === writeFailure,
198
+ "entity I/O failure must still reach the caller",
199
+ );
200
+
201
+ const facts = (await storage.readAllMemories()).filter(
202
+ (m) => m.frontmatter.category === "preference",
203
+ );
204
+ assert.equal(facts.length, 1, "the preference fact must remain durable");
205
+ const factId = facts[0]!.frontmatter.id;
206
+ const hashes = new ContentHashIndex(path.join(storage.dir, "state"));
207
+ await hashes.load();
208
+ assert.equal(hashes.has(preference), true, "deferred fact hash must flush before rejection");
209
+
210
+ const signals = await storage.readBehaviorSignals(50);
211
+ assert.ok(
212
+ signals.some(
213
+ (s) => s.memoryId === factId && s.signalType === "preference_affinity",
214
+ ),
215
+ "batch-end behavior-signal append must cover the committed preference before rethrow",
216
+ );
217
+
218
+ const harmonic = await harmonicNodeStoreStatus(memoryDir);
219
+ assert.equal(harmonic.nodes.invalid, 0);
220
+ assert.ok(
221
+ harmonic.nodes.valid > 0 && (harmonic.latestNode?.sourceMemoryIds ?? []).includes(factId),
222
+ "batch-end harmonic construction must cover the committed fact before rethrow",
223
+ );
224
+
225
+ assert.ok(
226
+ temporalIdBatches.some((ids) => ids.includes(factId)),
227
+ "batch-end temporal/tag index update must include the committed fact before rethrow",
228
+ );
229
+ assert.ok(
230
+ qmdMaintenanceCalls > 0,
231
+ "partial durable writes must still schedule QMD reindex before the entity error propagates",
232
+ );
233
+
234
+ // Retry exact-dedupes the preference (`continue`), so missing deferred
235
+ // effects are not re-queued. Prove the failure-path finalization is the
236
+ // only chance they have to land.
237
+ storage.writeEntity = writeEntity;
238
+ const retried = await orchestrator.runExtraction(turns, { bufferKey });
239
+ assert.equal(retried.status, "completed");
240
+ assert.equal(
241
+ (await storage.readAllMemories()).filter((m) => m.frontmatter.category === "preference").length,
242
+ 1,
243
+ "retry must not duplicate the already-committed preference",
244
+ );
245
+ assert.ok(
246
+ (await storage.readBehaviorSignals(50)).some(
247
+ (s) => s.memoryId === factId && s.signalType === "preference_affinity",
248
+ ),
249
+ "behavior signals must survive retry without relying on re-queue from the deduped fact",
250
+ );
251
+ assert.ok(
252
+ ((await harmonicNodeStoreStatus(memoryDir)).latestNode?.sourceMemoryIds ?? []).includes(factId),
253
+ "harmonic records must survive retry without relying on re-queue from the deduped fact",
254
+ );
255
+ } finally {
256
+ storage.writeEntity = writeEntity;
257
+ orchestrator.requestQmdMaintenance = originalQmd;
258
+ orchestrator["updateTemporalTagIndexes"] = originalTemporal;
259
+ try {
260
+ await orchestrator.destroy();
261
+ } finally {
262
+ resetExtractionLlmHealthForTests();
263
+ await cleanupDir(memoryDir);
264
+ }
265
+ }
266
+ });
267
+
268
+ test("#3231 P2: a post-write contradiction-resolve failure still finalizes the durable fact", async () => {
269
+ // The fact is durable (writeSealedMemory returned) when
270
+ // applyDeferredContradictionResolve rejects. Its behavior signal, harmonic
271
+ // node, temporal/tag index entry and QMD request must not depend on code
272
+ // that runs after that catch; retry exact-dedupes the fact.
273
+ const memoryDir = await mkTempMemoryDir("contradiction-resolve-failure-maintenance");
274
+ const preference = "Prefers terse queue names for background jobs in Project Beta.";
275
+ const config = makeHarmonicLifecycleConfig(memoryDir, {
276
+ chunkingEnabled: false,
277
+ multiGraphMemoryEnabled: false,
278
+ factDeduplicationEnabled: true,
279
+ extractionMinImportanceLevel: "trivial",
280
+ entityActivityLogEnabled: false,
281
+ queryAwareIndexingEnabled: true,
282
+ });
283
+ const orchestrator = new Orchestrator(config);
284
+ const storage = await orchestrator.getStorageForNamespace();
285
+ const temporalIdBatches: string[][] = [];
286
+ const originalTemporal = orchestrator["updateTemporalTagIndexes"].bind(orchestrator);
287
+ orchestrator["updateTemporalTagIndexes"] = async (target, persistedIds) => {
288
+ temporalIdBatches.push([...persistedIds]);
289
+ return originalTemporal(target, persistedIds);
290
+ };
291
+ let qmdMaintenanceCalls = 0;
292
+ const originalQmd = orchestrator.requestQmdMaintenance.bind(orchestrator);
293
+ orchestrator.requestQmdMaintenance = () => {
294
+ qmdMaintenanceCalls += 1;
295
+ originalQmd();
296
+ };
297
+ const originalResolve = orchestrator["applyDeferredContradictionResolve"].bind(orchestrator);
298
+ const resolveFailure = new Error("synthetic contradiction resolve failure");
299
+ orchestrator["applyDeferredContradictionResolve"] = async () => {
300
+ throw resolveFailure;
301
+ };
302
+ try {
303
+ await storage.ensureDirectories();
304
+ await orchestrator.buffer.load();
305
+ const bufferKey = "contradiction-resolve-failure-maintenance";
306
+ await orchestrator.buffer.addTurn(bufferKey, {
307
+ role: "user",
308
+ content: preference,
309
+ sessionKey: bufferKey,
310
+ timestamp: "2026-10-01T12:00:00.000Z",
311
+ sourceValidAt: "2026-10-01T12:00:00.000Z",
312
+ persistProcessedFingerprint: true,
313
+ });
314
+ await orchestrator.buffer.flushPendingSave({ throwOnFailure: true });
315
+ const turns = orchestrator.buffer.getTurns(bufferKey);
316
+ stubExtraction(orchestrator, (): ExtractionResult => ({
317
+ facts: [
318
+ {
319
+ content: preference,
320
+ category: "preference",
321
+ confidence: 0.95,
322
+ tags: ["queue-naming"],
323
+ },
324
+ ],
325
+ entities: [],
326
+ questions: [],
327
+ profileUpdates: [],
328
+ }));
329
+
330
+ await assert.rejects(
331
+ orchestrator.runExtraction(turns, { bufferKey }),
332
+ (error: Error) => error === resolveFailure,
333
+ "the post-write failure must still reach the caller",
334
+ );
335
+
336
+ const facts = (await storage.readAllMemories()).filter(
337
+ (m) => m.frontmatter.category === "preference",
338
+ );
339
+ assert.equal(facts.length, 1, "the preference fact must remain durable");
340
+ const factId = facts[0]!.frontmatter.id;
341
+ assert.ok(
342
+ (await storage.readBehaviorSignals(50)).some(
343
+ (s) => s.memoryId === factId && s.signalType === "preference_affinity",
344
+ ),
345
+ "the durable fact's behavior signal must land before the rethrow",
346
+ );
347
+ assert.ok(
348
+ ((await harmonicNodeStoreStatus(memoryDir)).latestNode?.sourceMemoryIds ?? []).includes(factId),
349
+ "the durable fact's harmonic node must land before the rethrow",
350
+ );
351
+ assert.ok(
352
+ temporalIdBatches.some((ids) => ids.includes(factId)),
353
+ "the durable fact's temporal/tag index update must run before the rethrow",
354
+ );
355
+ assert.ok(
356
+ qmdMaintenanceCalls > 0,
357
+ "a durable fact that is the only write must still schedule QMD reindex",
358
+ );
359
+ } finally {
360
+ orchestrator["applyDeferredContradictionResolve"] = originalResolve;
361
+ orchestrator.requestQmdMaintenance = originalQmd;
362
+ orchestrator["updateTemporalTagIndexes"] = originalTemporal;
363
+ try {
364
+ await orchestrator.destroy();
365
+ } finally {
366
+ resetExtractionLlmHealthForTests();
367
+ await cleanupDir(memoryDir);
368
+ }
369
+ }
370
+ });
@@ -865,6 +865,7 @@ export class Orchestrator {
865
865
  invalidateGraphEdgeCache: (storage) => this.graphIndexFor(storage).invalidateEdgeCache(),
866
866
  updateTemporalTagIndexes: (storage, persistedIds) =>
867
867
  this.updateTemporalTagIndexes(storage, persistedIds),
868
+ requestQmdMaintenance: () => this.requestQmdMaintenance(),
868
869
  });
869
870
  }
870
871
  return this._extractionPersistCoordinator;
@@ -46,7 +46,7 @@ const ENTITY_MAPPING_RESCAN_MAX_PASSES = 3;
46
46
  const EntityCanonicalIdMigrationStateSchema = z.object({
47
47
  version: z.literal(ENTITY_CANONICAL_ID_MIGRATION_VERSION),
48
48
  complete: z.boolean(),
49
- mappings: z.record(z.string().min(1)),
49
+ mappings: z.record(z.string(), z.string().min(1)),
50
50
  completionFingerprint: z.string().optional(),
51
51
  /**
52
52
  * Pairs the migration refused to resolve, PARKED rather than dropped.
@@ -55,7 +55,7 @@ const EntityCanonicalIdMigrationStateSchema = z.object({
55
55
  * still naming the legacy id would never be rewritten. Optional so a journal
56
56
  * written by an older build still parses.
57
57
  */
58
- blocked: z.record(z.string().min(1)).optional(),
58
+ blocked: z.record(z.string(), z.string().min(1)).optional(),
59
59
  });
60
60
 
61
61
  type EntityCanonicalIdMigrationState = z.infer<typeof EntityCanonicalIdMigrationStateSchema>;
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/extraction-span-wire.ts"],"sourcesContent":["/**\n * Extraction seam for span mode (issue #2333 leftover slice + #2952).\n *\n * Default off. Callers pass `enabled` to `applySpanMode`. The provider JSON\n * schema is mode-aware so off-mode structured output cannot advertise `span`.\n */\n\nimport { zodToJsonSchema } from \"zod-to-json-schema\";\nimport type { ExtractionSpanConfig } from \"./extraction-span-config.js\";\nimport { parseSpanOffsets, type ParsedSpan } from \"./extraction-span.js\";\nimport { ExtractionProviderOutputSchema } from \"./schemas.js\";\n\nexport function applySpanMode(opts: {\n enabled?: boolean | 0 | 1;\n text: string;\n start: number;\n end: number;\n}): ParsedSpan {\n return parseSpanOffsets(opts.text, { start: opts.start, end: opts.end }, opts.enabled);\n}\n\nconst JSON_SCHEMA_OPTS = { $refStrategy: \"none\" as const, effectStrategy: \"input\" as const };\n\nconst EXTRACTION_PROVIDER_JSON_SCHEMA_WITH_SPAN = zodToJsonSchema(\n ExtractionProviderOutputSchema,\n JSON_SCHEMA_OPTS,\n) as Record<string, unknown>;\n\ntype FactItems = {\n properties?: Record<string, unknown>;\n required?: string[];\n};\n\nfunction omitFactSpan(schema: Record<string, unknown>): Record<string, unknown> {\n const clone = structuredClone(schema) as {\n properties?: { facts?: { items?: FactItems } };\n };\n const items = clone.properties?.facts?.items;\n if (!items?.properties || !(\"span\" in items.properties)) {\n throw new Error(\"extraction provider schema is missing facts.items.properties.span\");\n }\n delete items.properties.span;\n if (Array.isArray(items.required)) {\n items.required = items.required.filter((key) => key !== \"span\");\n }\n return clone as Record<string, unknown>;\n}\n\nconst EXTRACTION_PROVIDER_JSON_SCHEMA_WITHOUT_SPAN = omitFactSpan(\n EXTRACTION_PROVIDER_JSON_SCHEMA_WITH_SPAN,\n);\n\n/** Provider JSON schema: `span` is present in on/shadow, absent in off. */\nexport function extractionProviderJsonSchema(\n spanMode: ExtractionSpanConfig[\"spanMode\"] = \"off\",\n): Record<string, unknown> {\n return spanMode === \"off\"\n ? EXTRACTION_PROVIDER_JSON_SCHEMA_WITHOUT_SPAN\n : EXTRACTION_PROVIDER_JSON_SCHEMA_WITH_SPAN;\n}\n\nexport function extractionProviderResponseFormat(spanMode: ExtractionSpanConfig[\"spanMode\"] = \"off\") {\n return {\n type: \"json_schema\" as const,\n json_schema: {\n name: \"extraction_result\",\n schema: extractionProviderJsonSchema(spanMode),\n strict: false,\n },\n };\n}\n"],"mappings":";;;;;;;;AAOA,SAAS,uBAAuB;AAKzB,SAAS,cAAc,MAKf;AACb,SAAO,iBAAiB,KAAK,MAAM,EAAE,OAAO,KAAK,OAAO,KAAK,KAAK,IAAI,GAAG,KAAK,OAAO;AACvF;AAEA,IAAM,mBAAmB,EAAE,cAAc,QAAiB,gBAAgB,QAAiB;AAE3F,IAAM,4CAA4C;AAAA,EAChD;AAAA,EACA;AACF;AAOA,SAAS,aAAa,QAA0D;AAC9E,QAAM,QAAQ,gBAAgB,MAAM;AAGpC,QAAM,QAAQ,MAAM,YAAY,OAAO;AACvC,MAAI,CAAC,OAAO,cAAc,EAAE,UAAU,MAAM,aAAa;AACvD,UAAM,IAAI,MAAM,mEAAmE;AAAA,EACrF;AACA,SAAO,MAAM,WAAW;AACxB,MAAI,MAAM,QAAQ,MAAM,QAAQ,GAAG;AACjC,UAAM,WAAW,MAAM,SAAS,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,EAChE;AACA,SAAO;AACT;AAEA,IAAM,+CAA+C;AAAA,EACnD;AACF;AAGO,SAAS,6BACd,WAA6C,OACpB;AACzB,SAAO,aAAa,QAChB,+CACA;AACN;AAEO,SAAS,iCAAiC,WAA6C,OAAO;AACnG,SAAO;AAAA,IACL,MAAM;AAAA,IACN,aAAa;AAAA,MACX,MAAM;AAAA,MACN,QAAQ,6BAA6B,QAAQ;AAAA,MAC7C,QAAQ;AAAA,IACV;AAAA,EACF;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/access-errors.ts"],"sourcesContent":["/**\n * Tool-surface error enrichment: nearest-tool suggestions and arg-shape hints.\n *\n * Each function is a pure message builder — it returns a better error string\n * without changing semantics, status codes, or rejection behavior.\n *\n * @module access-errors\n */\n\nimport type { ZodError } from \"zod\";\n\n// ─── Existing error classes (restored from pre-#3042) ─────────────────────\n\n/** Authenticated caller lacks the required authorization (HTTP 403). */\nexport class EngramAccessForbiddenError extends Error {}\n\n/** Invalid caller input (HTTP 400). */\nexport class EngramAccessInputError extends Error {}\n\n/**\n * A write rejected by the namespace write-ACL (issue #1888). Subclasses\n * EngramAccessInputError so every existing catch/HTTP-400 mapping still\n * applies, but carries the attempted namespace + principal so the observe/\n * write surfaces can dead-letter the payload before re-throwing (fail-closed\n * placement is unchanged; only the destroyed-payload behavior is fixed).\n */\nexport class NamespaceNotWritableError extends EngramAccessInputError {\n constructor(\n readonly attemptedNamespace: string,\n readonly principal: string | undefined,\n message?: string,\n ) {\n super(message ?? `namespace is not writable: ${attemptedNamespace}`);\n this.name = \"NamespaceNotWritableError\";\n }\n}\n\n// ─── Levenshtein distance ─────────────────────────────────────────────────\n\n/**\n * Standard Levenshtein edit distance. O(n*m) over bounded inputs.\n * Exported so the test can verify it without importing private helpers.\n */\nexport function levenshtein(a: string, b: string): number {\n const an = a.length;\n const bn = b.length;\n // Fast paths for empty strings.\n if (an === 0) return bn;\n if (bn === 0) return an;\n\n // Use two rows to keep memory O(min(n,m)).\n let prev: number[] = [];\n let curr: number[] = [];\n\n // Align loops to the shorter string.\n const [shorter, longer, sn, ln] =\n an < bn ? [a, b, an, bn] : [b, a, bn, an];\n\n for (let i = 0; i <= sn; i++) prev[i] = i;\n\n for (let j = 1; j <= ln; j++) {\n curr[0] = j;\n for (let i = 1; i <= sn; i++) {\n const cost = shorter[i - 1] === longer[j - 1] ? 0 : 1;\n curr[i] = Math.min(\n prev[i] + 1, // deletion\n curr[i - 1] + 1, // insertion\n prev[i - 1] + cost, // substitution\n );\n }\n [prev, curr] = [curr, prev];\n }\n return prev[sn];\n}\n\n// ─── Similar-name helpers ─────────────────────────────────────────────────\n\nconst SUGGESTION_DISTANCE_CAP = 3;\n/**\n * Upper bound on the requested tool name. Anything longer is rejected before\n * the O(n*m) Levenshtein pass (#3042 review, P2): a 128 KB request over ~30\n * tools would otherwise run hundreds of millions of comparisons and block\n * the server event loop. The repo documents 64 chars as the tool-name shape;\n * this matches the limit enforced by every published surface.\n */\nexport const SUGGESTION_REQUEST_MAX_LEN = 64;\n\nexport interface SuggestionCandidate {\n name: string;\n distance: number;\n}\n\n/**\n * Find the nearest registered tool names by Levenshtein distance.\n * Results are sorted by distance ascending then name ascending (deterministic).\n * Only names with distance <= {@link SUGGESTION_DISTANCE_CAP} are returned;\n * when no name is that close the caller falls back to listing the full set.\n */\nexport function nearestSuggestions(\n requested: string,\n registeredNames: readonly string[],\n): SuggestionCandidate[] {\n // Defense in depth: the entry point already caps; this guards any direct\n // caller of the helper from an unbounded Levenshtein pass (issue #3042).\n if (requested.length > SUGGESTION_REQUEST_MAX_LEN) return [];\n const candidates: SuggestionCandidate[] = [];\n for (const name of registeredNames) {\n const distance = levenshtein(requested.toLowerCase(), name.toLowerCase());\n if (distance <= SUGGESTION_DISTANCE_CAP) {\n candidates.push({ name, distance });\n }\n }\n // Total comparator: distance asc, then name asc.\n candidates.sort((a, b) => {\n if (a.distance !== b.distance) return a.distance < b.distance ? -1 : 1;\n return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;\n });\n return candidates;\n}\n\n/**\n * Build a human-readable \"did you mean?\" suggestion string.\n * When no close match is found, returns a fallback listing.\n */\nfunction buildSuggestion(\n requested: string,\n registeredNames: readonly string[],\n): string {\n const suggestions = nearestSuggestions(requested, registeredNames);\n if (suggestions.length === 0) {\n return `Registered tools: ${registeredNames.join(\", \")}`;\n }\n return `Did you mean ${suggestions.map((s) => s.name).join(\", \")}?`;\n}\n\n/**\n * Read a Zod type tag without asserting a shape.\n *\n * Zod keeps its discriminant at `_def.typeName`. Both hops are narrowed with\n * `in`/`typeof` rather than an inline cast, so a non-Zod object yields\n * `undefined` instead of a fabricated read.\n */\nfunction zodTypeName(schema: unknown): string | undefined {\n if (typeof schema !== \"object\" || schema === null || !(\"_def\" in schema)) return undefined;\n const def = schema._def;\n if (typeof def !== \"object\" || def === null || !(\"typeName\" in def)) return undefined;\n return typeof def.typeName === \"string\" ? def.typeName : undefined;\n}\n\n/** Read `_def.<key>` with the same narrowing discipline. */\nfunction zodDefField(schema: unknown, key: string): unknown {\n if (typeof schema !== \"object\" || schema === null || !(\"_def\" in schema)) return undefined;\n const def = schema._def;\n if (typeof def !== \"object\" || def === null) return undefined;\n return Object.hasOwn(def, key) ? (def as Record<string, unknown>)[key] : undefined;\n}\n\n/**\n * Reverse-engineer ONE synthetic example value from a Zod schema type.\n * Returns a deterministic placeholder intended to round-trip through parse().\n */\nfunction exampleFromSchema(schema: unknown): unknown {\n switch (zodTypeName(schema)) {\n case \"ZodString\":\n return \"<string>\";\n case \"ZodNumber\":\n return 42;\n case \"ZodBoolean\":\n return true;\n case \"ZodNullable\":\n return null;\n case \"ZodArray\":\n return [];\n case \"ZodOptional\":\n // Optional fields are omitted from the example rather than sent as null.\n return undefined;\n case \"ZodEnum\": {\n const values = zodDefField(schema, \"values\");\n return Array.isArray(values) && values.length > 0 ? values[0] : \"<value>\";\n }\n case \"ZodObject\": {\n const shapeFn = zodDefField(schema, \"shape\");\n const shape = typeof shapeFn === \"function\" ? shapeFn() : undefined;\n if (typeof shape !== \"object\" || shape === null) return {};\n const result: Record<string, unknown> = {};\n for (const key of Object.getOwnPropertyNames(shape)) {\n if (!Object.hasOwn(shape, key)) continue;\n const field = (shape as Record<string, unknown>)[key];\n const value = exampleFromSchema(field);\n if (value !== undefined) result[key] = value;\n }\n return result;\n }\n default:\n return undefined;\n }\n}\n/**\n * Build an enriched \"unknown tool\" error message.\n *\n * @param requested - the tool name the caller used\n * @param registeredNames - ALL registered tool names (including aliases)\n */\nexport function unknownToolError(\n requested: string,\n registeredNames: readonly string[],\n): string {\n // Cap the request before the suggestion pass: a 128 KB name would otherwise\n // pay O(n*m) per registered tool (issue #3042 review, P2). The cap matches\n // the documented tool-name shape; an over-long request is rejected with a\n // teaching message rather than a silent truncate.\n if (requested.length > SUGGESTION_REQUEST_MAX_LEN) {\n return `Unknown tool: name exceeds ${SUGGESTION_REQUEST_MAX_LEN} characters (got ${requested.length}).`;\n }\n const suggestion = buildSuggestion(requested, registeredNames);\n return `Unknown tool: ${requested}. ${suggestion}`;\n}\n\n/**\n * Build an enriched \"invalid arguments\" error message.\n *\n * The example is generated from the schema (never hand-written, so it cannot\n * drift) and is shown ONLY when it round-trips through that same schema. It\n * carries schema-derived fields only: injecting the tool name would add an\n * unrecognized property and fail a `.strict()` object (#3035 review finding).\n *\n * @param tool - the tool name that received the invalid args\n * @param zodError - the ZodError from parse()\n * @param schema - the original Zod schema, used to generate the example\n */\nexport function invalidArgsError(\n tool: string,\n zodError: ZodError,\n schema?: unknown,\n): string {\n const issues = zodError.issues.map((issue) => {\n const path = issue.path.length > 0 ? issue.path.join(\".\") : \"(root)\";\n // `expected` exists only on the invalid_type / invalid_literal variants of\n // ZodIssue, so it is read through a presence check rather than assumed.\n const expected = \"expected\" in issue ? issue.expected : undefined;\n return expected === undefined\n ? `${path}: ${issue.message}`\n : `${path}: ${issue.message} (expected ${String(expected)})`;\n });\n let message = `Invalid arguments for \"${tool}\": ${issues.join(\"; \")}`;\n if (!schema) return message;\n\n const example = exampleFromSchema(schema);\n if (!example || typeof example !== \"object\" || Object.keys(example).length === 0) {\n return message;\n }\n\n // Withhold an example that cannot parse against its own schema: a\n // non-round-tripping example is misinformation, worse than none. Narrowed\n // with `in`/`typeof` rather than an inline cast so the shape is checked.\n let roundTrips = false;\n if (typeof schema === \"object\" && \"safeParse\" in schema) {\n const parser = schema.safeParse;\n if (typeof parser === \"function\") {\n try {\n const parsed: unknown = parser.call(schema, example);\n roundTrips =\n typeof parsed === \"object\"\n && parsed !== null\n && \"success\" in parsed\n && parsed.success === true;\n } catch {\n roundTrips = false;\n }\n }\n }\n\n return roundTrips ? `${message}. Example arguments: ${JSON.stringify(example)}` : message;\n}\n"],"mappings":"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","names":[]}