@askdkc/kiokuko 0.1.0

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 (391) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja.md +112 -0
  3. package/README.ko.md +112 -0
  4. package/README.md +147 -0
  5. package/README.zh-CN.md +112 -0
  6. package/dist/agent-file/atomic-write.d.ts +6 -0
  7. package/dist/agent-file/atomic-write.d.ts.map +1 -0
  8. package/dist/agent-file/atomic-write.js +36 -0
  9. package/dist/agent-file/atomic-write.js.map +1 -0
  10. package/dist/agent-file/managed-block.d.ts +12 -0
  11. package/dist/agent-file/managed-block.d.ts.map +1 -0
  12. package/dist/agent-file/managed-block.js +56 -0
  13. package/dist/agent-file/managed-block.js.map +1 -0
  14. package/dist/agent-file/render.d.ts +14 -0
  15. package/dist/agent-file/render.d.ts.map +1 -0
  16. package/dist/agent-file/render.js +41 -0
  17. package/dist/agent-file/render.js.map +1 -0
  18. package/dist/akinator/domain.d.ts +25 -0
  19. package/dist/akinator/domain.d.ts.map +1 -0
  20. package/dist/akinator/domain.js +255 -0
  21. package/dist/akinator/domain.js.map +1 -0
  22. package/dist/akinator/orchestrator.d.ts +8 -0
  23. package/dist/akinator/orchestrator.d.ts.map +1 -0
  24. package/dist/akinator/orchestrator.js +11 -0
  25. package/dist/akinator/orchestrator.js.map +1 -0
  26. package/dist/akinator/service.d.ts +35 -0
  27. package/dist/akinator/service.d.ts.map +1 -0
  28. package/dist/akinator/service.js +296 -0
  29. package/dist/akinator/service.js.map +1 -0
  30. package/dist/akinator/store.d.ts +96 -0
  31. package/dist/akinator/store.d.ts.map +1 -0
  32. package/dist/akinator/store.js +583 -0
  33. package/dist/akinator/store.js.map +1 -0
  34. package/dist/akinator/types.d.ts +49 -0
  35. package/dist/akinator/types.d.ts.map +1 -0
  36. package/dist/akinator/types.js +2 -0
  37. package/dist/akinator/types.js.map +1 -0
  38. package/dist/bin/kiokuko.d.ts +3 -0
  39. package/dist/bin/kiokuko.d.ts.map +1 -0
  40. package/dist/bin/kiokuko.js +6 -0
  41. package/dist/bin/kiokuko.js.map +1 -0
  42. package/dist/cli.d.ts +10 -0
  43. package/dist/cli.d.ts.map +1 -0
  44. package/dist/cli.js +362 -0
  45. package/dist/cli.js.map +1 -0
  46. package/dist/client/runtime-discovery.d.ts +15 -0
  47. package/dist/client/runtime-discovery.d.ts.map +1 -0
  48. package/dist/client/runtime-discovery.js +45 -0
  49. package/dist/client/runtime-discovery.js.map +1 -0
  50. package/dist/client/server-client.d.ts +20 -0
  51. package/dist/client/server-client.d.ts.map +1 -0
  52. package/dist/client/server-client.js +424 -0
  53. package/dist/client/server-client.js.map +1 -0
  54. package/dist/commands/agent.d.ts +10 -0
  55. package/dist/commands/agent.d.ts.map +1 -0
  56. package/dist/commands/agent.js +283 -0
  57. package/dist/commands/agent.js.map +1 -0
  58. package/dist/commands/backup.d.ts +5 -0
  59. package/dist/commands/backup.d.ts.map +1 -0
  60. package/dist/commands/backup.js +14 -0
  61. package/dist/commands/backup.js.map +1 -0
  62. package/dist/commands/call.d.ts +7 -0
  63. package/dist/commands/call.d.ts.map +1 -0
  64. package/dist/commands/call.js +10 -0
  65. package/dist/commands/call.js.map +1 -0
  66. package/dist/commands/doctor.d.ts +31 -0
  67. package/dist/commands/doctor.d.ts.map +1 -0
  68. package/dist/commands/doctor.js +142 -0
  69. package/dist/commands/doctor.js.map +1 -0
  70. package/dist/commands/export.d.ts +21 -0
  71. package/dist/commands/export.d.ts.map +1 -0
  72. package/dist/commands/export.js +68 -0
  73. package/dist/commands/export.js.map +1 -0
  74. package/dist/commands/import.d.ts +15 -0
  75. package/dist/commands/import.d.ts.map +1 -0
  76. package/dist/commands/import.js +265 -0
  77. package/dist/commands/import.js.map +1 -0
  78. package/dist/commands/init.d.ts +14 -0
  79. package/dist/commands/init.d.ts.map +1 -0
  80. package/dist/commands/init.js +26 -0
  81. package/dist/commands/init.js.map +1 -0
  82. package/dist/commands/ledger.d.ts +7 -0
  83. package/dist/commands/ledger.d.ts.map +1 -0
  84. package/dist/commands/ledger.js +66 -0
  85. package/dist/commands/ledger.js.map +1 -0
  86. package/dist/commands/link.d.ts +6 -0
  87. package/dist/commands/link.d.ts.map +1 -0
  88. package/dist/commands/link.js +6 -0
  89. package/dist/commands/link.js.map +1 -0
  90. package/dist/commands/promote.d.ts +6 -0
  91. package/dist/commands/promote.d.ts.map +1 -0
  92. package/dist/commands/promote.js +6 -0
  93. package/dist/commands/promote.js.map +1 -0
  94. package/dist/commands/purge.d.ts +10 -0
  95. package/dist/commands/purge.d.ts.map +1 -0
  96. package/dist/commands/purge.js +26 -0
  97. package/dist/commands/purge.js.map +1 -0
  98. package/dist/commands/read.d.ts +6 -0
  99. package/dist/commands/read.d.ts.map +1 -0
  100. package/dist/commands/read.js +4 -0
  101. package/dist/commands/read.js.map +1 -0
  102. package/dist/commands/recall.d.ts +6 -0
  103. package/dist/commands/recall.d.ts.map +1 -0
  104. package/dist/commands/recall.js +4 -0
  105. package/dist/commands/recall.js.map +1 -0
  106. package/dist/commands/record.d.ts +6 -0
  107. package/dist/commands/record.d.ts.map +1 -0
  108. package/dist/commands/record.js +4 -0
  109. package/dist/commands/record.js.map +1 -0
  110. package/dist/commands/search.d.ts +6 -0
  111. package/dist/commands/search.d.ts.map +1 -0
  112. package/dist/commands/search.js +4 -0
  113. package/dist/commands/search.js.map +1 -0
  114. package/dist/commands/serve.d.ts +34 -0
  115. package/dist/commands/serve.d.ts.map +1 -0
  116. package/dist/commands/serve.js +120 -0
  117. package/dist/commands/serve.js.map +1 -0
  118. package/dist/commands/server-status.d.ts +20 -0
  119. package/dist/commands/server-status.d.ts.map +1 -0
  120. package/dist/commands/server-status.js +21 -0
  121. package/dist/commands/server-status.js.map +1 -0
  122. package/dist/commands/server.d.ts +13 -0
  123. package/dist/commands/server.d.ts.map +1 -0
  124. package/dist/commands/server.js +89 -0
  125. package/dist/commands/server.js.map +1 -0
  126. package/dist/commands/setup.d.ts +32 -0
  127. package/dist/commands/setup.d.ts.map +1 -0
  128. package/dist/commands/setup.js +121 -0
  129. package/dist/commands/setup.js.map +1 -0
  130. package/dist/commands/supersede.d.ts +6 -0
  131. package/dist/commands/supersede.d.ts.map +1 -0
  132. package/dist/commands/supersede.js +6 -0
  133. package/dist/commands/supersede.js.map +1 -0
  134. package/dist/commands/use.d.ts +28 -0
  135. package/dist/commands/use.d.ts.map +1 -0
  136. package/dist/commands/use.js +176 -0
  137. package/dist/commands/use.js.map +1 -0
  138. package/dist/config/paths.d.ts +18 -0
  139. package/dist/config/paths.d.ts.map +1 -0
  140. package/dist/config/paths.js +104 -0
  141. package/dist/config/paths.js.map +1 -0
  142. package/dist/config/project-config.d.ts +10 -0
  143. package/dist/config/project-config.d.ts.map +1 -0
  144. package/dist/config/project-config.js +59 -0
  145. package/dist/config/project-config.js.map +1 -0
  146. package/dist/context/broker.d.ts +80 -0
  147. package/dist/context/broker.d.ts.map +1 -0
  148. package/dist/context/broker.js +437 -0
  149. package/dist/context/broker.js.map +1 -0
  150. package/dist/context/delivery.d.ts +66 -0
  151. package/dist/context/delivery.d.ts.map +1 -0
  152. package/dist/context/delivery.js +758 -0
  153. package/dist/context/delivery.js.map +1 -0
  154. package/dist/context/feedback.d.ts +75 -0
  155. package/dist/context/feedback.d.ts.map +1 -0
  156. package/dist/context/feedback.js +997 -0
  157. package/dist/context/feedback.js.map +1 -0
  158. package/dist/context/ranking.d.ts +98 -0
  159. package/dist/context/ranking.d.ts.map +1 -0
  160. package/dist/context/ranking.js +513 -0
  161. package/dist/context/ranking.js.map +1 -0
  162. package/dist/context/recommendations.d.ts +85 -0
  163. package/dist/context/recommendations.d.ts.map +1 -0
  164. package/dist/context/recommendations.js +342 -0
  165. package/dist/context/recommendations.js.map +1 -0
  166. package/dist/db/adapter.d.ts +25 -0
  167. package/dist/db/adapter.d.ts.map +1 -0
  168. package/dist/db/adapter.js +37 -0
  169. package/dist/db/adapter.js.map +1 -0
  170. package/dist/db/capabilities.d.ts +13 -0
  171. package/dist/db/capabilities.d.ts.map +1 -0
  172. package/dist/db/capabilities.js +25 -0
  173. package/dist/db/capabilities.js.map +1 -0
  174. package/dist/db/connection.d.ts +6 -0
  175. package/dist/db/connection.d.ts.map +1 -0
  176. package/dist/db/connection.js +42 -0
  177. package/dist/db/connection.js.map +1 -0
  178. package/dist/db/migrate.d.ts +9 -0
  179. package/dist/db/migrate.d.ts.map +1 -0
  180. package/dist/db/migrate.js +102 -0
  181. package/dist/db/migrate.js.map +1 -0
  182. package/dist/db/transaction.d.ts +4 -0
  183. package/dist/db/transaction.d.ts.map +1 -0
  184. package/dist/db/transaction.js +39 -0
  185. package/dist/db/transaction.js.map +1 -0
  186. package/dist/errors.d.ts +9 -0
  187. package/dist/errors.d.ts.map +1 -0
  188. package/dist/errors.js +32 -0
  189. package/dist/errors.js.map +1 -0
  190. package/dist/gateway/agent-service.d.ts +69 -0
  191. package/dist/gateway/agent-service.d.ts.map +1 -0
  192. package/dist/gateway/agent-service.js +581 -0
  193. package/dist/gateway/agent-service.js.map +1 -0
  194. package/dist/gateway/checkpoint-service.d.ts +42 -0
  195. package/dist/gateway/checkpoint-service.d.ts.map +1 -0
  196. package/dist/gateway/checkpoint-service.js +295 -0
  197. package/dist/gateway/checkpoint-service.js.map +1 -0
  198. package/dist/knowledge/sources.d.ts +72 -0
  199. package/dist/knowledge/sources.d.ts.map +1 -0
  200. package/dist/knowledge/sources.js +406 -0
  201. package/dist/knowledge/sources.js.map +1 -0
  202. package/dist/ledger/archive.d.ts +50 -0
  203. package/dist/ledger/archive.d.ts.map +1 -0
  204. package/dist/ledger/archive.js +1072 -0
  205. package/dist/ledger/archive.js.map +1 -0
  206. package/dist/ledger/hash.d.ts +22 -0
  207. package/dist/ledger/hash.d.ts.map +1 -0
  208. package/dist/ledger/hash.js +25 -0
  209. package/dist/ledger/hash.js.map +1 -0
  210. package/dist/ledger/maintenance.d.ts +78 -0
  211. package/dist/ledger/maintenance.d.ts.map +1 -0
  212. package/dist/ledger/maintenance.js +683 -0
  213. package/dist/ledger/maintenance.js.map +1 -0
  214. package/dist/ledger/projection.d.ts +37 -0
  215. package/dist/ledger/projection.d.ts.map +1 -0
  216. package/dist/ledger/projection.js +307 -0
  217. package/dist/ledger/projection.js.map +1 -0
  218. package/dist/ledger/promotion.d.ts +28 -0
  219. package/dist/ledger/promotion.d.ts.map +1 -0
  220. package/dist/ledger/promotion.js +308 -0
  221. package/dist/ledger/promotion.js.map +1 -0
  222. package/dist/ledger/query.d.ts +55 -0
  223. package/dist/ledger/query.d.ts.map +1 -0
  224. package/dist/ledger/query.js +321 -0
  225. package/dist/ledger/query.js.map +1 -0
  226. package/dist/ledger/redaction.d.ts +8 -0
  227. package/dist/ledger/redaction.d.ts.map +1 -0
  228. package/dist/ledger/redaction.js +40 -0
  229. package/dist/ledger/redaction.js.map +1 -0
  230. package/dist/ledger/store.d.ts +46 -0
  231. package/dist/ledger/store.d.ts.map +1 -0
  232. package/dist/ledger/store.js +324 -0
  233. package/dist/ledger/store.js.map +1 -0
  234. package/dist/ledger/types.d.ts +119 -0
  235. package/dist/ledger/types.d.ts.map +1 -0
  236. package/dist/ledger/types.js +22 -0
  237. package/dist/ledger/types.js.map +1 -0
  238. package/dist/ledger/validate.d.ts +10 -0
  239. package/dist/ledger/validate.d.ts.map +1 -0
  240. package/dist/ledger/validate.js +183 -0
  241. package/dist/ledger/validate.js.map +1 -0
  242. package/dist/mcp/server.d.ts +9 -0
  243. package/dist/mcp/server.d.ts.map +1 -0
  244. package/dist/mcp/server.js +86 -0
  245. package/dist/mcp/server.js.map +1 -0
  246. package/dist/memory/audit.d.ts +14 -0
  247. package/dist/memory/audit.d.ts.map +1 -0
  248. package/dist/memory/audit.js +16 -0
  249. package/dist/memory/audit.js.map +1 -0
  250. package/dist/memory/entries.d.ts +65 -0
  251. package/dist/memory/entries.d.ts.map +1 -0
  252. package/dist/memory/entries.js +200 -0
  253. package/dist/memory/entries.js.map +1 -0
  254. package/dist/memory/lifecycle.d.ts +29 -0
  255. package/dist/memory/lifecycle.d.ts.map +1 -0
  256. package/dist/memory/lifecycle.js +61 -0
  257. package/dist/memory/lifecycle.js.map +1 -0
  258. package/dist/memory/retrieval.d.ts +44 -0
  259. package/dist/memory/retrieval.d.ts.map +1 -0
  260. package/dist/memory/retrieval.js +174 -0
  261. package/dist/memory/retrieval.js.map +1 -0
  262. package/dist/memory/scoped-memory.d.ts +41 -0
  263. package/dist/memory/scoped-memory.d.ts.map +1 -0
  264. package/dist/memory/scoped-memory.js +65 -0
  265. package/dist/memory/scoped-memory.js.map +1 -0
  266. package/dist/memory/secrets.d.ts +6 -0
  267. package/dist/memory/secrets.d.ts.map +1 -0
  268. package/dist/memory/secrets.js +17 -0
  269. package/dist/memory/secrets.js.map +1 -0
  270. package/dist/memory/workspaces.d.ts +17 -0
  271. package/dist/memory/workspaces.d.ts.map +1 -0
  272. package/dist/memory/workspaces.js +92 -0
  273. package/dist/memory/workspaces.js.map +1 -0
  274. package/dist/repository/binding.d.ts +19 -0
  275. package/dist/repository/binding.d.ts.map +1 -0
  276. package/dist/repository/binding.js +78 -0
  277. package/dist/repository/binding.js.map +1 -0
  278. package/dist/repository/detect-root.d.ts +12 -0
  279. package/dist/repository/detect-root.d.ts.map +1 -0
  280. package/dist/repository/detect-root.js +57 -0
  281. package/dist/repository/detect-root.js.map +1 -0
  282. package/dist/repository/git-origin.d.ts +2 -0
  283. package/dist/repository/git-origin.d.ts.map +1 -0
  284. package/dist/repository/git-origin.js +15 -0
  285. package/dist/repository/git-origin.js.map +1 -0
  286. package/dist/repository/identity.d.ts +19 -0
  287. package/dist/repository/identity.d.ts.map +1 -0
  288. package/dist/repository/identity.js +23 -0
  289. package/dist/repository/identity.js.map +1 -0
  290. package/dist/repository/remote-url.d.ts +4 -0
  291. package/dist/repository/remote-url.d.ts.map +1 -0
  292. package/dist/repository/remote-url.js +36 -0
  293. package/dist/repository/remote-url.js.map +1 -0
  294. package/dist/security/sanitize.d.ts +4 -0
  295. package/dist/security/sanitize.d.ts.map +1 -0
  296. package/dist/security/sanitize.js +174 -0
  297. package/dist/security/sanitize.js.map +1 -0
  298. package/dist/serialization/envelope.d.ts +20 -0
  299. package/dist/serialization/envelope.d.ts.map +1 -0
  300. package/dist/serialization/envelope.js +11 -0
  301. package/dist/serialization/envelope.js.map +1 -0
  302. package/dist/serialization/validate.d.ts +31 -0
  303. package/dist/serialization/validate.d.ts.map +1 -0
  304. package/dist/serialization/validate.js +168 -0
  305. package/dist/serialization/validate.js.map +1 -0
  306. package/dist/server/agent-application.d.ts +6 -0
  307. package/dist/server/agent-application.d.ts.map +1 -0
  308. package/dist/server/agent-application.js +60 -0
  309. package/dist/server/agent-application.js.map +1 -0
  310. package/dist/server/app.d.ts +5 -0
  311. package/dist/server/app.d.ts.map +1 -0
  312. package/dist/server/app.js +87 -0
  313. package/dist/server/app.js.map +1 -0
  314. package/dist/server/auth.d.ts +2 -0
  315. package/dist/server/auth.d.ts.map +1 -0
  316. package/dist/server/auth.js +22 -0
  317. package/dist/server/auth.js.map +1 -0
  318. package/dist/server/http.d.ts +69 -0
  319. package/dist/server/http.d.ts.map +1 -0
  320. package/dist/server/http.js +349 -0
  321. package/dist/server/http.js.map +1 -0
  322. package/dist/server/idempotency.d.ts +12 -0
  323. package/dist/server/idempotency.d.ts.map +1 -0
  324. package/dist/server/idempotency.js +210 -0
  325. package/dist/server/idempotency.js.map +1 -0
  326. package/dist/server/instance-lock.d.ts +17 -0
  327. package/dist/server/instance-lock.d.ts.map +1 -0
  328. package/dist/server/instance-lock.js +118 -0
  329. package/dist/server/instance-lock.js.map +1 -0
  330. package/dist/server/router.d.ts +29 -0
  331. package/dist/server/router.d.ts.map +1 -0
  332. package/dist/server/router.js +112 -0
  333. package/dist/server/router.js.map +1 -0
  334. package/dist/server/routes/agent-events.d.ts +5 -0
  335. package/dist/server/routes/agent-events.d.ts.map +1 -0
  336. package/dist/server/routes/agent-events.js +46 -0
  337. package/dist/server/routes/agent-events.js.map +1 -0
  338. package/dist/server/routes/agent-intake.d.ts +5 -0
  339. package/dist/server/routes/agent-intake.d.ts.map +1 -0
  340. package/dist/server/routes/agent-intake.js +42 -0
  341. package/dist/server/routes/agent-intake.js.map +1 -0
  342. package/dist/server/routes/agent-promotions.d.ts +5 -0
  343. package/dist/server/routes/agent-promotions.d.ts.map +1 -0
  344. package/dist/server/routes/agent-promotions.js +52 -0
  345. package/dist/server/routes/agent-promotions.js.map +1 -0
  346. package/dist/server/routes/agent-runs.d.ts +25 -0
  347. package/dist/server/routes/agent-runs.d.ts.map +1 -0
  348. package/dist/server/routes/agent-runs.js +151 -0
  349. package/dist/server/routes/agent-runs.js.map +1 -0
  350. package/dist/server/routes/task5-support.d.ts +11 -0
  351. package/dist/server/routes/task5-support.d.ts.map +1 -0
  352. package/dist/server/routes/task5-support.js +22 -0
  353. package/dist/server/routes/task5-support.js.map +1 -0
  354. package/dist/server/routes/task5.d.ts +5 -0
  355. package/dist/server/routes/task5.d.ts.map +1 -0
  356. package/dist/server/routes/task5.js +72 -0
  357. package/dist/server/routes/task5.js.map +1 -0
  358. package/dist/server/runtime-descriptor.d.ts +24 -0
  359. package/dist/server/runtime-descriptor.d.ts.map +1 -0
  360. package/dist/server/runtime-descriptor.js +156 -0
  361. package/dist/server/runtime-descriptor.js.map +1 -0
  362. package/dist/server/write-queue.d.ts +20 -0
  363. package/dist/server/write-queue.d.ts.map +1 -0
  364. package/dist/server/write-queue.js +83 -0
  365. package/dist/server/write-queue.js.map +1 -0
  366. package/dist/setup/managed-text.d.ts +6 -0
  367. package/dist/setup/managed-text.d.ts.map +1 -0
  368. package/dist/setup/managed-text.js +42 -0
  369. package/dist/setup/managed-text.js.map +1 -0
  370. package/dist/setup/opencode-config.d.ts +3 -0
  371. package/dist/setup/opencode-config.d.ts.map +1 -0
  372. package/dist/setup/opencode-config.js +26 -0
  373. package/dist/setup/opencode-config.js.map +1 -0
  374. package/dist/setup/render.d.ts +8 -0
  375. package/dist/setup/render.d.ts.map +1 -0
  376. package/dist/setup/render.js +45 -0
  377. package/dist/setup/render.js.map +1 -0
  378. package/dist/web/server.d.ts +16 -0
  379. package/dist/web/server.d.ts.map +1 -0
  380. package/dist/web/server.js +424 -0
  381. package/dist/web/server.js.map +1 -0
  382. package/dist/web/ui.d.ts +2 -0
  383. package/dist/web/ui.d.ts.map +1 -0
  384. package/dist/web/ui.js +271 -0
  385. package/dist/web/ui.js.map +1 -0
  386. package/migrations/001_initial.sql +89 -0
  387. package/migrations/002_fts.sql +66 -0
  388. package/migrations/003_akinator.sql +30 -0
  389. package/migrations/004_agent_gateway.sql +259 -0
  390. package/package.json +45 -0
  391. package/templates/AGENTS.md +29 -0
@@ -0,0 +1,997 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { withImmediateTransaction } from '../db/transaction.js';
3
+ import { KiokukoError } from '../errors.js';
4
+ import { sanitizeJson } from '../security/sanitize.js';
5
+ import { canonicalContentHash } from '../serialization/validate.js';
6
+ export const MAX_FEEDBACK_COMMENT_BYTES = 4 * 1024;
7
+ export const MAX_FEEDBACK_IDENTIFIER_LENGTH = 256;
8
+ export const CONTEXT_FEEDBACK_VERDICTS = ['helpful', 'irrelevant', 'stale', 'conflicting'];
9
+ export const RUN_FEEDBACK_RECOMMENDATION_VERDICTS = ['accepted', 'dismissed', 'resolved'];
10
+ const CONTEXT_INPUT_FIELDS = new Set([
11
+ 'workspace', 'feedbackId', 'deliveryId', 'entryId', 'runId', 'verdict',
12
+ 'comment', 'actor', 'idempotencyKey', 'createdAt',
13
+ ]);
14
+ const CONTEXT_LIST_FIELDS = new Set(['workspace', 'runId', 'deliveryId', 'entryId', 'limit']);
15
+ const RUN_INPUT_FIELDS = new Set([
16
+ 'workspace', 'feedbackId', 'runId', 'outcome', 'recommendationCode', 'recommendationVerdict',
17
+ 'rating', 'comment', 'actor', 'idempotencyKey', 'createdAt',
18
+ ]);
19
+ const RUN_LIST_FIELDS = new Set(['workspace', 'runId', 'limit']);
20
+ const VALIDATION_MESSAGE = 'Feedback input is invalid';
21
+ const NOT_FOUND_MESSAGE = 'Feedback target was not found';
22
+ const CONFLICT_MESSAGE = 'Feedback conflicts with existing record';
23
+ const INTEGRITY_MESSAGE = 'Stored feedback is invalid';
24
+ const DATABASE_MESSAGE = 'Feedback database operation failed';
25
+ function fail(code, message) {
26
+ throw new KiokukoError(code, message);
27
+ }
28
+ function validation() {
29
+ return fail('VALIDATION_ERROR', VALIDATION_MESSAGE);
30
+ }
31
+ function notFound() {
32
+ return fail('NOT_FOUND', NOT_FOUND_MESSAGE);
33
+ }
34
+ function conflict() {
35
+ return fail('CONFLICT', CONFLICT_MESSAGE);
36
+ }
37
+ function integrity() {
38
+ return fail('INTEGRITY_ERROR', INTEGRITY_MESSAGE);
39
+ }
40
+ function databaseFailure() {
41
+ return fail('DATABASE_ERROR', DATABASE_MESSAGE);
42
+ }
43
+ function isPlainObject(value) {
44
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
45
+ return false;
46
+ const prototype = Object.getPrototypeOf(value);
47
+ return prototype === Object.prototype || prototype === null;
48
+ }
49
+ function isKiokukoError(error) {
50
+ return error instanceof KiokukoError;
51
+ }
52
+ function normalizeDatabaseError(error) {
53
+ if (isKiokukoError(error))
54
+ throw error;
55
+ if (error instanceof Error && /unique|constraint/i.test(error.message))
56
+ conflict();
57
+ databaseFailure();
58
+ }
59
+ function requiredString(value) {
60
+ if (typeof value !== 'string' || value.length === 0 || value.length > MAX_FEEDBACK_IDENTIFIER_LENGTH)
61
+ validation();
62
+ return value;
63
+ }
64
+ function optionalText(value, maximum) {
65
+ if (value === undefined || value === null)
66
+ return null;
67
+ if (typeof value !== 'string' || value.length === 0 || value.length > maximum)
68
+ validation();
69
+ return value;
70
+ }
71
+ function normalizeTimestamp(value) {
72
+ if (typeof value !== 'string' || value.length === 0)
73
+ validation();
74
+ const date = new Date(value);
75
+ if (Number.isNaN(date.getTime()))
76
+ validation();
77
+ return date.toISOString();
78
+ }
79
+ function normalizeComment(value, workspace) {
80
+ if (value === undefined || value === null)
81
+ return null;
82
+ if (typeof value !== 'string')
83
+ validation();
84
+ if (value.trim().length === 0)
85
+ return null;
86
+ let sanitized;
87
+ try {
88
+ sanitized = sanitizeJson(value, { workspace }).value;
89
+ }
90
+ catch {
91
+ validation();
92
+ }
93
+ if (typeof sanitized !== 'string')
94
+ validation();
95
+ if (sanitized.trim().length === 0)
96
+ return null;
97
+ if (Buffer.byteLength(sanitized, 'utf8') > MAX_FEEDBACK_COMMENT_BYTES)
98
+ validation();
99
+ return sanitized;
100
+ }
101
+ function hashIdempotencyKey(value) {
102
+ return createHash('sha256').update(value, 'utf8').digest('hex');
103
+ }
104
+ function contextBodyHash(input) {
105
+ return canonicalContentHash({
106
+ feedbackId: input.feedbackId,
107
+ workspace: input.workspace,
108
+ deliveryId: input.deliveryId,
109
+ entryId: input.entryId,
110
+ runId: input.runId,
111
+ verdict: input.verdict,
112
+ comment: input.comment,
113
+ actor: input.actor,
114
+ createdAt: input.createdAt,
115
+ });
116
+ }
117
+ function runBodyHash(input) {
118
+ return canonicalContentHash({
119
+ feedbackId: input.feedbackId,
120
+ workspace: input.workspace,
121
+ runId: input.runId,
122
+ outcome: input.outcome,
123
+ recommendationCode: input.recommendationCode,
124
+ recommendationVerdict: input.recommendationVerdict,
125
+ rating: input.rating,
126
+ comment: input.comment,
127
+ actor: input.actor,
128
+ createdAt: input.createdAt,
129
+ });
130
+ }
131
+ function validateContextFeedbackInput(value) {
132
+ try {
133
+ if (!isPlainObject(value))
134
+ validation();
135
+ for (const field of Object.keys(value)) {
136
+ if (!CONTEXT_INPUT_FIELDS.has(field))
137
+ validation();
138
+ }
139
+ const workspace = requiredString(value.workspace);
140
+ const feedbackId = requiredString(value.feedbackId);
141
+ const deliveryId = requiredString(value.deliveryId);
142
+ const entryId = requiredString(value.entryId);
143
+ const runId = requiredString(value.runId);
144
+ if (typeof value.verdict !== 'string' || !CONTEXT_FEEDBACK_VERDICTS.includes(value.verdict))
145
+ validation();
146
+ const verdict = value.verdict;
147
+ const actor = requiredString(value.actor);
148
+ const idempotencyKey = requiredString(value.idempotencyKey);
149
+ const createdAt = normalizeTimestamp(value.createdAt);
150
+ const comment = normalizeComment(value.comment, workspace);
151
+ return {
152
+ feedbackId,
153
+ workspace,
154
+ deliveryId,
155
+ entryId,
156
+ runId,
157
+ verdict,
158
+ comment,
159
+ actor,
160
+ idempotencyKeyHash: hashIdempotencyKey(idempotencyKey),
161
+ createdAt,
162
+ };
163
+ }
164
+ catch (error) {
165
+ if (isKiokukoError(error) && error.code === 'VALIDATION_ERROR')
166
+ validation();
167
+ validation();
168
+ }
169
+ }
170
+ function validateContextFeedbackListInput(value) {
171
+ try {
172
+ if (!isPlainObject(value))
173
+ validation();
174
+ for (const field of Object.keys(value)) {
175
+ if (!CONTEXT_LIST_FIELDS.has(field))
176
+ validation();
177
+ }
178
+ const workspace = requiredString(value.workspace);
179
+ const runId = value.runId === undefined ? undefined : requiredString(value.runId);
180
+ const deliveryId = value.deliveryId === undefined ? undefined : requiredString(value.deliveryId);
181
+ const entryId = value.entryId === undefined ? undefined : requiredString(value.entryId);
182
+ const limit = value.limit === undefined ? 100 : value.limit;
183
+ if (typeof limit !== 'number' || !Number.isInteger(limit) || limit < 1 || limit > 100)
184
+ validation();
185
+ return {
186
+ workspace,
187
+ ...(runId === undefined ? {} : { runId }),
188
+ ...(deliveryId === undefined ? {} : { deliveryId }),
189
+ ...(entryId === undefined ? {} : { entryId }),
190
+ limit,
191
+ };
192
+ }
193
+ catch (error) {
194
+ if (isKiokukoError(error) && error.code === 'VALIDATION_ERROR')
195
+ validation();
196
+ validation();
197
+ }
198
+ }
199
+ function validateRunFeedbackListInput(value) {
200
+ try {
201
+ if (!isPlainObject(value))
202
+ validation();
203
+ for (const field of Object.keys(value)) {
204
+ if (!RUN_LIST_FIELDS.has(field))
205
+ validation();
206
+ }
207
+ const workspace = requiredString(value.workspace);
208
+ const runId = value.runId === undefined ? undefined : requiredString(value.runId);
209
+ const limit = value.limit === undefined ? 100 : value.limit;
210
+ if (typeof limit !== 'number' || !Number.isInteger(limit) || limit < 1 || limit > 100)
211
+ validation();
212
+ return { workspace, ...(runId === undefined ? {} : { runId }), limit };
213
+ }
214
+ catch (error) {
215
+ if (isKiokukoError(error) && error.code === 'VALIDATION_ERROR')
216
+ validation();
217
+ validation();
218
+ }
219
+ }
220
+ function validateRunFeedbackInput(value) {
221
+ try {
222
+ if (!isPlainObject(value))
223
+ validation();
224
+ for (const field of Object.keys(value)) {
225
+ if (!RUN_INPUT_FIELDS.has(field))
226
+ validation();
227
+ }
228
+ const workspace = requiredString(value.workspace);
229
+ const feedbackId = requiredString(value.feedbackId);
230
+ const runId = requiredString(value.runId);
231
+ const outcome = optionalText(value.outcome, MAX_FEEDBACK_COMMENT_BYTES);
232
+ const recommendationCode = optionalText(value.recommendationCode, MAX_FEEDBACK_IDENTIFIER_LENGTH);
233
+ const rawRecommendationVerdict = value.recommendationVerdict;
234
+ const recommendationVerdict = rawRecommendationVerdict === undefined || rawRecommendationVerdict === null
235
+ ? null
236
+ : typeof rawRecommendationVerdict === 'string' && RUN_FEEDBACK_RECOMMENDATION_VERDICTS.includes(rawRecommendationVerdict)
237
+ ? rawRecommendationVerdict
238
+ : validation();
239
+ if ((recommendationCode === null) !== (recommendationVerdict === null))
240
+ validation();
241
+ const rawRating = value.rating;
242
+ const rating = rawRating === undefined || rawRating === null
243
+ ? null
244
+ : typeof rawRating === 'number' && Number.isInteger(rawRating) && rawRating >= 1 && rawRating <= 5
245
+ ? rawRating
246
+ : validation();
247
+ if (outcome === null && recommendationCode === null && rating === null)
248
+ validation();
249
+ const actor = requiredString(value.actor);
250
+ const idempotencyKey = requiredString(value.idempotencyKey);
251
+ const createdAt = normalizeTimestamp(value.createdAt);
252
+ const comment = normalizeComment(value.comment, workspace);
253
+ return {
254
+ feedbackId,
255
+ workspace,
256
+ runId,
257
+ outcome,
258
+ recommendationCode,
259
+ recommendationVerdict,
260
+ rating,
261
+ comment,
262
+ actor,
263
+ idempotencyKeyHash: hashIdempotencyKey(idempotencyKey),
264
+ createdAt,
265
+ };
266
+ }
267
+ catch (error) {
268
+ if (isKiokukoError(error) && error.code === 'VALIDATION_ERROR')
269
+ validation();
270
+ validation();
271
+ }
272
+ }
273
+ function assertContextTarget(database, input) {
274
+ const target = database.prepare(`
275
+ SELECT 1 AS present
276
+ FROM context_deliveries AS cd
277
+ JOIN ledger_runs AS lr ON lr.run_id = cd.run_id
278
+ JOIN context_delivery_entries AS cde
279
+ ON cde.delivery_id = cd.delivery_id AND cde.entry_id = ?
280
+ JOIN entries AS e ON e.id = cde.entry_id
281
+ WHERE cd.delivery_id = ?
282
+ AND cd.run_id = ?
283
+ AND lr.workspace = ?
284
+ AND e.workspace = ?
285
+ `).get(input.entryId, input.deliveryId, input.runId, input.workspace, input.workspace);
286
+ if (!target)
287
+ notFound();
288
+ }
289
+ function selectContextByKey(database, input) {
290
+ return database.prepare(`
291
+ SELECT
292
+ cf.feedback_id, cf.delivery_id, cf.entry_id, cf.run_id, cf.verdict, cf.comment,
293
+ cf.actor, cf.idempotency_key, cf.created_at,
294
+ lr.workspace AS run_workspace,
295
+ cd.run_id AS delivery_run_id,
296
+ cd.delivery_id AS joined_delivery_id,
297
+ cde.delivery_id AS linked_delivery_id,
298
+ cde.entry_id AS linked_entry_id,
299
+ e.workspace AS entry_workspace
300
+ FROM context_feedback AS cf
301
+ LEFT JOIN ledger_runs AS lr ON lr.run_id = cf.run_id
302
+ LEFT JOIN context_deliveries AS cd ON cd.delivery_id = cf.delivery_id
303
+ LEFT JOIN context_delivery_entries AS cde
304
+ ON cde.delivery_id = cf.delivery_id AND cde.entry_id = cf.entry_id
305
+ LEFT JOIN entries AS e ON e.id = cf.entry_id
306
+ WHERE cf.run_id = ? AND cf.actor = ? AND cf.idempotency_key = ?
307
+ `).get(input.runId, input.actor, input.idempotencyKeyHash);
308
+ }
309
+ function hasContextFeedbackId(database, feedbackId) {
310
+ return Boolean(database.prepare('SELECT 1 AS present FROM context_feedback WHERE feedback_id = ?').get(feedbackId));
311
+ }
312
+ function storedString(value) {
313
+ if (typeof value !== 'string' || value.length === 0)
314
+ integrity();
315
+ return value;
316
+ }
317
+ function storedHash(value) {
318
+ if (typeof value !== 'string' || !/^[0-9a-f]{64}$/.test(value))
319
+ integrity();
320
+ return value;
321
+ }
322
+ function storedTimestamp(value) {
323
+ if (typeof value !== 'string')
324
+ integrity();
325
+ const date = new Date(value);
326
+ if (Number.isNaN(date.getTime()))
327
+ integrity();
328
+ const normalized = date.toISOString();
329
+ if (normalized !== value)
330
+ integrity();
331
+ return value;
332
+ }
333
+ function rowToContextFeedback(row, workspace) {
334
+ const feedbackId = storedString(row.feedback_id);
335
+ const deliveryId = storedString(row.delivery_id);
336
+ const entryId = storedString(row.entry_id);
337
+ const runId = storedString(row.run_id);
338
+ const actor = storedString(row.actor);
339
+ const runWorkspace = storedString(row.run_workspace);
340
+ if (runWorkspace !== workspace)
341
+ integrity();
342
+ if (storedString(row.delivery_run_id) !== runId)
343
+ integrity();
344
+ if (storedString(row.joined_delivery_id) !== deliveryId)
345
+ integrity();
346
+ if (storedString(row.linked_delivery_id) !== deliveryId)
347
+ integrity();
348
+ if (storedString(row.linked_entry_id) !== entryId)
349
+ integrity();
350
+ if (storedString(row.entry_workspace) !== workspace)
351
+ integrity();
352
+ if (typeof row.verdict !== 'string' || !CONTEXT_FEEDBACK_VERDICTS.includes(row.verdict))
353
+ integrity();
354
+ const verdict = row.verdict;
355
+ let comment;
356
+ if (row.comment === null) {
357
+ comment = null;
358
+ }
359
+ else if (typeof row.comment === 'string') {
360
+ if (row.comment.length === 0 || Buffer.byteLength(row.comment, 'utf8') > MAX_FEEDBACK_COMMENT_BYTES)
361
+ integrity();
362
+ let sanitized;
363
+ try {
364
+ sanitized = normalizeComment(row.comment, workspace);
365
+ }
366
+ catch {
367
+ integrity();
368
+ }
369
+ if (sanitized !== row.comment)
370
+ integrity();
371
+ comment = row.comment;
372
+ }
373
+ else {
374
+ integrity();
375
+ }
376
+ return {
377
+ feedbackId,
378
+ workspace,
379
+ deliveryId,
380
+ entryId,
381
+ runId,
382
+ verdict,
383
+ comment,
384
+ actor,
385
+ idempotencyKeyHash: storedHash(row.idempotency_key),
386
+ createdAt: storedTimestamp(row.created_at),
387
+ };
388
+ }
389
+ function writeContextFeedback(database, input) {
390
+ assertContextTarget(database, input);
391
+ const existingByKey = selectContextByKey(database, input);
392
+ if (existingByKey) {
393
+ const existing = rowToContextFeedback(existingByKey, input.workspace);
394
+ if (contextBodyHash(existing) === contextBodyHash(input))
395
+ return existing;
396
+ conflict();
397
+ }
398
+ if (hasContextFeedbackId(database, input.feedbackId))
399
+ conflict();
400
+ database.prepare(`
401
+ INSERT INTO context_feedback (
402
+ feedback_id, delivery_id, entry_id, run_id, verdict, comment, actor, idempotency_key, created_at
403
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
404
+ `).run(input.feedbackId, input.deliveryId, input.entryId, input.runId, input.verdict, input.comment, input.actor, input.idempotencyKeyHash, input.createdAt);
405
+ const row = selectContextByKey(database, input);
406
+ if (!row)
407
+ integrity();
408
+ return rowToContextFeedback(row, input.workspace);
409
+ }
410
+ export function recordContextFeedbackInTransaction(database, input) {
411
+ const validated = validateContextFeedbackInput(input);
412
+ try {
413
+ return writeContextFeedback(database, validated);
414
+ }
415
+ catch (error) {
416
+ normalizeDatabaseError(error);
417
+ }
418
+ }
419
+ export function recordContextFeedback(database, input) {
420
+ const validated = validateContextFeedbackInput(input);
421
+ try {
422
+ return withImmediateTransaction(database, () => writeContextFeedback(database, validated));
423
+ }
424
+ catch (error) {
425
+ normalizeDatabaseError(error);
426
+ }
427
+ }
428
+ function runOptionalStoredText(value, maximum) {
429
+ if (value === null)
430
+ return null;
431
+ if (typeof value !== 'string' || value.length === 0 || value.length > maximum)
432
+ integrity();
433
+ return value;
434
+ }
435
+ function runStoredComment(value, workspace) {
436
+ if (value === null)
437
+ return null;
438
+ if (typeof value !== 'string' || value.length === 0 || Buffer.byteLength(value, 'utf8') > MAX_FEEDBACK_COMMENT_BYTES)
439
+ integrity();
440
+ let sanitized;
441
+ try {
442
+ sanitized = normalizeComment(value, workspace);
443
+ }
444
+ catch {
445
+ integrity();
446
+ }
447
+ if (sanitized !== value)
448
+ integrity();
449
+ return value;
450
+ }
451
+ function runBodyHashFromInput(input) {
452
+ return runBodyHash(input);
453
+ }
454
+ function assertRunTarget(database, input) {
455
+ const target = database.prepare('SELECT 1 AS present FROM ledger_runs WHERE run_id = ? AND workspace = ?').get(input.runId, input.workspace);
456
+ if (!target)
457
+ notFound();
458
+ }
459
+ function selectRunByKey(database, input) {
460
+ return database.prepare(`
461
+ SELECT
462
+ rf.feedback_id, rf.run_id, rf.outcome, rf.recommendation_code, rf.recommendation_verdict,
463
+ rf.rating, rf.comment, rf.actor, rf.idempotency_key, rf.created_at,
464
+ lr.workspace AS run_workspace
465
+ FROM run_feedback AS rf
466
+ LEFT JOIN ledger_runs AS lr ON lr.run_id = rf.run_id
467
+ WHERE rf.run_id = ? AND rf.actor = ? AND rf.idempotency_key = ?
468
+ `).get(input.runId, input.actor, input.idempotencyKeyHash);
469
+ }
470
+ function hasRunFeedbackId(database, feedbackId) {
471
+ return Boolean(database.prepare('SELECT 1 AS present FROM run_feedback WHERE feedback_id = ?').get(feedbackId));
472
+ }
473
+ function rowToRunFeedback(row, workspace) {
474
+ const feedbackId = storedString(row.feedback_id);
475
+ const runId = storedString(row.run_id);
476
+ const runWorkspace = storedString(row.run_workspace);
477
+ if (runWorkspace !== workspace)
478
+ integrity();
479
+ const outcome = runOptionalStoredText(row.outcome, MAX_FEEDBACK_COMMENT_BYTES);
480
+ const recommendationCode = runOptionalStoredText(row.recommendation_code, MAX_FEEDBACK_IDENTIFIER_LENGTH);
481
+ let recommendationVerdict;
482
+ if (row.recommendation_verdict === null) {
483
+ recommendationVerdict = null;
484
+ }
485
+ else if (typeof row.recommendation_verdict === 'string' && RUN_FEEDBACK_RECOMMENDATION_VERDICTS.includes(row.recommendation_verdict)) {
486
+ recommendationVerdict = row.recommendation_verdict;
487
+ }
488
+ else {
489
+ integrity();
490
+ }
491
+ if ((recommendationCode === null) !== (recommendationVerdict === null))
492
+ integrity();
493
+ const rating = row.rating === null
494
+ ? null
495
+ : typeof row.rating === 'number' && Number.isInteger(row.rating) && row.rating >= 1 && row.rating <= 5
496
+ ? row.rating
497
+ : integrity();
498
+ if (outcome === null && recommendationCode === null && rating === null)
499
+ integrity();
500
+ const actor = storedString(row.actor);
501
+ const comment = runStoredComment(row.comment, workspace);
502
+ return {
503
+ feedbackId,
504
+ workspace,
505
+ runId,
506
+ outcome,
507
+ recommendationCode,
508
+ recommendationVerdict,
509
+ rating,
510
+ comment,
511
+ actor,
512
+ idempotencyKeyHash: storedHash(row.idempotency_key),
513
+ createdAt: storedTimestamp(row.created_at),
514
+ };
515
+ }
516
+ function writeRunFeedback(database, input) {
517
+ assertRunTarget(database, input);
518
+ const existingByKey = selectRunByKey(database, input);
519
+ if (existingByKey) {
520
+ const existing = rowToRunFeedback(existingByKey, input.workspace);
521
+ if (runBodyHash(existing) === runBodyHashFromInput(input))
522
+ return existing;
523
+ conflict();
524
+ }
525
+ if (hasRunFeedbackId(database, input.feedbackId))
526
+ conflict();
527
+ database.prepare(`
528
+ INSERT INTO run_feedback (
529
+ feedback_id, run_id, outcome, recommendation_code, recommendation_verdict,
530
+ rating, comment, actor, idempotency_key, created_at
531
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
532
+ `).run(input.feedbackId, input.runId, input.outcome, input.recommendationCode, input.recommendationVerdict, input.rating, input.comment, input.actor, input.idempotencyKeyHash, input.createdAt);
533
+ const row = selectRunByKey(database, input);
534
+ if (!row)
535
+ integrity();
536
+ return rowToRunFeedback(row, input.workspace);
537
+ }
538
+ export function recordRunFeedbackInTransaction(database, input) {
539
+ const validated = validateRunFeedbackInput(input);
540
+ try {
541
+ return writeRunFeedback(database, validated);
542
+ }
543
+ catch (error) {
544
+ normalizeDatabaseError(error);
545
+ }
546
+ }
547
+ export function recordRunFeedback(database, input) {
548
+ const validated = validateRunFeedbackInput(input);
549
+ try {
550
+ return withImmediateTransaction(database, () => writeRunFeedback(database, validated));
551
+ }
552
+ catch (error) {
553
+ normalizeDatabaseError(error);
554
+ }
555
+ }
556
+ export function listRunFeedback(database, input) {
557
+ const validated = validateRunFeedbackListInput(input);
558
+ try {
559
+ const conditions = ['(lr.workspace = ? OR lr.workspace IS NULL)'];
560
+ const parameters = [validated.workspace];
561
+ if (validated.runId !== undefined) {
562
+ conditions.push('rf.run_id = ?');
563
+ parameters.push(validated.runId);
564
+ }
565
+ const rows = database.prepare(`
566
+ SELECT
567
+ rf.feedback_id, rf.run_id, rf.outcome, rf.recommendation_code, rf.recommendation_verdict,
568
+ rf.rating, rf.comment, rf.actor, rf.idempotency_key, rf.created_at,
569
+ lr.workspace AS run_workspace
570
+ FROM run_feedback AS rf
571
+ LEFT JOIN ledger_runs AS lr ON lr.run_id = rf.run_id
572
+ WHERE ${conditions.join(' AND ')}
573
+ ORDER BY rf.created_at ASC, rf.feedback_id ASC
574
+ LIMIT ?
575
+ `).all(...parameters, validated.limit + 1);
576
+ const records = rows.map((row) => rowToRunFeedback(row, validated.workspace));
577
+ return { records: records.slice(0, validated.limit), truncated: records.length > validated.limit };
578
+ }
579
+ catch (error) {
580
+ normalizeDatabaseError(error);
581
+ }
582
+ }
583
+ export function listContextFeedback(database, input) {
584
+ const validated = validateContextFeedbackListInput(input);
585
+ try {
586
+ const conditions = ['(lr.workspace = ? OR lr.workspace IS NULL)'];
587
+ const parameters = [validated.workspace];
588
+ if (validated.runId !== undefined) {
589
+ conditions.push('cf.run_id = ?');
590
+ parameters.push(validated.runId);
591
+ }
592
+ if (validated.deliveryId !== undefined) {
593
+ conditions.push('cf.delivery_id = ?');
594
+ parameters.push(validated.deliveryId);
595
+ }
596
+ if (validated.entryId !== undefined) {
597
+ conditions.push('cf.entry_id = ?');
598
+ parameters.push(validated.entryId);
599
+ }
600
+ const rows = database.prepare(`
601
+ SELECT
602
+ cf.feedback_id, cf.delivery_id, cf.entry_id, cf.run_id, cf.verdict, cf.comment,
603
+ cf.actor, cf.idempotency_key, cf.created_at,
604
+ lr.workspace AS run_workspace,
605
+ cd.run_id AS delivery_run_id,
606
+ cd.delivery_id AS joined_delivery_id,
607
+ cde.delivery_id AS linked_delivery_id,
608
+ cde.entry_id AS linked_entry_id,
609
+ e.workspace AS entry_workspace
610
+ FROM context_feedback AS cf
611
+ LEFT JOIN ledger_runs AS lr ON lr.run_id = cf.run_id
612
+ LEFT JOIN context_deliveries AS cd ON cd.delivery_id = cf.delivery_id
613
+ LEFT JOIN context_delivery_entries AS cde
614
+ ON cde.delivery_id = cf.delivery_id AND cde.entry_id = cf.entry_id
615
+ LEFT JOIN entries AS e ON e.id = cf.entry_id
616
+ WHERE ${conditions.join(' AND ')}
617
+ ORDER BY cf.created_at ASC, cf.feedback_id ASC
618
+ LIMIT ?
619
+ `).all(...parameters, validated.limit + 1);
620
+ const records = rows.map((row) => rowToContextFeedback(row, validated.workspace));
621
+ return { records: records.slice(0, validated.limit), truncated: records.length > validated.limit };
622
+ }
623
+ catch (error) {
624
+ normalizeDatabaseError(error);
625
+ }
626
+ }
627
+ export const INTAKE_FEEDBACK_VERDICTS = ['helpful', 'unnecessary', 'corrected'];
628
+ const INTAKE_FEEDBACK_FIELDS = new Set([
629
+ 'workspace', 'feedbackId', 'runId', 'sessionId', 'questionId', 'profileField',
630
+ 'verdict', 'comment', 'actor', 'idempotencyKey', 'createdAt',
631
+ ]);
632
+ const INTAKE_PROFILE_FIELDS = new Set(['taskType', 'target', 'expected', 'constraints']);
633
+ function initialInputFields(value) {
634
+ try {
635
+ if (!isPlainObject(value))
636
+ validation();
637
+ const keys = Reflect.ownKeys(value);
638
+ if (keys.length !== INTAKE_FEEDBACK_FIELDS.size || keys.some((key) => typeof key !== 'string' || !INTAKE_FEEDBACK_FIELDS.has(key)))
639
+ validation();
640
+ const descriptors = Object.getOwnPropertyDescriptors(value);
641
+ const result = {};
642
+ for (const field of INTAKE_FEEDBACK_FIELDS) {
643
+ const descriptor = descriptors[field];
644
+ if (!descriptor || !('value' in descriptor) || descriptor.enumerable !== true)
645
+ validation();
646
+ result[field] = descriptor.value;
647
+ }
648
+ structuredClone(value);
649
+ return result;
650
+ }
651
+ catch {
652
+ validation();
653
+ }
654
+ }
655
+ function initialInputIdentifier(value) {
656
+ if (typeof value !== 'string' || value.length === 0 || Buffer.byteLength(value, 'utf8') > MAX_FEEDBACK_IDENTIFIER_LENGTH || /\p{Cc}/u.test(value))
657
+ validation();
658
+ return value;
659
+ }
660
+ function initialInputTimestamp(value) {
661
+ if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value))
662
+ validation();
663
+ const date = new Date(value);
664
+ if (Number.isNaN(date.getTime()) || date.toISOString() !== value)
665
+ validation();
666
+ return value;
667
+ }
668
+ function initialInputComment(value, workspace) {
669
+ if (value === null)
670
+ return null;
671
+ if (typeof value !== 'string' || value.length === 0)
672
+ validation();
673
+ let sanitized;
674
+ try {
675
+ sanitized = sanitizeJson(value, { workspace }).value;
676
+ }
677
+ catch {
678
+ validation();
679
+ }
680
+ if (typeof sanitized !== 'string' || sanitized.length === 0 || Buffer.byteLength(sanitized, 'utf8') > MAX_FEEDBACK_COMMENT_BYTES)
681
+ validation();
682
+ return sanitized;
683
+ }
684
+ function validateInitialIntakeFeedbackInput(value) {
685
+ const fields = initialInputFields(value);
686
+ const workspace = initialInputIdentifier(fields.workspace);
687
+ const feedbackId = initialInputIdentifier(fields.feedbackId);
688
+ const runId = initialInputIdentifier(fields.runId);
689
+ const sessionId = initialInputIdentifier(fields.sessionId);
690
+ const questionId = fields.questionId === null
691
+ ? null
692
+ : typeof fields.questionId === 'string' && INTAKE_PROFILE_FIELDS.has(fields.questionId)
693
+ ? fields.questionId
694
+ : validation();
695
+ const profileField = fields.profileField === null
696
+ ? null
697
+ : typeof fields.profileField === 'string' && INTAKE_PROFILE_FIELDS.has(fields.profileField)
698
+ ? fields.profileField
699
+ : validation();
700
+ if ((questionId === null) === (profileField === null))
701
+ validation();
702
+ if (typeof fields.verdict !== 'string' || !INTAKE_FEEDBACK_VERDICTS.includes(fields.verdict))
703
+ validation();
704
+ const actor = initialInputIdentifier(fields.actor);
705
+ const idempotencyKey = initialInputIdentifier(fields.idempotencyKey);
706
+ return {
707
+ workspace,
708
+ feedbackId,
709
+ runId,
710
+ sessionId,
711
+ questionId,
712
+ profileField,
713
+ verdict: fields.verdict,
714
+ comment: initialInputComment(fields.comment, workspace),
715
+ actor,
716
+ idempotencyKeyHash: hashIdempotencyKey(idempotencyKey),
717
+ createdAt: initialInputTimestamp(fields.createdAt),
718
+ };
719
+ }
720
+ function initialIntakeTarget(database, input) {
721
+ const linked = database.prepare(`
722
+ SELECT ri.run_id, ri.session_id
723
+ FROM run_intakes AS ri
724
+ JOIN ledger_runs AS lr ON lr.run_id = ri.run_id
725
+ JOIN akinator_sessions AS s ON s.id = ri.session_id
726
+ WHERE ri.run_id = ? AND ri.session_id = ?
727
+ AND lr.workspace = ? AND s.workspace = ?
728
+ `).get(input.runId, input.sessionId, input.workspace, input.workspace);
729
+ if (!linked)
730
+ notFound();
731
+ if (input.questionId !== null) {
732
+ const answer = database.prepare(`
733
+ SELECT 1 AS present
734
+ FROM akinator_answers
735
+ WHERE session_id = ? AND question_id = ?
736
+ `).get(input.sessionId, input.questionId);
737
+ if (!answer)
738
+ notFound();
739
+ }
740
+ }
741
+ function initialIntakeRecord(input) {
742
+ return {
743
+ feedbackId: input.feedbackId,
744
+ workspace: input.workspace,
745
+ runId: input.runId,
746
+ sessionId: input.sessionId,
747
+ questionId: input.questionId,
748
+ profileField: input.profileField,
749
+ verdict: input.verdict,
750
+ comment: input.comment,
751
+ actor: input.actor,
752
+ idempotencyKeyHash: input.idempotencyKeyHash,
753
+ createdAt: input.createdAt,
754
+ };
755
+ }
756
+ function initialIntakeBodyHash(input) {
757
+ return canonicalContentHash({
758
+ feedbackId: input.feedbackId,
759
+ workspace: input.workspace,
760
+ runId: input.runId,
761
+ sessionId: input.sessionId,
762
+ questionId: input.questionId,
763
+ profileField: input.profileField,
764
+ verdict: input.verdict,
765
+ comment: input.comment,
766
+ actor: input.actor,
767
+ createdAt: input.createdAt,
768
+ });
769
+ }
770
+ function selectInitialIntakeByKey(database, input) {
771
+ return database.prepare(`
772
+ SELECT
773
+ ifb.feedback_id, ifb.run_id, ifb.session_id, ifb.question_id, ifb.profile_field,
774
+ ifb.verdict, ifb.comment, ifb.actor, ifb.idempotency_key, ifb.created_at,
775
+ lr.workspace AS run_workspace,
776
+ s.workspace AS session_workspace,
777
+ ri.run_id AS linked_run_id,
778
+ ri.session_id AS linked_session_id,
779
+ aa.session_id AS question_answer_session_id
780
+ FROM intake_feedback AS ifb
781
+ LEFT JOIN ledger_runs AS lr ON lr.run_id = ifb.run_id
782
+ LEFT JOIN akinator_sessions AS s ON s.id = ifb.session_id
783
+ LEFT JOIN run_intakes AS ri ON ri.run_id = ifb.run_id AND ri.session_id = ifb.session_id
784
+ LEFT JOIN akinator_answers AS aa ON aa.session_id = ifb.session_id AND aa.question_id = ifb.question_id
785
+ WHERE ifb.run_id = ? AND ifb.actor = ? AND ifb.idempotency_key = ?
786
+ `).get(input.runId, input.actor, input.idempotencyKeyHash);
787
+ }
788
+ function hasInitialIntakeFeedbackId(database, feedbackId) {
789
+ return Boolean(database.prepare('SELECT 1 AS present FROM intake_feedback WHERE feedback_id = ?').get(feedbackId));
790
+ }
791
+ function initialStoredIdentifier(value) {
792
+ if (typeof value !== 'string' || value.length === 0 || Buffer.byteLength(value, 'utf8') > MAX_FEEDBACK_IDENTIFIER_LENGTH || /\p{Cc}/u.test(value))
793
+ integrity();
794
+ return value;
795
+ }
796
+ function initialStoredHash(value) {
797
+ if (typeof value !== 'string' || !/^[0-9a-f]{64}$/.test(value))
798
+ integrity();
799
+ return value;
800
+ }
801
+ function initialStoredTimestamp(value) {
802
+ if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value))
803
+ integrity();
804
+ const date = new Date(value);
805
+ if (Number.isNaN(date.getTime()) || date.toISOString() !== value)
806
+ integrity();
807
+ return value;
808
+ }
809
+ function initialStoredComment(value, workspace) {
810
+ if (value === null)
811
+ return null;
812
+ if (typeof value !== 'string' || value.length === 0 || Buffer.byteLength(value, 'utf8') > MAX_FEEDBACK_COMMENT_BYTES)
813
+ integrity();
814
+ let sanitized;
815
+ try {
816
+ sanitized = normalizeComment(value, workspace);
817
+ }
818
+ catch {
819
+ integrity();
820
+ }
821
+ if (sanitized !== value)
822
+ integrity();
823
+ return value;
824
+ }
825
+ function rowToInitialIntakeFeedback(row, workspace) {
826
+ const feedbackId = initialStoredIdentifier(row.feedback_id);
827
+ const runId = initialStoredIdentifier(row.run_id);
828
+ const sessionId = initialStoredIdentifier(row.session_id);
829
+ const actor = initialStoredIdentifier(row.actor);
830
+ const runWorkspace = initialStoredIdentifier(row.run_workspace);
831
+ const sessionWorkspace = initialStoredIdentifier(row.session_workspace);
832
+ const linkedRunId = initialStoredIdentifier(row.linked_run_id);
833
+ const linkedSessionId = initialStoredIdentifier(row.linked_session_id);
834
+ if (runWorkspace !== workspace || sessionWorkspace !== workspace || linkedRunId !== runId || linkedSessionId !== sessionId)
835
+ integrity();
836
+ const questionId = row.question_id === null
837
+ ? null
838
+ : typeof row.question_id === 'string' && INTAKE_PROFILE_FIELDS.has(row.question_id)
839
+ ? row.question_id
840
+ : integrity();
841
+ const profileField = row.profile_field === null
842
+ ? null
843
+ : typeof row.profile_field === 'string' && INTAKE_PROFILE_FIELDS.has(row.profile_field)
844
+ ? row.profile_field
845
+ : integrity();
846
+ if ((questionId === null) === (profileField === null))
847
+ integrity();
848
+ if (questionId !== null && initialStoredIdentifier(row.question_answer_session_id) !== sessionId)
849
+ integrity();
850
+ if (typeof row.verdict !== 'string' || !INTAKE_FEEDBACK_VERDICTS.includes(row.verdict))
851
+ integrity();
852
+ return {
853
+ feedbackId,
854
+ workspace,
855
+ runId,
856
+ sessionId,
857
+ questionId,
858
+ profileField,
859
+ verdict: row.verdict,
860
+ comment: initialStoredComment(row.comment, workspace),
861
+ actor,
862
+ idempotencyKeyHash: initialStoredHash(row.idempotency_key),
863
+ createdAt: initialStoredTimestamp(row.created_at),
864
+ };
865
+ }
866
+ function recordInitialIntakeFeedback(database, input) {
867
+ initialIntakeTarget(database, input);
868
+ const existingByKey = selectInitialIntakeByKey(database, input);
869
+ if (existingByKey) {
870
+ const existing = rowToInitialIntakeFeedback(existingByKey, input.workspace);
871
+ if (initialIntakeBodyHash(existing) === initialIntakeBodyHash(initialIntakeRecord(input)))
872
+ return existing;
873
+ conflict();
874
+ }
875
+ if (hasInitialIntakeFeedbackId(database, input.feedbackId))
876
+ conflict();
877
+ database.prepare(`
878
+ INSERT INTO intake_feedback (
879
+ feedback_id, run_id, session_id, question_id, profile_field, verdict, comment, actor, idempotency_key, created_at
880
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
881
+ `).run(input.feedbackId, input.runId, input.sessionId, input.questionId, input.profileField, input.verdict, input.comment, input.actor, input.idempotencyKeyHash, input.createdAt);
882
+ const row = selectInitialIntakeByKey(database, input);
883
+ if (!row)
884
+ integrity();
885
+ return rowToInitialIntakeFeedback(row, input.workspace);
886
+ }
887
+ export function recordIntakeFeedback(database, input) {
888
+ const validated = validateInitialIntakeFeedbackInput(input);
889
+ try {
890
+ return withImmediateTransaction(database, () => recordInitialIntakeFeedback(database, validated));
891
+ }
892
+ catch (error) {
893
+ normalizeDatabaseError(error);
894
+ }
895
+ }
896
+ export function recordIntakeFeedbackInTransaction(database, input) {
897
+ const validated = validateInitialIntakeFeedbackInput(input);
898
+ try {
899
+ return recordInitialIntakeFeedback(database, validated);
900
+ }
901
+ catch (error) {
902
+ normalizeDatabaseError(error);
903
+ }
904
+ }
905
+ const INTAKE_FEEDBACK_LIST_FIELDS = new Set(['workspace', 'runId', 'sessionId', 'questionId', 'profileField', 'limit']);
906
+ function initialListInputFields(value) {
907
+ try {
908
+ if (!isPlainObject(value))
909
+ validation();
910
+ const keys = Reflect.ownKeys(value);
911
+ if (keys.some((key) => typeof key !== 'string' || !INTAKE_FEEDBACK_LIST_FIELDS.has(key)))
912
+ validation();
913
+ const descriptors = Object.getOwnPropertyDescriptors(value);
914
+ const result = {};
915
+ for (const key of keys) {
916
+ if (typeof key !== 'string')
917
+ validation();
918
+ const descriptor = descriptors[key];
919
+ if (!descriptor || !('value' in descriptor) || descriptor.enumerable !== true)
920
+ validation();
921
+ result[key] = descriptor.value;
922
+ }
923
+ structuredClone(value);
924
+ return result;
925
+ }
926
+ catch {
927
+ validation();
928
+ }
929
+ }
930
+ function validateInitialIntakeFeedbackListInput(value) {
931
+ const fields = initialListInputFields(value);
932
+ const workspace = initialInputIdentifier(fields.workspace);
933
+ const runId = fields.runId === undefined ? undefined : initialInputIdentifier(fields.runId);
934
+ const sessionId = fields.sessionId === undefined ? undefined : initialInputIdentifier(fields.sessionId);
935
+ const questionId = fields.questionId === undefined ? undefined : initialInputIdentifier(fields.questionId);
936
+ const profileField = fields.profileField === undefined ? undefined : initialInputIdentifier(fields.profileField);
937
+ if (questionId !== undefined && !INTAKE_PROFILE_FIELDS.has(questionId))
938
+ validation();
939
+ if (profileField !== undefined && !INTAKE_PROFILE_FIELDS.has(profileField))
940
+ validation();
941
+ if (questionId !== undefined && profileField !== undefined)
942
+ validation();
943
+ const limit = fields.limit === undefined ? 100 : fields.limit;
944
+ if (typeof limit !== 'number' || !Number.isInteger(limit) || limit < 1 || limit > 100)
945
+ validation();
946
+ return { workspace, ...(runId === undefined ? {} : { runId }), ...(sessionId === undefined ? {} : { sessionId }), ...(questionId === undefined ? {} : { questionId }), ...(profileField === undefined ? {} : { profileField }), limit };
947
+ }
948
+ function selectInitialIntakeRows(database, input) {
949
+ const conditions = ['(lr.workspace = ? OR lr.workspace IS NULL)'];
950
+ const parameters = [input.workspace];
951
+ if (input.runId !== undefined) {
952
+ conditions.push('ifb.run_id = ?');
953
+ parameters.push(input.runId);
954
+ }
955
+ if (input.sessionId !== undefined) {
956
+ conditions.push('ifb.session_id = ?');
957
+ parameters.push(input.sessionId);
958
+ }
959
+ if (input.questionId !== undefined) {
960
+ conditions.push('ifb.question_id = ?');
961
+ parameters.push(input.questionId);
962
+ }
963
+ if (input.profileField !== undefined) {
964
+ conditions.push('ifb.profile_field = ?');
965
+ parameters.push(input.profileField);
966
+ }
967
+ return database.prepare(`
968
+ SELECT
969
+ ifb.feedback_id, ifb.run_id, ifb.session_id, ifb.question_id, ifb.profile_field,
970
+ ifb.verdict, ifb.comment, ifb.actor, ifb.idempotency_key, ifb.created_at,
971
+ lr.workspace AS run_workspace,
972
+ s.workspace AS session_workspace,
973
+ ri.run_id AS linked_run_id,
974
+ ri.session_id AS linked_session_id,
975
+ aa.session_id AS question_answer_session_id
976
+ FROM intake_feedback AS ifb
977
+ LEFT JOIN ledger_runs AS lr ON lr.run_id = ifb.run_id
978
+ LEFT JOIN akinator_sessions AS s ON s.id = ifb.session_id
979
+ LEFT JOIN run_intakes AS ri ON ri.run_id = ifb.run_id AND ri.session_id = ifb.session_id
980
+ LEFT JOIN akinator_answers AS aa ON aa.session_id = ifb.session_id AND aa.question_id = ifb.question_id
981
+ WHERE ${conditions.join(' AND ')}
982
+ ORDER BY ifb.created_at ASC, ifb.feedback_id ASC
983
+ LIMIT ?
984
+ `).all(...parameters, input.limit + 1);
985
+ }
986
+ export function listIntakeFeedback(database, input) {
987
+ const validated = validateInitialIntakeFeedbackListInput(input);
988
+ try {
989
+ const rows = selectInitialIntakeRows(database, validated);
990
+ const records = rows.map((row) => rowToInitialIntakeFeedback(row, validated.workspace));
991
+ return { records: records.slice(0, validated.limit), truncated: records.length > validated.limit };
992
+ }
993
+ catch (error) {
994
+ normalizeDatabaseError(error);
995
+ }
996
+ }
997
+ //# sourceMappingURL=feedback.js.map