@astragenie/astramemory-local 0.7.2

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 (493) hide show
  1. package/CHANGELOG.md +341 -0
  2. package/README.md +419 -0
  3. package/dist/backup/retention.d.ts +15 -0
  4. package/dist/backup/retention.js +62 -0
  5. package/dist/backup/retention.js.map +1 -0
  6. package/dist/backup/snapshot.d.ts +21 -0
  7. package/dist/backup/snapshot.js +55 -0
  8. package/dist/backup/snapshot.js.map +1 -0
  9. package/dist/backup/verify.d.ts +23 -0
  10. package/dist/backup/verify.js +77 -0
  11. package/dist/backup/verify.js.map +1 -0
  12. package/dist/budget/tracker.d.ts +58 -0
  13. package/dist/budget/tracker.js +102 -0
  14. package/dist/budget/tracker.js.map +1 -0
  15. package/dist/capture/codex.d.ts +63 -0
  16. package/dist/capture/codex.js +0 -0
  17. package/dist/capture/codex.js.map +1 -0
  18. package/dist/cli/backup.d.ts +1 -0
  19. package/dist/cli/backup.js +112 -0
  20. package/dist/cli/backup.js.map +1 -0
  21. package/dist/cli/budget.d.ts +7 -0
  22. package/dist/cli/budget.js +44 -0
  23. package/dist/cli/budget.js.map +1 -0
  24. package/dist/cli/capture.d.ts +10 -0
  25. package/dist/cli/capture.js +113 -0
  26. package/dist/cli/capture.js.map +1 -0
  27. package/dist/cli/consolidate.d.ts +16 -0
  28. package/dist/cli/consolidate.js +146 -0
  29. package/dist/cli/consolidate.js.map +1 -0
  30. package/dist/cli/doctor.d.ts +1 -0
  31. package/dist/cli/doctor.js +54 -0
  32. package/dist/cli/doctor.js.map +1 -0
  33. package/dist/cli/entity-backfill.d.ts +10 -0
  34. package/dist/cli/entity-backfill.js +46 -0
  35. package/dist/cli/entity-backfill.js.map +1 -0
  36. package/dist/cli/hook-install.d.ts +45 -0
  37. package/dist/cli/hook-install.js +77 -0
  38. package/dist/cli/hook-install.js.map +1 -0
  39. package/dist/cli/index.d.ts +2 -0
  40. package/dist/cli/index.js +312 -0
  41. package/dist/cli/index.js.map +1 -0
  42. package/dist/cli/init.d.ts +16 -0
  43. package/dist/cli/init.js +431 -0
  44. package/dist/cli/init.js.map +1 -0
  45. package/dist/cli/mcp-stdio.d.ts +18 -0
  46. package/dist/cli/mcp-stdio.js +67 -0
  47. package/dist/cli/mcp-stdio.js.map +1 -0
  48. package/dist/cli/memory.d.ts +15 -0
  49. package/dist/cli/memory.js +52 -0
  50. package/dist/cli/memory.js.map +1 -0
  51. package/dist/cli/open-runtime-db.d.ts +15 -0
  52. package/dist/cli/open-runtime-db.js +37 -0
  53. package/dist/cli/open-runtime-db.js.map +1 -0
  54. package/dist/cli/pair.d.ts +29 -0
  55. package/dist/cli/pair.js +64 -0
  56. package/dist/cli/pair.js.map +1 -0
  57. package/dist/cli/providers.d.ts +10 -0
  58. package/dist/cli/providers.js +97 -0
  59. package/dist/cli/providers.js.map +1 -0
  60. package/dist/cli/queue-purge.d.ts +5 -0
  61. package/dist/cli/queue-purge.js +92 -0
  62. package/dist/cli/queue-purge.js.map +1 -0
  63. package/dist/cli/queue.d.ts +29 -0
  64. package/dist/cli/queue.js +73 -0
  65. package/dist/cli/queue.js.map +1 -0
  66. package/dist/cli/rebuild.d.ts +15 -0
  67. package/dist/cli/rebuild.js +70 -0
  68. package/dist/cli/rebuild.js.map +1 -0
  69. package/dist/cli/reembed-dim.d.ts +21 -0
  70. package/dist/cli/reembed-dim.js +199 -0
  71. package/dist/cli/reembed-dim.js.map +1 -0
  72. package/dist/cli/reinstall.d.ts +1 -0
  73. package/dist/cli/reinstall.js +205 -0
  74. package/dist/cli/reinstall.js.map +1 -0
  75. package/dist/cli/restore.d.ts +1 -0
  76. package/dist/cli/restore.js +167 -0
  77. package/dist/cli/restore.js.map +1 -0
  78. package/dist/cli/retag.d.ts +14 -0
  79. package/dist/cli/retag.js +62 -0
  80. package/dist/cli/retag.js.map +1 -0
  81. package/dist/cli/search.d.ts +66 -0
  82. package/dist/cli/search.js +174 -0
  83. package/dist/cli/search.js.map +1 -0
  84. package/dist/cli/serve.d.ts +9 -0
  85. package/dist/cli/serve.js +364 -0
  86. package/dist/cli/serve.js.map +1 -0
  87. package/dist/cli/service.d.ts +1 -0
  88. package/dist/cli/service.js +121 -0
  89. package/dist/cli/service.js.map +1 -0
  90. package/dist/cli/sync.d.ts +15 -0
  91. package/dist/cli/sync.js +61 -0
  92. package/dist/cli/sync.js.map +1 -0
  93. package/dist/cli/token.d.ts +24 -0
  94. package/dist/cli/token.js +77 -0
  95. package/dist/cli/token.js.map +1 -0
  96. package/dist/cli/wait-health.d.ts +4 -0
  97. package/dist/cli/wait-health.js +23 -0
  98. package/dist/cli/wait-health.js.map +1 -0
  99. package/dist/config/config.d.ts +127 -0
  100. package/dist/config/config.js +38 -0
  101. package/dist/config/config.js.map +1 -0
  102. package/dist/config/datadir.d.ts +30 -0
  103. package/dist/config/datadir.js +65 -0
  104. package/dist/config/datadir.js.map +1 -0
  105. package/dist/config/loader.d.ts +23 -0
  106. package/dist/config/loader.js +102 -0
  107. package/dist/config/loader.js.map +1 -0
  108. package/dist/config/migrate-dirs.d.ts +36 -0
  109. package/dist/config/migrate-dirs.js +132 -0
  110. package/dist/config/migrate-dirs.js.map +1 -0
  111. package/dist/config/persist-envs.d.ts +23 -0
  112. package/dist/config/persist-envs.js +119 -0
  113. package/dist/config/persist-envs.js.map +1 -0
  114. package/dist/config/resolve-runtime.d.ts +19 -0
  115. package/dist/config/resolve-runtime.js +53 -0
  116. package/dist/config/resolve-runtime.js.map +1 -0
  117. package/dist/config/secrets.d.ts +28 -0
  118. package/dist/config/secrets.js +38 -0
  119. package/dist/config/secrets.js.map +1 -0
  120. package/dist/config/sync-settings.d.ts +16 -0
  121. package/dist/config/sync-settings.js +34 -0
  122. package/dist/config/sync-settings.js.map +1 -0
  123. package/dist/config/writer.d.ts +19 -0
  124. package/dist/config/writer.js +121 -0
  125. package/dist/config/writer.js.map +1 -0
  126. package/dist/consolidate/consolidate.d.ts +80 -0
  127. package/dist/consolidate/consolidate.js +0 -0
  128. package/dist/consolidate/consolidate.js.map +1 -0
  129. package/dist/consolidate/proposals.d.ts +35 -0
  130. package/dist/consolidate/proposals.js +66 -0
  131. package/dist/consolidate/proposals.js.map +1 -0
  132. package/dist/contracts/atom-wire.d.ts +48 -0
  133. package/dist/contracts/atom-wire.js +55 -0
  134. package/dist/contracts/atom-wire.js.map +1 -0
  135. package/dist/contracts/embed.d.ts +41 -0
  136. package/dist/contracts/embed.js +20 -0
  137. package/dist/contracts/embed.js.map +1 -0
  138. package/dist/contracts/index.d.ts +5 -0
  139. package/dist/contracts/index.js +6 -0
  140. package/dist/contracts/index.js.map +1 -0
  141. package/dist/contracts/job.d.ts +113 -0
  142. package/dist/contracts/job.js +32 -0
  143. package/dist/contracts/job.js.map +1 -0
  144. package/dist/contracts/llm.d.ts +30 -0
  145. package/dist/contracts/llm.js +2 -0
  146. package/dist/contracts/llm.js.map +1 -0
  147. package/dist/contracts/memory.d.ts +47 -0
  148. package/dist/contracts/memory.js +5 -0
  149. package/dist/contracts/memory.js.map +1 -0
  150. package/dist/contracts/vector.d.ts +29 -0
  151. package/dist/contracts/vector.js +2 -0
  152. package/dist/contracts/vector.js.map +1 -0
  153. package/dist/distill/flatten-turns.d.ts +1 -0
  154. package/dist/distill/flatten-turns.js +50 -0
  155. package/dist/distill/flatten-turns.js.map +1 -0
  156. package/dist/distill/pipeline.d.ts +45 -0
  157. package/dist/distill/pipeline.js +113 -0
  158. package/dist/distill/pipeline.js.map +1 -0
  159. package/dist/distill/prompts/extract.d.ts +122 -0
  160. package/dist/distill/prompts/extract.js +67 -0
  161. package/dist/distill/prompts/extract.js.map +1 -0
  162. package/dist/distill/stages/01-cleanup.d.ts +9 -0
  163. package/dist/distill/stages/01-cleanup.js +67 -0
  164. package/dist/distill/stages/01-cleanup.js.map +1 -0
  165. package/dist/distill/stages/02-normalize.d.ts +9 -0
  166. package/dist/distill/stages/02-normalize.js +76 -0
  167. package/dist/distill/stages/02-normalize.js.map +1 -0
  168. package/dist/distill/stages/03-chunk.d.ts +22 -0
  169. package/dist/distill/stages/03-chunk.js +138 -0
  170. package/dist/distill/stages/03-chunk.js.map +1 -0
  171. package/dist/distill/stages/04-compact.d.ts +28 -0
  172. package/dist/distill/stages/04-compact.js +69 -0
  173. package/dist/distill/stages/04-compact.js.map +1 -0
  174. package/dist/distill/stages/05-extract.d.ts +35 -0
  175. package/dist/distill/stages/05-extract.js +101 -0
  176. package/dist/distill/stages/05-extract.js.map +1 -0
  177. package/dist/distill/stages/06-reduce.d.ts +16 -0
  178. package/dist/distill/stages/06-reduce.js +30 -0
  179. package/dist/distill/stages/06-reduce.js.map +1 -0
  180. package/dist/distill/stages/07-memory-normalize.d.ts +27 -0
  181. package/dist/distill/stages/07-memory-normalize.js +65 -0
  182. package/dist/distill/stages/07-memory-normalize.js.map +1 -0
  183. package/dist/distill/stages/08-embed-index.d.ts +31 -0
  184. package/dist/distill/stages/08-embed-index.js +82 -0
  185. package/dist/distill/stages/08-embed-index.js.map +1 -0
  186. package/dist/doctor/checks.d.ts +77 -0
  187. package/dist/doctor/checks.js +626 -0
  188. package/dist/doctor/checks.js.map +1 -0
  189. package/dist/doctor/hardening-checks.d.ts +9 -0
  190. package/dist/doctor/hardening-checks.js +182 -0
  191. package/dist/doctor/hardening-checks.js.map +1 -0
  192. package/dist/doctor/probes/embed-probe.d.ts +19 -0
  193. package/dist/doctor/probes/embed-probe.js +47 -0
  194. package/dist/doctor/probes/embed-probe.js.map +1 -0
  195. package/dist/doctor/probes/llm-chat-probe.d.ts +11 -0
  196. package/dist/doctor/probes/llm-chat-probe.js +41 -0
  197. package/dist/doctor/probes/llm-chat-probe.js.map +1 -0
  198. package/dist/doctor/probes/plugin-coexistence.d.ts +14 -0
  199. package/dist/doctor/probes/plugin-coexistence.js +60 -0
  200. package/dist/doctor/probes/plugin-coexistence.js.map +1 -0
  201. package/dist/doctor/runner.d.ts +17 -0
  202. package/dist/doctor/runner.js +53 -0
  203. package/dist/doctor/runner.js.map +1 -0
  204. package/dist/doctor/types.d.ts +12 -0
  205. package/dist/doctor/types.js +2 -0
  206. package/dist/doctor/types.js.map +1 -0
  207. package/dist/entity/backfill.d.ts +30 -0
  208. package/dist/entity/backfill.js +55 -0
  209. package/dist/entity/backfill.js.map +1 -0
  210. package/dist/entity/extract-entities.d.ts +27 -0
  211. package/dist/entity/extract-entities.js +86 -0
  212. package/dist/entity/extract-entities.js.map +1 -0
  213. package/dist/entity/normalize.d.ts +17 -0
  214. package/dist/entity/normalize.js +20 -0
  215. package/dist/entity/normalize.js.map +1 -0
  216. package/dist/eval/harness.d.ts +96 -0
  217. package/dist/eval/harness.js +119 -0
  218. package/dist/eval/harness.js.map +1 -0
  219. package/dist/eval/metrics.d.ts +23 -0
  220. package/dist/eval/metrics.js +44 -0
  221. package/dist/eval/metrics.js.map +1 -0
  222. package/dist/log/correlation.d.ts +24 -0
  223. package/dist/log/correlation.js +33 -0
  224. package/dist/log/correlation.js.map +1 -0
  225. package/dist/log/logger.d.ts +38 -0
  226. package/dist/log/logger.js +129 -0
  227. package/dist/log/logger.js.map +1 -0
  228. package/dist/log/scrub.d.ts +33 -0
  229. package/dist/log/scrub.js +91 -0
  230. package/dist/log/scrub.js.map +1 -0
  231. package/dist/mcp/server.d.ts +36 -0
  232. package/dist/mcp/server.js +553 -0
  233. package/dist/mcp/server.js.map +1 -0
  234. package/dist/memory-tool/adapter.d.ts +73 -0
  235. package/dist/memory-tool/adapter.js +269 -0
  236. package/dist/memory-tool/adapter.js.map +1 -0
  237. package/dist/pipeline/errors.d.ts +21 -0
  238. package/dist/pipeline/errors.js +34 -0
  239. package/dist/pipeline/errors.js.map +1 -0
  240. package/dist/pipeline/failure-classifier.d.ts +13 -0
  241. package/dist/pipeline/failure-classifier.js +72 -0
  242. package/dist/pipeline/failure-classifier.js.map +1 -0
  243. package/dist/pipeline/handler-ctx-ext.d.ts +23 -0
  244. package/dist/pipeline/handler-ctx-ext.js +19 -0
  245. package/dist/pipeline/handler-ctx-ext.js.map +1 -0
  246. package/dist/pipeline/handler.d.ts +20 -0
  247. package/dist/pipeline/handler.js +2 -0
  248. package/dist/pipeline/handler.js.map +1 -0
  249. package/dist/pipeline/handlers/cleanup.d.ts +14 -0
  250. package/dist/pipeline/handlers/cleanup.js +47 -0
  251. package/dist/pipeline/handlers/cleanup.js.map +1 -0
  252. package/dist/pipeline/handlers/consolidate.d.ts +8 -0
  253. package/dist/pipeline/handlers/consolidate.js +23 -0
  254. package/dist/pipeline/handlers/consolidate.js.map +1 -0
  255. package/dist/pipeline/handlers/distill-events.d.ts +15 -0
  256. package/dist/pipeline/handlers/distill-events.js +134 -0
  257. package/dist/pipeline/handlers/distill-events.js.map +1 -0
  258. package/dist/pipeline/handlers/distill.d.ts +17 -0
  259. package/dist/pipeline/handlers/distill.js +110 -0
  260. package/dist/pipeline/handlers/distill.js.map +1 -0
  261. package/dist/pipeline/handlers/reembed.d.ts +10 -0
  262. package/dist/pipeline/handlers/reembed.js +34 -0
  263. package/dist/pipeline/handlers/reembed.js.map +1 -0
  264. package/dist/pipeline/job-repo.d.ts +86 -0
  265. package/dist/pipeline/job-repo.js +168 -0
  266. package/dist/pipeline/job-repo.js.map +1 -0
  267. package/dist/pipeline/mock-providers.d.ts +49 -0
  268. package/dist/pipeline/mock-providers.js +175 -0
  269. package/dist/pipeline/mock-providers.js.map +1 -0
  270. package/dist/pipeline/registry.d.ts +15 -0
  271. package/dist/pipeline/registry.js +20 -0
  272. package/dist/pipeline/registry.js.map +1 -0
  273. package/dist/pipeline/worker.d.ts +41 -0
  274. package/dist/pipeline/worker.js +167 -0
  275. package/dist/pipeline/worker.js.map +1 -0
  276. package/dist/providers/embed/azure-openai.d.ts +25 -0
  277. package/dist/providers/embed/azure-openai.js +138 -0
  278. package/dist/providers/embed/azure-openai.js.map +1 -0
  279. package/dist/providers/embed/ollama.d.ts +17 -0
  280. package/dist/providers/embed/ollama.js +106 -0
  281. package/dist/providers/embed/ollama.js.map +1 -0
  282. package/dist/providers/index.d.ts +19 -0
  283. package/dist/providers/index.js +72 -0
  284. package/dist/providers/index.js.map +1 -0
  285. package/dist/providers/llm/azure-openai.d.ts +20 -0
  286. package/dist/providers/llm/azure-openai.js +135 -0
  287. package/dist/providers/llm/azure-openai.js.map +1 -0
  288. package/dist/providers/llm/ollama.d.ts +13 -0
  289. package/dist/providers/llm/ollama.js +113 -0
  290. package/dist/providers/llm/ollama.js.map +1 -0
  291. package/dist/providers/llm/pricing.d.ts +21 -0
  292. package/dist/providers/llm/pricing.js +22 -0
  293. package/dist/providers/llm/pricing.js.map +1 -0
  294. package/dist/recall/pack.d.ts +32 -0
  295. package/dist/recall/pack.js +90 -0
  296. package/dist/recall/pack.js.map +1 -0
  297. package/dist/recall/policy.d.ts +39 -0
  298. package/dist/recall/policy.js +96 -0
  299. package/dist/recall/policy.js.map +1 -0
  300. package/dist/redact/detectors.d.ts +20 -0
  301. package/dist/redact/detectors.js +85 -0
  302. package/dist/redact/detectors.js.map +1 -0
  303. package/dist/redact/entropy.d.ts +24 -0
  304. package/dist/redact/entropy.js +77 -0
  305. package/dist/redact/entropy.js.map +1 -0
  306. package/dist/redact/index.d.ts +47 -0
  307. package/dist/redact/index.js +165 -0
  308. package/dist/redact/index.js.map +1 -0
  309. package/dist/search/fuse.d.ts +108 -0
  310. package/dist/search/fuse.js +135 -0
  311. package/dist/search/fuse.js.map +1 -0
  312. package/dist/search/query.d.ts +28 -0
  313. package/dist/search/query.js +70 -0
  314. package/dist/search/query.js.map +1 -0
  315. package/dist/search/search.d.ts +164 -0
  316. package/dist/search/search.js +310 -0
  317. package/dist/search/search.js.map +1 -0
  318. package/dist/server/app.d.ts +17 -0
  319. package/dist/server/app.js +133 -0
  320. package/dist/server/app.js.map +1 -0
  321. package/dist/server/health-state.d.ts +29 -0
  322. package/dist/server/health-state.js +28 -0
  323. package/dist/server/health-state.js.map +1 -0
  324. package/dist/server/lib/network.d.ts +12 -0
  325. package/dist/server/lib/network.js +16 -0
  326. package/dist/server/lib/network.js.map +1 -0
  327. package/dist/server/lib/score-contract.d.ts +36 -0
  328. package/dist/server/lib/score-contract.js +54 -0
  329. package/dist/server/lib/score-contract.js.map +1 -0
  330. package/dist/server/lib/stable-stringify.d.ts +10 -0
  331. package/dist/server/lib/stable-stringify.js +27 -0
  332. package/dist/server/lib/stable-stringify.js.map +1 -0
  333. package/dist/server/lib/wire-meta.d.ts +7 -0
  334. package/dist/server/lib/wire-meta.js +29 -0
  335. package/dist/server/lib/wire-meta.js.map +1 -0
  336. package/dist/server/queries/dashboard.d.ts +142 -0
  337. package/dist/server/queries/dashboard.js +166 -0
  338. package/dist/server/queries/dashboard.js.map +1 -0
  339. package/dist/server/routes/consolidation.d.ts +14 -0
  340. package/dist/server/routes/consolidation.js +67 -0
  341. package/dist/server/routes/consolidation.js.map +1 -0
  342. package/dist/server/routes/dashboard-api-html.d.ts +15 -0
  343. package/dist/server/routes/dashboard-api-html.js +144 -0
  344. package/dist/server/routes/dashboard-api-html.js.map +1 -0
  345. package/dist/server/routes/dashboard-consolidation-html.d.ts +26 -0
  346. package/dist/server/routes/dashboard-consolidation-html.js +202 -0
  347. package/dist/server/routes/dashboard-consolidation-html.js.map +1 -0
  348. package/dist/server/routes/dashboard-html.d.ts +15 -0
  349. package/dist/server/routes/dashboard-html.js +365 -0
  350. package/dist/server/routes/dashboard-html.js.map +1 -0
  351. package/dist/server/routes/dashboard-jobs-html.d.ts +18 -0
  352. package/dist/server/routes/dashboard-jobs-html.js +186 -0
  353. package/dist/server/routes/dashboard-jobs-html.js.map +1 -0
  354. package/dist/server/routes/dashboard-search-html.d.ts +18 -0
  355. package/dist/server/routes/dashboard-search-html.js +189 -0
  356. package/dist/server/routes/dashboard-search-html.js.map +1 -0
  357. package/dist/server/routes/dashboard.d.ts +19 -0
  358. package/dist/server/routes/dashboard.js +68 -0
  359. package/dist/server/routes/dashboard.js.map +1 -0
  360. package/dist/server/routes/digest.d.ts +9 -0
  361. package/dist/server/routes/digest.js +37 -0
  362. package/dist/server/routes/digest.js.map +1 -0
  363. package/dist/server/routes/entities.d.ts +12 -0
  364. package/dist/server/routes/entities.js +46 -0
  365. package/dist/server/routes/entities.js.map +1 -0
  366. package/dist/server/routes/health.d.ts +14 -0
  367. package/dist/server/routes/health.js +100 -0
  368. package/dist/server/routes/health.js.map +1 -0
  369. package/dist/server/routes/ingest.d.ts +209 -0
  370. package/dist/server/routes/ingest.js +454 -0
  371. package/dist/server/routes/ingest.js.map +1 -0
  372. package/dist/server/routes/lifecycle.d.ts +21 -0
  373. package/dist/server/routes/lifecycle.js +132 -0
  374. package/dist/server/routes/lifecycle.js.map +1 -0
  375. package/dist/server/routes/mcp.d.ts +15 -0
  376. package/dist/server/routes/mcp.js +36 -0
  377. package/dist/server/routes/mcp.js.map +1 -0
  378. package/dist/server/routes/memory-tool.d.ts +14 -0
  379. package/dist/server/routes/memory-tool.js +28 -0
  380. package/dist/server/routes/memory-tool.js.map +1 -0
  381. package/dist/server/routes/memory.d.ts +7 -0
  382. package/dist/server/routes/memory.js +19 -0
  383. package/dist/server/routes/memory.js.map +1 -0
  384. package/dist/server/routes/recall.d.ts +15 -0
  385. package/dist/server/routes/recall.js +74 -0
  386. package/dist/server/routes/recall.js.map +1 -0
  387. package/dist/server/routes/search.d.ts +12 -0
  388. package/dist/server/routes/search.js +203 -0
  389. package/dist/server/routes/search.js.map +1 -0
  390. package/dist/server/routes/version.d.ts +2 -0
  391. package/dist/server/routes/version.js +11 -0
  392. package/dist/server/routes/version.js.map +1 -0
  393. package/dist/server/routes/why.d.ts +9 -0
  394. package/dist/server/routes/why.js +38 -0
  395. package/dist/server/routes/why.js.map +1 -0
  396. package/dist/service/index.d.ts +10 -0
  397. package/dist/service/index.js +25 -0
  398. package/dist/service/index.js.map +1 -0
  399. package/dist/service/install-flow.d.ts +18 -0
  400. package/dist/service/install-flow.js +47 -0
  401. package/dist/service/install-flow.js.map +1 -0
  402. package/dist/service/instance-lock.d.ts +26 -0
  403. package/dist/service/instance-lock.js +150 -0
  404. package/dist/service/instance-lock.js.map +1 -0
  405. package/dist/service/launchd.d.ts +11 -0
  406. package/dist/service/launchd.js +196 -0
  407. package/dist/service/launchd.js.map +1 -0
  408. package/dist/service/schtasks.d.ts +31 -0
  409. package/dist/service/schtasks.js +274 -0
  410. package/dist/service/schtasks.js.map +1 -0
  411. package/dist/service/shim.d.ts +21 -0
  412. package/dist/service/shim.js +80 -0
  413. package/dist/service/shim.js.map +1 -0
  414. package/dist/service/systemd.d.ts +11 -0
  415. package/dist/service/systemd.js +150 -0
  416. package/dist/service/systemd.js.map +1 -0
  417. package/dist/service/task-xml.d.ts +36 -0
  418. package/dist/service/task-xml.js +91 -0
  419. package/dist/service/task-xml.js.map +1 -0
  420. package/dist/service/types.d.ts +47 -0
  421. package/dist/service/types.js +2 -0
  422. package/dist/service/types.js.map +1 -0
  423. package/dist/storage/archival.d.ts +29 -0
  424. package/dist/storage/archival.js +47 -0
  425. package/dist/storage/archival.js.map +1 -0
  426. package/dist/storage/bearer-keystore.d.ts +34 -0
  427. package/dist/storage/bearer-keystore.js +75 -0
  428. package/dist/storage/bearer-keystore.js.map +1 -0
  429. package/dist/storage/db.d.ts +37 -0
  430. package/dist/storage/db.js +92 -0
  431. package/dist/storage/db.js.map +1 -0
  432. package/dist/storage/entities.d.ts +71 -0
  433. package/dist/storage/entities.js +141 -0
  434. package/dist/storage/entities.js.map +1 -0
  435. package/dist/storage/ingest-idempotency.d.ts +26 -0
  436. package/dist/storage/ingest-idempotency.js +29 -0
  437. package/dist/storage/ingest-idempotency.js.map +1 -0
  438. package/dist/storage/keystore.d.ts +64 -0
  439. package/dist/storage/keystore.js +194 -0
  440. package/dist/storage/keystore.js.map +1 -0
  441. package/dist/storage/memories.d.ts +51 -0
  442. package/dist/storage/memories.js +67 -0
  443. package/dist/storage/memories.js.map +1 -0
  444. package/dist/storage/memory-events.d.ts +145 -0
  445. package/dist/storage/memory-events.js +287 -0
  446. package/dist/storage/memory-events.js.map +1 -0
  447. package/dist/storage/migrate-encrypt.d.ts +16 -0
  448. package/dist/storage/migrate-encrypt.js +121 -0
  449. package/dist/storage/migrate-encrypt.js.map +1 -0
  450. package/dist/storage/migrate.d.ts +27 -0
  451. package/dist/storage/migrate.js +105 -0
  452. package/dist/storage/migrate.js.map +1 -0
  453. package/dist/storage/redaction-log.d.ts +18 -0
  454. package/dist/storage/redaction-log.js +27 -0
  455. package/dist/storage/redaction-log.js.map +1 -0
  456. package/dist/storage/usefulness.d.ts +115 -0
  457. package/dist/storage/usefulness.js +203 -0
  458. package/dist/storage/usefulness.js.map +1 -0
  459. package/dist/sync/conflict-resolve.d.ts +26 -0
  460. package/dist/sync/conflict-resolve.js +139 -0
  461. package/dist/sync/conflict-resolve.js.map +1 -0
  462. package/dist/sync/puller.d.ts +115 -0
  463. package/dist/sync/puller.js +173 -0
  464. package/dist/sync/puller.js.map +1 -0
  465. package/dist/sync/shipper.d.ts +112 -0
  466. package/dist/sync/shipper.js +189 -0
  467. package/dist/sync/shipper.js.map +1 -0
  468. package/dist/tag-hygiene/backfill.d.ts +50 -0
  469. package/dist/tag-hygiene/backfill.js +117 -0
  470. package/dist/tag-hygiene/backfill.js.map +1 -0
  471. package/dist/tag-hygiene/derive-repo.d.ts +9 -0
  472. package/dist/tag-hygiene/derive-repo.js +19 -0
  473. package/dist/tag-hygiene/derive-repo.js.map +1 -0
  474. package/dist/tag-hygiene/tier2-infer.d.ts +28 -0
  475. package/dist/tag-hygiene/tier2-infer.js +72 -0
  476. package/dist/tag-hygiene/tier2-infer.js.map +1 -0
  477. package/dist/vector/sqlite-vec.d.ts +16 -0
  478. package/dist/vector/sqlite-vec.js +49 -0
  479. package/dist/vector/sqlite-vec.js.map +1 -0
  480. package/migrations/001-init.sql +117 -0
  481. package/migrations/002-wire-v1.sql +16 -0
  482. package/migrations/003-expand-memory-types.sql +81 -0
  483. package/migrations/004-provenance.sql +4 -0
  484. package/migrations/005-security.sql +12 -0
  485. package/migrations/006-atom-v3.sql +28 -0
  486. package/migrations/007-memory-events.sql +30 -0
  487. package/migrations/008-consolidation.sql +31 -0
  488. package/migrations/009-tag-hygiene.sql +13 -0
  489. package/migrations/010-sync-pull.sql +53 -0
  490. package/migrations/011-embed-dim-migration.sql +28 -0
  491. package/migrations/012-entities.sql +36 -0
  492. package/migrations/013-archival.sql +50 -0
  493. package/package.json +50 -0
@@ -0,0 +1,209 @@
1
+ import { z } from 'zod';
2
+ import type { FastifyInstance } from 'fastify';
3
+ import type { DB } from '../../storage/db.js';
4
+ import { type Config } from '../../config/config.js';
5
+ export declare const TranscriptTurnSchema: z.ZodObject<{
6
+ role: z.ZodEnum<["user", "assistant"]>;
7
+ text: z.ZodString;
8
+ ts: z.ZodOptional<z.ZodString>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ role: "user" | "assistant";
11
+ text: string;
12
+ ts?: string | undefined;
13
+ }, {
14
+ role: "user" | "assistant";
15
+ text: string;
16
+ ts?: string | undefined;
17
+ }>;
18
+ export declare const CanonicalEventItemSchema: z.ZodObject<{
19
+ type: z.ZodEnum<["decision", "fact", "lesson", "command", "todo", "note", "event"]>;
20
+ text: z.ZodString;
21
+ importance: z.ZodOptional<z.ZodNumber>;
22
+ confidence: z.ZodOptional<z.ZodNumber>;
23
+ evidence: z.ZodOptional<z.ZodString>;
24
+ occurred_at: z.ZodOptional<z.ZodNumber>;
25
+ }, "strip", z.ZodTypeAny, {
26
+ type: "decision" | "fact" | "lesson" | "command" | "todo" | "note" | "event";
27
+ text: string;
28
+ importance?: number | undefined;
29
+ confidence?: number | undefined;
30
+ evidence?: string | undefined;
31
+ occurred_at?: number | undefined;
32
+ }, {
33
+ type: "decision" | "fact" | "lesson" | "command" | "todo" | "note" | "event";
34
+ text: string;
35
+ importance?: number | undefined;
36
+ confidence?: number | undefined;
37
+ evidence?: string | undefined;
38
+ occurred_at?: number | undefined;
39
+ }>;
40
+ export declare const CanonicalIngestSchema: z.ZodEffects<z.ZodObject<{
41
+ event: z.ZodEnum<["pre_compact", "session_end", "subagent_stop"]>;
42
+ session_id: z.ZodString;
43
+ project_id: z.ZodString;
44
+ agent_type: z.ZodOptional<z.ZodString>;
45
+ cwd: z.ZodOptional<z.ZodString>;
46
+ captured_at: z.ZodString;
47
+ /** ADR-008 payload kind. Defaults to 'transcript' — existing clients unaffected. */
48
+ kind: z.ZodDefault<z.ZodOptional<z.ZodEnum<["transcript", "events"]>>>;
49
+ turns: z.ZodOptional<z.ZodArray<z.ZodObject<{
50
+ role: z.ZodEnum<["user", "assistant"]>;
51
+ text: z.ZodString;
52
+ ts: z.ZodOptional<z.ZodString>;
53
+ }, "strip", z.ZodTypeAny, {
54
+ role: "user" | "assistant";
55
+ text: string;
56
+ ts?: string | undefined;
57
+ }, {
58
+ role: "user" | "assistant";
59
+ text: string;
60
+ ts?: string | undefined;
61
+ }>, "many">>;
62
+ /** Required (min 1, max 500) when kind === 'events' — see superRefine below. */
63
+ events: z.ZodOptional<z.ZodArray<z.ZodObject<{
64
+ type: z.ZodEnum<["decision", "fact", "lesson", "command", "todo", "note", "event"]>;
65
+ text: z.ZodString;
66
+ importance: z.ZodOptional<z.ZodNumber>;
67
+ confidence: z.ZodOptional<z.ZodNumber>;
68
+ evidence: z.ZodOptional<z.ZodString>;
69
+ occurred_at: z.ZodOptional<z.ZodNumber>;
70
+ }, "strip", z.ZodTypeAny, {
71
+ type: "decision" | "fact" | "lesson" | "command" | "todo" | "note" | "event";
72
+ text: string;
73
+ importance?: number | undefined;
74
+ confidence?: number | undefined;
75
+ evidence?: string | undefined;
76
+ occurred_at?: number | undefined;
77
+ }, {
78
+ type: "decision" | "fact" | "lesson" | "command" | "todo" | "note" | "event";
79
+ text: string;
80
+ importance?: number | undefined;
81
+ confidence?: number | undefined;
82
+ evidence?: string | undefined;
83
+ occurred_at?: number | undefined;
84
+ }>, "many">>;
85
+ /** Provenance (ADR-008 `tool` field). No default — record what's sent or null. */
86
+ tool: z.ZodOptional<z.ZodString>;
87
+ /** Aggregate scrub flag — required; still consumed for telemetry. Superseded by client_scrub_hits_by_label for per-label breakdown but not deprecated. */
88
+ client_scrub_applied: z.ZodBoolean;
89
+ /** Aggregate scrub hit count — required; still consumed for telemetry. Superseded by client_scrub_hits_by_label sum for per-label breakdown but not deprecated. */
90
+ client_scrub_hits: z.ZodNumber;
91
+ client_version: z.ZodString;
92
+ client_scrub_version: z.ZodString;
93
+ client_scrub_hits_by_label: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
94
+ /** Wire protocol version — must match ^v(?:0|[1-9][0-9]*)\.[0-9]+$ (e.g. "v1.0"). */
95
+ wire_version: z.ZodString;
96
+ }, "strip", z.ZodTypeAny, {
97
+ event: "session_end" | "pre_compact" | "subagent_stop";
98
+ kind: "transcript" | "events";
99
+ session_id: string;
100
+ project_id: string;
101
+ captured_at: string;
102
+ client_scrub_applied: boolean;
103
+ client_scrub_hits: number;
104
+ client_version: string;
105
+ client_scrub_version: string;
106
+ wire_version: string;
107
+ cwd?: string | undefined;
108
+ agent_type?: string | undefined;
109
+ events?: {
110
+ type: "decision" | "fact" | "lesson" | "command" | "todo" | "note" | "event";
111
+ text: string;
112
+ importance?: number | undefined;
113
+ confidence?: number | undefined;
114
+ evidence?: string | undefined;
115
+ occurred_at?: number | undefined;
116
+ }[] | undefined;
117
+ turns?: {
118
+ role: "user" | "assistant";
119
+ text: string;
120
+ ts?: string | undefined;
121
+ }[] | undefined;
122
+ tool?: string | undefined;
123
+ client_scrub_hits_by_label?: Record<string, number> | undefined;
124
+ }, {
125
+ event: "session_end" | "pre_compact" | "subagent_stop";
126
+ session_id: string;
127
+ project_id: string;
128
+ captured_at: string;
129
+ client_scrub_applied: boolean;
130
+ client_scrub_hits: number;
131
+ client_version: string;
132
+ client_scrub_version: string;
133
+ wire_version: string;
134
+ cwd?: string | undefined;
135
+ kind?: "transcript" | "events" | undefined;
136
+ agent_type?: string | undefined;
137
+ events?: {
138
+ type: "decision" | "fact" | "lesson" | "command" | "todo" | "note" | "event";
139
+ text: string;
140
+ importance?: number | undefined;
141
+ confidence?: number | undefined;
142
+ evidence?: string | undefined;
143
+ occurred_at?: number | undefined;
144
+ }[] | undefined;
145
+ turns?: {
146
+ role: "user" | "assistant";
147
+ text: string;
148
+ ts?: string | undefined;
149
+ }[] | undefined;
150
+ tool?: string | undefined;
151
+ client_scrub_hits_by_label?: Record<string, number> | undefined;
152
+ }>, {
153
+ event: "session_end" | "pre_compact" | "subagent_stop";
154
+ kind: "transcript" | "events";
155
+ session_id: string;
156
+ project_id: string;
157
+ captured_at: string;
158
+ client_scrub_applied: boolean;
159
+ client_scrub_hits: number;
160
+ client_version: string;
161
+ client_scrub_version: string;
162
+ wire_version: string;
163
+ cwd?: string | undefined;
164
+ agent_type?: string | undefined;
165
+ events?: {
166
+ type: "decision" | "fact" | "lesson" | "command" | "todo" | "note" | "event";
167
+ text: string;
168
+ importance?: number | undefined;
169
+ confidence?: number | undefined;
170
+ evidence?: string | undefined;
171
+ occurred_at?: number | undefined;
172
+ }[] | undefined;
173
+ turns?: {
174
+ role: "user" | "assistant";
175
+ text: string;
176
+ ts?: string | undefined;
177
+ }[] | undefined;
178
+ tool?: string | undefined;
179
+ client_scrub_hits_by_label?: Record<string, number> | undefined;
180
+ }, {
181
+ event: "session_end" | "pre_compact" | "subagent_stop";
182
+ session_id: string;
183
+ project_id: string;
184
+ captured_at: string;
185
+ client_scrub_applied: boolean;
186
+ client_scrub_hits: number;
187
+ client_version: string;
188
+ client_scrub_version: string;
189
+ wire_version: string;
190
+ cwd?: string | undefined;
191
+ kind?: "transcript" | "events" | undefined;
192
+ agent_type?: string | undefined;
193
+ events?: {
194
+ type: "decision" | "fact" | "lesson" | "command" | "todo" | "note" | "event";
195
+ text: string;
196
+ importance?: number | undefined;
197
+ confidence?: number | undefined;
198
+ evidence?: string | undefined;
199
+ occurred_at?: number | undefined;
200
+ }[] | undefined;
201
+ turns?: {
202
+ role: "user" | "assistant";
203
+ text: string;
204
+ ts?: string | undefined;
205
+ }[] | undefined;
206
+ tool?: string | undefined;
207
+ client_scrub_hits_by_label?: Record<string, number> | undefined;
208
+ }>;
209
+ export declare function ingestRoute(db: DB, config?: Config): (app: FastifyInstance) => Promise<void>;
@@ -0,0 +1,454 @@
1
+ import { randomUUID, createHash } from 'node:crypto';
2
+ import { z } from 'zod';
3
+ import { childLogger } from '../../log/logger.js';
4
+ import { stableStringify } from '../lib/stable-stringify.js';
5
+ import { defaultConfig } from '../../config/config.js';
6
+ import { redactIfEnabled } from '../../redact/index.js';
7
+ import { recordRedactionEvents } from '../../storage/redaction-log.js';
8
+ // ---------------------------------------------------------------------------
9
+ // Legacy envelope (v0.0) — original shape; discriminated by presence of `content`
10
+ // ---------------------------------------------------------------------------
11
+ const LegacyIngestSchema = z.object({
12
+ session_id: z.string().min(1),
13
+ source: z.string().min(1),
14
+ content: z.string().min(1),
15
+ repo: z.string().nullable().optional(),
16
+ project: z.string().nullable().optional(),
17
+ branch: z.string().nullable().optional(),
18
+ agent: z.string().nullable().optional(),
19
+ });
20
+ // ---------------------------------------------------------------------------
21
+ // Canonical envelope (v1.0) — FEAT-4a SaaS wire format
22
+ // Field names and types mirror TranscriptIngestPayloadSchema in wire.ts exactly,
23
+ // with the addition of `wire_version` (^v\d+\.\d+$) which is required here even
24
+ // though the plugin schema doesn't yet carry it (pending SaaS DTO catch-up).
25
+ //
26
+ // Discriminated from legacy by presence of `turns` (array, min 1).
27
+ // ---------------------------------------------------------------------------
28
+ export const TranscriptTurnSchema = z.object({
29
+ role: z.enum(['user', 'assistant']),
30
+ text: z.string(),
31
+ ts: z.string().optional(), // ISO-8601 if present
32
+ });
33
+ // ---------------------------------------------------------------------------
34
+ // ADR-008 capture@1 `events` kind — pre-typed atom candidates (runner-plugin
35
+ // grades/lessons/review verdicts) that skip pipeline stages 1-5 and enter at
36
+ // reduce (stage 6). Type enum mirrors the 7-type registry in
37
+ // src/distill/prompts/extract.ts (AtomSchema).
38
+ // ---------------------------------------------------------------------------
39
+ export const CanonicalEventItemSchema = z.object({
40
+ type: z.enum(['decision', 'fact', 'lesson', 'command', 'todo', 'note', 'event']),
41
+ text: z.string().min(1),
42
+ importance: z.number().min(0).max(1).optional(),
43
+ confidence: z.number().min(0).max(1).optional(),
44
+ evidence: z.string().optional(),
45
+ occurred_at: z.number().optional(),
46
+ });
47
+ export const CanonicalIngestSchema = z.object({
48
+ event: z.enum(['pre_compact', 'session_end', 'subagent_stop']),
49
+ session_id: z.string(),
50
+ project_id: z.string(),
51
+ agent_type: z.string().optional(),
52
+ cwd: z.string().optional(),
53
+ captured_at: z.string().datetime({ offset: true }), // ISO-8601 with UTC Z — guards NaN on getTime()
54
+ /** ADR-008 payload kind. Defaults to 'transcript' — existing clients unaffected. */
55
+ kind: z.enum(['transcript', 'events']).optional().default('transcript'),
56
+ turns: z.array(TranscriptTurnSchema).min(1).optional(),
57
+ /** Required (min 1, max 500) when kind === 'events' — see superRefine below. */
58
+ events: z.array(CanonicalEventItemSchema).min(1).max(500).optional(),
59
+ /** Provenance (ADR-008 `tool` field). No default — record what's sent or null. */
60
+ tool: z.string().optional(),
61
+ /** Aggregate scrub flag — required; still consumed for telemetry. Superseded by client_scrub_hits_by_label for per-label breakdown but not deprecated. */
62
+ client_scrub_applied: z.boolean(),
63
+ /** Aggregate scrub hit count — required; still consumed for telemetry. Superseded by client_scrub_hits_by_label sum for per-label breakdown but not deprecated. */
64
+ client_scrub_hits: z.number().int().nonnegative(),
65
+ client_version: z.string(),
66
+ client_scrub_version: z.string(),
67
+ client_scrub_hits_by_label: z.record(z.string(), z.number().int().nonnegative()).optional(),
68
+ // Regex mirrors SaaS IngestTranscriptRequest.cs:65 per FEAT-4a Phase 3 alignment.
69
+ /** Wire protocol version — must match ^v(?:0|[1-9][0-9]*)\.[0-9]+$ (e.g. "v1.0"). */
70
+ wire_version: z.string().regex(/^v(?:0|[1-9][0-9]*)\.[0-9]+$/, 'wire_version must match ^v(?:0|[1-9][0-9]*)\\.[0-9]+$ (e.g. "v1.0")'),
71
+ }).superRefine((data, ctx) => {
72
+ if (data.kind === 'events') {
73
+ if (!data.events || data.events.length === 0) {
74
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'events kind requires a non-empty `events` array', path: ['events'] });
75
+ }
76
+ }
77
+ else if (!data.turns || data.turns.length === 0) {
78
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'transcript kind requires a non-empty `turns` array', path: ['turns'] });
79
+ }
80
+ });
81
+ // ---------------------------------------------------------------------------
82
+ // Combined schema — canonical tried first (has `wire_version`), legacy as fallback
83
+ // ---------------------------------------------------------------------------
84
+ const IngestSchema = z.union([CanonicalIngestSchema, LegacyIngestSchema]);
85
+ function isCanonical(data) {
86
+ // `turns` is now optional on the canonical schema (events kind omits it),
87
+ // so discriminate on `wire_version` instead — required on canonical,
88
+ // absent from the legacy envelope.
89
+ return 'wire_version' in data;
90
+ }
91
+ // ---------------------------------------------------------------------------
92
+ // Route factory — prepared statements hoisted to factory scope (D-B6)
93
+ // All db.prepare() calls happen ONCE per server start, not per request.
94
+ // ---------------------------------------------------------------------------
95
+ export function ingestRoute(db, config = defaultConfig()) {
96
+ // ---- Canonical path statements ----
97
+ const stmtSelectIdempotency = db.prepare('SELECT body_hash, summary_memory_id FROM ingest_idempotency WHERE key = ?');
98
+ // cwd (FEAT-403 / migration 009) is stored so the tag-hygiene backfill can
99
+ // derive `repo` from it later — the canonical envelope never sends `repo`
100
+ // directly, so without cwd this session would have no repo signal at all.
101
+ const stmtUpsertSession = db.prepare(`
102
+ INSERT INTO sessions (id, repo, project, branch, agent, cwd, started_at)
103
+ VALUES (?, NULL, ?, NULL, ?, ?, ?)
104
+ ON CONFLICT(id) DO UPDATE SET
105
+ project = COALESCE(excluded.project, project),
106
+ agent = COALESCE(excluded.agent, agent),
107
+ cwd = COALESCE(excluded.cwd, cwd)
108
+ `);
109
+ const stmtInsertTranscript = db.prepare(`
110
+ INSERT INTO transcripts (
111
+ id, session_id, source, content, ingested_at,
112
+ event, captured_at, client_scrub_applied, client_scrub_hits,
113
+ client_scrub_version, client_scrub_hits_by_label_json,
114
+ client_version, wire_version
115
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
116
+ `);
117
+ const stmtInsertJob = db.prepare(`
118
+ INSERT INTO jobs (id, kind, payload_json, state, attempts, created_at, updated_at)
119
+ VALUES (?, 'distill', ?, 'pending', 0, ?, ?)
120
+ `);
121
+ // ADR-008 events kind — same transcripts-row shape as the transcript path
122
+ // (reuses stmtInsertTranscript), but enqueues a distinct job kind so the
123
+ // worker routes it to distillEventsHandler instead of distillHandler.
124
+ const stmtInsertDistillEventsJob = db.prepare(`
125
+ INSERT INTO jobs (id, kind, payload_json, state, attempts, created_at, updated_at)
126
+ VALUES (?, 'distill-events', ?, 'pending', 0, ?, ?)
127
+ `);
128
+ const stmtInsertIdempotency = db.prepare(`
129
+ INSERT INTO ingest_idempotency (key, body_hash, summary_memory_id, created_at)
130
+ VALUES (?, ?, ?, ?)
131
+ ON CONFLICT(key) DO NOTHING
132
+ `);
133
+ // Used for D-B3 dangling-row check on replay. `summary_memory_id` starts
134
+ // out as the transcript id (placeholder, written at ingest time) and may
135
+ // later be backfilled to a real `memories.id` once distillation completes
136
+ // (D-DEF2, see storage/ingest-idempotency.ts) — so "does this id still
137
+ // resolve to something" has to check both tables.
138
+ const stmtCheckTranscriptExists = db.prepare('SELECT id FROM transcripts WHERE id = ?');
139
+ const stmtCheckMemoryExists = db.prepare('SELECT id FROM memories WHERE id = ?');
140
+ // Used for D-B3 invalidation of dangling idempotency row
141
+ const stmtDeleteIdempotency = db.prepare('DELETE FROM ingest_idempotency WHERE key = ?');
142
+ // ---- Legacy path statements ----
143
+ const stmtUpsertSessionLegacy = db.prepare(`
144
+ INSERT INTO sessions (id, repo, project, branch, agent, started_at)
145
+ VALUES (?, ?, ?, ?, ?, ?)
146
+ ON CONFLICT(id) DO UPDATE SET
147
+ repo = COALESCE(excluded.repo, repo),
148
+ project = COALESCE(excluded.project, project),
149
+ branch = COALESCE(excluded.branch, branch),
150
+ agent = COALESCE(excluded.agent, agent)
151
+ `);
152
+ const stmtInsertTranscriptLegacy = db.prepare(`
153
+ INSERT INTO transcripts (id, session_id, source, content, ingested_at)
154
+ VALUES (?, ?, ?, ?, ?)
155
+ `);
156
+ const stmtInsertJobLegacy = db.prepare(`
157
+ INSERT INTO jobs (id, kind, payload_json, state, attempts, created_at, updated_at)
158
+ VALUES (?, 'distill', ?, 'pending', 0, ?, ?)
159
+ `);
160
+ // ---------------------------------------------------------------------
161
+ // Shared idempotency-key handling (D-B1/D-B2/D-B3) — extracted so the
162
+ // events-kind branch can reuse the exact same claim/replay/conflict/
163
+ // dangling-row semantics as the transcript-kind path without duplicating
164
+ // the logic inline. MUST be called from inside a db.transaction().
165
+ // ---------------------------------------------------------------------
166
+ /**
167
+ * Attempt to claim the idempotency slot for `key`. Throws a sentinel error
168
+ * (`_isReplay` or `_isIdempotencyConflict`) to signal the caller's
169
+ * transaction should abort and the route should return early — mirrors
170
+ * the inline logic in the transcript-kind path.
171
+ */
172
+ function claimIdempotencySlot(key, bodyHash, now) {
173
+ stmtInsertIdempotency.run(key, bodyHash, '__pending__', now);
174
+ const existing = stmtSelectIdempotency.get(key);
175
+ if (existing && existing.summary_memory_id !== '__pending__') {
176
+ if (existing.body_hash !== bodyHash) {
177
+ throw Object.assign(new Error('idempotency_conflict'), { _isIdempotencyConflict: true });
178
+ }
179
+ // D-B3: guard against a dangling row (summary_memory_id may point at
180
+ // either a transcript or a backfilled memory — check both).
181
+ const stillExists = existing.summary_memory_id != null
182
+ ? (stmtCheckTranscriptExists.get(existing.summary_memory_id)
183
+ ?? stmtCheckMemoryExists.get(existing.summary_memory_id))
184
+ : undefined;
185
+ if (!stillExists) {
186
+ stmtDeleteIdempotency.run(key);
187
+ stmtInsertIdempotency.run(key, bodyHash, '__pending__', now);
188
+ }
189
+ else {
190
+ throw Object.assign(new Error('idempotent_replay'), {
191
+ _isReplay: true,
192
+ _replayId: existing.summary_memory_id,
193
+ });
194
+ }
195
+ }
196
+ }
197
+ /** Update the idempotency placeholder with the real transcript id once the insert has happened. */
198
+ function finalizeIdempotencySlot(key, bodyHash, transcriptId, now) {
199
+ stmtDeleteIdempotency.run(key);
200
+ stmtInsertIdempotency.run(key, bodyHash, transcriptId, now);
201
+ }
202
+ return async (app) => {
203
+ app.post('/ingest/transcript', async (req, reply) => {
204
+ const requestId = req['requestId'];
205
+ const parsed = IngestSchema.safeParse(req.body);
206
+ if (!parsed.success) {
207
+ const log = childLogger({ request_id: requestId ?? 'unknown' });
208
+ log.warn({ details: parsed.error.flatten() }, 'ingest validation failed');
209
+ return reply.code(400).send({ error: 'invalid', details: parsed.error.flatten() });
210
+ }
211
+ const data = parsed.data;
212
+ // ------------------------------------------------------------------
213
+ // Canonical path — real insert (FEAT-4a Phase 2 stage 3)
214
+ // ------------------------------------------------------------------
215
+ if (isCanonical(data)) {
216
+ const log = childLogger({ request_id: requestId ?? 'unknown', session_id: data.session_id });
217
+ // ------------------------------------------------------------------
218
+ // ADR-008 events kind — pre-typed atom candidates (runner-plugin
219
+ // grades/lessons/review verdicts). Redact, store as a transcripts
220
+ // row, enqueue 'distill-events' (skips pipeline stages 1-5, enters
221
+ // at reduce/stage 6 — see src/pipeline/handlers/distill-events.ts).
222
+ // ------------------------------------------------------------------
223
+ if (data.kind === 'events') {
224
+ const events = data.events; // superRefine guarantees non-empty for kind === 'events'
225
+ // ---- Stage-0 secret redaction (SEC-3/5) applies to events too ----
226
+ const redactionEventsForEvents = [];
227
+ const redactedEvents = events.map(ev => {
228
+ const { text, events: textRedactions } = redactIfEnabled(ev.text, config);
229
+ redactionEventsForEvents.push(...textRedactions);
230
+ let evidence = ev.evidence;
231
+ if (evidence !== undefined) {
232
+ const { text: redactedEvidence, events: evidenceRedactions } = redactIfEnabled(evidence, config);
233
+ evidence = redactedEvidence;
234
+ redactionEventsForEvents.push(...evidenceRedactions);
235
+ }
236
+ return { ...ev, text, evidence };
237
+ });
238
+ const idempotencyKey = req.headers['idempotency-key'] ?? null;
239
+ const bodyHash = createHash('sha256').update(stableStringify(data)).digest('hex');
240
+ const now = Date.now();
241
+ const capturedAtMs = new Date(data.captured_at).getTime();
242
+ const sourceLabel = data.tool ? `${data.tool}-events` : 'events';
243
+ let transcriptId;
244
+ let jobId;
245
+ try {
246
+ const tx = db.transaction(() => {
247
+ if (idempotencyKey !== null)
248
+ claimIdempotencySlot(idempotencyKey, bodyHash, now);
249
+ stmtUpsertSession.run(data.session_id, data.project_id, data.agent_type ?? null, data.cwd ?? null, now);
250
+ transcriptId = randomUUID();
251
+ stmtInsertTranscript.run(transcriptId, data.session_id, sourceLabel, JSON.stringify(redactedEvents), now, data.event, capturedAtMs, data.client_scrub_applied ? 1 : 0, data.client_scrub_hits ?? 0, data.client_scrub_version ?? null, data.client_scrub_hits_by_label != null
252
+ ? JSON.stringify(data.client_scrub_hits_by_label)
253
+ : null, data.client_version ?? null, data.wire_version);
254
+ // SEC-6: one redaction_log row per distinct type found in this ingest.
255
+ recordRedactionEvents(db, redactionEventsForEvents, data.session_id);
256
+ jobId = randomUUID();
257
+ stmtInsertDistillEventsJob.run(jobId, JSON.stringify({ transcript_id: transcriptId, session_id: data.session_id }), now, now);
258
+ if (idempotencyKey !== null)
259
+ finalizeIdempotencySlot(idempotencyKey, bodyHash, transcriptId, now);
260
+ });
261
+ tx();
262
+ }
263
+ catch (err) {
264
+ const e = err;
265
+ if (e['_isReplay'] === true) {
266
+ log.info({ idempotency_key: idempotencyKey }, 'idempotent replay (events)');
267
+ return reply.code(200).send({
268
+ ok: true,
269
+ summary_memory_id: e['_replayId'],
270
+ session_id: data.session_id,
271
+ idempotent: true,
272
+ });
273
+ }
274
+ if (e['_isIdempotencyConflict'] === true) {
275
+ log.warn({ idempotency_key: idempotencyKey }, 'idempotency key reused with different body (events)');
276
+ return reply.code(409).send({
277
+ error: 'idempotency_conflict',
278
+ idempotency_key: idempotencyKey,
279
+ detail: 'key reused with different body',
280
+ });
281
+ }
282
+ if (typeof e['code'] === 'string' &&
283
+ (e['code'] === 'SQLITE_CONSTRAINT_PRIMARYKEY' || e['code'] === 'SQLITE_CONSTRAINT_UNIQUE')) {
284
+ log.warn({ idempotency_key: idempotencyKey, err: String(err) }, 'sqlite constraint on idempotency insert — treating as replay (events)');
285
+ const existing = stmtSelectIdempotency.get(idempotencyKey ?? '');
286
+ return reply.code(200).send({
287
+ ok: true,
288
+ summary_memory_id: existing?.summary_memory_id ?? null,
289
+ session_id: data.session_id,
290
+ idempotent: true,
291
+ });
292
+ }
293
+ throw err;
294
+ }
295
+ log.info({ job_id: jobId, transcript_id: transcriptId, wire_version: data.wire_version, event: data.event, event_count: events.length, tool: data.tool ?? null }, 'events ingested, distill-events job enqueued');
296
+ return reply.code(200).send({
297
+ ok: true,
298
+ summary_memory_id: transcriptId,
299
+ session_id: data.session_id,
300
+ idempotent: false,
301
+ });
302
+ }
303
+ // ---- Stage-0 secret redaction (SEC-3/5) — BEFORE the transcripts INSERT.
304
+ // Downstream pipeline stages inherit the redacted turns.
305
+ const turns = data.turns; // superRefine guarantees non-empty when kind !== 'events'
306
+ const redactionEvents = [];
307
+ const redactedTurns = turns.map(turn => {
308
+ const { text, events } = redactIfEnabled(turn.text, config);
309
+ redactionEvents.push(...events);
310
+ return { ...turn, text };
311
+ });
312
+ // ---- Idempotency-key handling ----
313
+ // D-B1: use stableStringify on the Zod-parsed object so key order
314
+ // differences between clients don't produce different hashes.
315
+ const idempotencyKey = req.headers['idempotency-key'] ?? null;
316
+ const bodyHash = createHash('sha256').update(stableStringify(data)).digest('hex');
317
+ const now = Date.now();
318
+ const capturedAtMs = new Date(data.captured_at).getTime();
319
+ let transcriptId;
320
+ let jobId;
321
+ // D-B2: move idempotency SELECT inside the transaction and use
322
+ // INSERT … ON CONFLICT DO NOTHING to make concurrent posts safe.
323
+ // The UNIQUE constraint can no longer leak a 500 to the caller.
324
+ try {
325
+ const tx = db.transaction(() => {
326
+ // --- Idempotency check (inside transaction — race-safe) ---
327
+ if (idempotencyKey !== null) {
328
+ // Attempt to claim the idempotency slot atomically.
329
+ // stmtInsertIdempotency uses ON CONFLICT DO NOTHING, so concurrent
330
+ // posts with the same key will silently no-op; we then read back
331
+ // the winner row and either replay or conflict.
332
+ stmtInsertIdempotency.run(idempotencyKey, bodyHash, '__pending__', now);
333
+ // Read the winner row (may have been written by a concurrent request)
334
+ const existing = stmtSelectIdempotency.get(idempotencyKey);
335
+ if (existing && existing.summary_memory_id !== '__pending__') {
336
+ // Another request already completed this key
337
+ if (existing.body_hash !== bodyHash) {
338
+ // Different body — conflict (throw so tx rolls back the DO NOTHING insert)
339
+ throw Object.assign(new Error('idempotency_conflict'), { _isIdempotencyConflict: true });
340
+ }
341
+ // D-B3: guard against a dangling row. summary_memory_id may
342
+ // point at either a transcript (pre-distillation) or a
343
+ // memory (post-D-DEF2-backfill) — check both.
344
+ const transcriptExists = existing.summary_memory_id != null
345
+ ? (stmtCheckTranscriptExists.get(existing.summary_memory_id)
346
+ ?? stmtCheckMemoryExists.get(existing.summary_memory_id))
347
+ : undefined;
348
+ if (!transcriptExists) {
349
+ // Dangling row — invalidate and fall through to fresh insert
350
+ stmtDeleteIdempotency.run(idempotencyKey);
351
+ // Re-insert placeholder so the fresh insert below can update it
352
+ stmtInsertIdempotency.run(idempotencyKey, bodyHash, '__pending__', now);
353
+ }
354
+ else {
355
+ // Valid replay — signal via special throw to exit tx and return early
356
+ throw Object.assign(new Error('idempotent_replay'), {
357
+ _isReplay: true,
358
+ _replayId: existing.summary_memory_id,
359
+ });
360
+ }
361
+ }
362
+ }
363
+ // --- Real insert ---
364
+ stmtUpsertSession.run(data.session_id, data.project_id, data.agent_type ?? null, data.cwd ?? null, now);
365
+ transcriptId = randomUUID();
366
+ stmtInsertTranscript.run(transcriptId, data.session_id, `claude-code-${data.event}`, JSON.stringify(redactedTurns), now, data.event, capturedAtMs, data.client_scrub_applied ? 1 : 0, data.client_scrub_hits ?? 0, data.client_scrub_version ?? null, data.client_scrub_hits_by_label != null
367
+ ? JSON.stringify(data.client_scrub_hits_by_label)
368
+ : null, data.client_version ?? null, data.wire_version);
369
+ // SEC-6: one redaction_log row per distinct type found in this ingest.
370
+ recordRedactionEvents(db, redactionEvents, data.session_id);
371
+ jobId = randomUUID();
372
+ stmtInsertJob.run(jobId, JSON.stringify({ transcript_id: transcriptId, session_id: data.session_id }), now, now);
373
+ // Update the idempotency placeholder with the real transcript id
374
+ if (idempotencyKey !== null) {
375
+ stmtDeleteIdempotency.run(idempotencyKey);
376
+ stmtInsertIdempotency.run(idempotencyKey, bodyHash, transcriptId, now);
377
+ }
378
+ });
379
+ tx();
380
+ }
381
+ catch (err) {
382
+ // Belt-and-suspenders: catch SQLite UNIQUE constraint errors that
383
+ // weren't handled by ON CONFLICT DO NOTHING (shouldn't happen, but
384
+ // guard just in case).
385
+ const e = err;
386
+ if (e['_isReplay'] === true) {
387
+ log.info({ idempotency_key: idempotencyKey }, 'idempotent replay');
388
+ return reply.code(200).send({
389
+ ok: true,
390
+ summary_memory_id: e['_replayId'],
391
+ session_id: data.session_id,
392
+ idempotent: true,
393
+ });
394
+ }
395
+ if (e['_isIdempotencyConflict'] === true) {
396
+ log.warn({ idempotency_key: idempotencyKey }, 'idempotency key reused with different body');
397
+ return reply.code(409).send({
398
+ error: 'idempotency_conflict',
399
+ idempotency_key: idempotencyKey,
400
+ detail: 'key reused with different body',
401
+ });
402
+ }
403
+ // SQLite UNIQUE constraint (belt+suspenders)
404
+ if (typeof e['code'] === 'string' &&
405
+ (e['code'] === 'SQLITE_CONSTRAINT_PRIMARYKEY' || e['code'] === 'SQLITE_CONSTRAINT_UNIQUE')) {
406
+ log.warn({ idempotency_key: idempotencyKey, err: String(err) }, 'sqlite constraint on idempotency insert — treating as replay');
407
+ const existing = stmtSelectIdempotency.get(idempotencyKey ?? '');
408
+ return reply.code(200).send({
409
+ ok: true,
410
+ summary_memory_id: existing?.summary_memory_id ?? null,
411
+ session_id: data.session_id,
412
+ idempotent: true,
413
+ });
414
+ }
415
+ throw err;
416
+ }
417
+ log.info({ job_id: jobId, transcript_id: transcriptId, wire_version: data.wire_version, event: data.event }, 'canonical transcript ingested, distill job enqueued');
418
+ return reply.code(200).send({
419
+ ok: true,
420
+ summary_memory_id: transcriptId,
421
+ session_id: data.session_id,
422
+ idempotent: false,
423
+ });
424
+ }
425
+ // ------------------------------------------------------------------
426
+ // Legacy path — unchanged insert logic, plus stage-0 redaction (SEC-3/5)
427
+ // ------------------------------------------------------------------
428
+ const { session_id, source, content, repo, project, branch, agent } = data;
429
+ const now = Date.now();
430
+ const { text: redactedContent, events: redactionEventsLegacy } = redactIfEnabled(content, config);
431
+ let transcriptId;
432
+ let jobId;
433
+ const tx = db.transaction(() => {
434
+ stmtUpsertSessionLegacy.run(session_id, repo ?? null, project ?? null, branch ?? null, agent ?? null, now);
435
+ transcriptId = randomUUID();
436
+ stmtInsertTranscriptLegacy.run(transcriptId, session_id, source, redactedContent, now);
437
+ // SEC-6: one redaction_log row per distinct type found in this ingest.
438
+ recordRedactionEvents(db, redactionEventsLegacy, session_id);
439
+ jobId = randomUUID();
440
+ stmtInsertJobLegacy.run(jobId, JSON.stringify({ transcript_id: transcriptId, session_id }), now, now);
441
+ });
442
+ tx();
443
+ // Bind session_id + transcript_id to log context for this ingest
444
+ const log = childLogger({
445
+ request_id: requestId ?? 'unknown',
446
+ session_id,
447
+ transcript_id: transcriptId,
448
+ });
449
+ log.info({ job_id: jobId, source }, 'transcript ingested, distill job enqueued');
450
+ return { ok: true };
451
+ });
452
+ };
453
+ }
454
+ //# sourceMappingURL=ingest.js.map