@retinue/agentkit 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 (405) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +310 -0
  3. package/dist/adapters/bullmq/consumer.d.ts +33 -0
  4. package/dist/adapters/bullmq/consumer.js +41 -0
  5. package/dist/adapters/bullmq/dispatcher.d.ts +74 -0
  6. package/dist/adapters/bullmq/dispatcher.js +160 -0
  7. package/dist/adapters/bullmq/export.d.ts +31 -0
  8. package/dist/adapters/bullmq/export.js +53 -0
  9. package/dist/adapters/bullmq/extraction.d.ts +42 -0
  10. package/dist/adapters/bullmq/extraction.js +63 -0
  11. package/dist/adapters/bullmq/index.d.ts +13 -0
  12. package/dist/adapters/bullmq/index.js +13 -0
  13. package/dist/adapters/bullmq/lock.d.ts +77 -0
  14. package/dist/adapters/bullmq/lock.js +126 -0
  15. package/dist/adapters/bullmq/queue.d.ts +50 -0
  16. package/dist/adapters/bullmq/queue.js +81 -0
  17. package/dist/adapters/memory/artifact-exports.d.ts +11 -0
  18. package/dist/adapters/memory/artifact-exports.js +102 -0
  19. package/dist/adapters/memory/artifacts.d.ts +15 -0
  20. package/dist/adapters/memory/artifacts.js +134 -0
  21. package/dist/adapters/memory/blobs.d.ts +7 -0
  22. package/dist/adapters/memory/blobs.js +27 -0
  23. package/dist/adapters/memory/evaluation.d.ts +18 -0
  24. package/dist/adapters/memory/evaluation.js +148 -0
  25. package/dist/adapters/memory/files.d.ts +27 -0
  26. package/dist/adapters/memory/files.js +0 -0
  27. package/dist/adapters/memory/flows.d.ts +16 -0
  28. package/dist/adapters/memory/flows.js +117 -0
  29. package/dist/adapters/memory/hitl.d.ts +9 -0
  30. package/dist/adapters/memory/hitl.js +130 -0
  31. package/dist/adapters/memory/idempotency.d.ts +13 -0
  32. package/dist/adapters/memory/idempotency.js +32 -0
  33. package/dist/adapters/memory/index.d.ts +39 -0
  34. package/dist/adapters/memory/index.js +107 -0
  35. package/dist/adapters/memory/knowledge.d.ts +43 -0
  36. package/dist/adapters/memory/knowledge.js +248 -0
  37. package/dist/adapters/memory/mcp.d.ts +9 -0
  38. package/dist/adapters/memory/mcp.js +37 -0
  39. package/dist/adapters/memory/message-store.d.ts +17 -0
  40. package/dist/adapters/memory/message-store.js +70 -0
  41. package/dist/adapters/memory/principal-memory.d.ts +7 -0
  42. package/dist/adapters/memory/principal-memory.js +83 -0
  43. package/dist/adapters/memory/runtime.d.ts +29 -0
  44. package/dist/adapters/memory/runtime.js +0 -0
  45. package/dist/adapters/memory/sessions.d.ts +29 -0
  46. package/dist/adapters/memory/sessions.js +0 -0
  47. package/dist/adapters/memory/skills.d.ts +10 -0
  48. package/dist/adapters/memory/skills.js +41 -0
  49. package/dist/adapters/memory/thread-summary.d.ts +7 -0
  50. package/dist/adapters/memory/thread-summary.js +29 -0
  51. package/dist/adapters/memory/usage-limits.d.ts +13 -0
  52. package/dist/adapters/memory/usage-limits.js +72 -0
  53. package/dist/adapters/memory/usage.d.ts +16 -0
  54. package/dist/adapters/memory/usage.js +279 -0
  55. package/dist/adapters/otel/index.d.ts +111 -0
  56. package/dist/adapters/otel/index.js +133 -0
  57. package/dist/adapters/postgres/artifact-exports.d.ts +12 -0
  58. package/dist/adapters/postgres/artifact-exports.js +117 -0
  59. package/dist/adapters/postgres/artifacts.d.ts +16 -0
  60. package/dist/adapters/postgres/artifacts.js +172 -0
  61. package/dist/adapters/postgres/checkpoint-store.d.ts +16 -0
  62. package/dist/adapters/postgres/checkpoint-store.js +34 -0
  63. package/dist/adapters/postgres/config.d.ts +15 -0
  64. package/dist/adapters/postgres/config.js +187 -0
  65. package/dist/adapters/postgres/conversation-store.d.ts +4 -0
  66. package/dist/adapters/postgres/conversation-store.js +82 -0
  67. package/dist/adapters/postgres/evaluation.d.ts +17 -0
  68. package/dist/adapters/postgres/evaluation.js +193 -0
  69. package/dist/adapters/postgres/file-content.d.ts +30 -0
  70. package/dist/adapters/postgres/file-content.js +111 -0
  71. package/dist/adapters/postgres/files.d.ts +19 -0
  72. package/dist/adapters/postgres/files.js +209 -0
  73. package/dist/adapters/postgres/flows.d.ts +20 -0
  74. package/dist/adapters/postgres/flows.js +206 -0
  75. package/dist/adapters/postgres/hitl.d.ts +5 -0
  76. package/dist/adapters/postgres/hitl.js +247 -0
  77. package/dist/adapters/postgres/index.d.ts +35 -0
  78. package/dist/adapters/postgres/index.js +35 -0
  79. package/dist/adapters/postgres/knowledge.d.ts +48 -0
  80. package/dist/adapters/postgres/knowledge.js +255 -0
  81. package/dist/adapters/postgres/memory.d.ts +14 -0
  82. package/dist/adapters/postgres/memory.js +194 -0
  83. package/dist/adapters/postgres/message-store.d.ts +11 -0
  84. package/dist/adapters/postgres/message-store.js +145 -0
  85. package/dist/adapters/postgres/migrations.d.ts +69 -0
  86. package/dist/adapters/postgres/migrations.js +1594 -0
  87. package/dist/adapters/postgres/pg-executor.d.ts +19 -0
  88. package/dist/adapters/postgres/pg-executor.js +32 -0
  89. package/dist/adapters/postgres/retention.d.ts +26 -0
  90. package/dist/adapters/postgres/retention.js +59 -0
  91. package/dist/adapters/postgres/rollups.d.ts +17 -0
  92. package/dist/adapters/postgres/rollups.js +267 -0
  93. package/dist/adapters/postgres/run-coordinator.d.ts +5 -0
  94. package/dist/adapters/postgres/run-coordinator.js +98 -0
  95. package/dist/adapters/postgres/run-event-log.d.ts +26 -0
  96. package/dist/adapters/postgres/run-event-log.js +30 -0
  97. package/dist/adapters/postgres/run-store.d.ts +4 -0
  98. package/dist/adapters/postgres/run-store.js +199 -0
  99. package/dist/adapters/postgres/schema.d.ts +39 -0
  100. package/dist/adapters/postgres/schema.js +70 -0
  101. package/dist/adapters/postgres/session-state.d.ts +7 -0
  102. package/dist/adapters/postgres/session-state.js +99 -0
  103. package/dist/adapters/postgres/sql.d.ts +8 -0
  104. package/dist/adapters/postgres/sql.js +2 -0
  105. package/dist/adapters/postgres/transaction.d.ts +37 -0
  106. package/dist/adapters/postgres/transaction.js +93 -0
  107. package/dist/adapters/postgres/unit-of-work.d.ts +18 -0
  108. package/dist/adapters/postgres/unit-of-work.js +8 -0
  109. package/dist/adapters/postgres/usage-limits.d.ts +15 -0
  110. package/dist/adapters/postgres/usage-limits.js +136 -0
  111. package/dist/adapters/postgres/usage.d.ts +15 -0
  112. package/dist/adapters/postgres/usage.js +226 -0
  113. package/dist/adapters/redis/index.d.ts +9 -0
  114. package/dist/adapters/redis/index.js +9 -0
  115. package/dist/adapters/redis/realtime.d.ts +74 -0
  116. package/dist/adapters/redis/realtime.js +112 -0
  117. package/dist/adapters/supabase/index.d.ts +88 -0
  118. package/dist/adapters/supabase/index.js +84 -0
  119. package/dist/adapters/supabase/realtime.d.ts +30 -0
  120. package/dist/adapters/supabase/realtime.js +53 -0
  121. package/dist/adapters/supabase/rls.d.ts +99 -0
  122. package/dist/adapters/supabase/rls.js +216 -0
  123. package/dist/adapters/supabase/storage.d.ts +50 -0
  124. package/dist/adapters/supabase/storage.js +207 -0
  125. package/dist/agents/agent.d.ts +66 -0
  126. package/dist/agents/agent.js +209 -0
  127. package/dist/agents/define.d.ts +21 -0
  128. package/dist/agents/define.js +63 -0
  129. package/dist/agents/engine.d.ts +98 -0
  130. package/dist/agents/engine.js +462 -0
  131. package/dist/agents/index.d.ts +50 -0
  132. package/dist/agents/index.js +17 -0
  133. package/dist/artifacts/index.d.ts +114 -0
  134. package/dist/artifacts/index.js +219 -0
  135. package/dist/authorization/index.d.ts +76 -0
  136. package/dist/authorization/index.js +76 -0
  137. package/dist/capabilities/index.d.ts +120 -0
  138. package/dist/capabilities/index.js +167 -0
  139. package/dist/capabilities/runtime.d.ts +89 -0
  140. package/dist/capabilities/runtime.js +84 -0
  141. package/dist/citations/index.d.ts +161 -0
  142. package/dist/citations/index.js +182 -0
  143. package/dist/context/assembler.d.ts +82 -0
  144. package/dist/context/assembler.js +129 -0
  145. package/dist/context/compaction.d.ts +45 -0
  146. package/dist/context/compaction.js +55 -0
  147. package/dist/context/index.d.ts +75 -0
  148. package/dist/context/index.js +17 -0
  149. package/dist/core/content-parts.d.ts +194 -0
  150. package/dist/core/content-parts.js +23 -0
  151. package/dist/core/context.d.ts +51 -0
  152. package/dist/core/context.js +9 -0
  153. package/dist/core/env.d.ts +25 -0
  154. package/dist/core/env.js +41 -0
  155. package/dist/core/errors.d.ts +30 -0
  156. package/dist/core/errors.js +65 -0
  157. package/dist/core/events.d.ts +139 -0
  158. package/dist/core/events.js +99 -0
  159. package/dist/core/ids.d.ts +52 -0
  160. package/dist/core/ids.js +13 -0
  161. package/dist/core/index.d.ts +9 -0
  162. package/dist/core/index.js +9 -0
  163. package/dist/core/tokens.d.ts +22 -0
  164. package/dist/core/tokens.js +22 -0
  165. package/dist/core/validation.d.ts +34 -0
  166. package/dist/core/validation.js +176 -0
  167. package/dist/documents/extraction.d.ts +121 -0
  168. package/dist/documents/extraction.js +293 -0
  169. package/dist/documents/index.d.ts +199 -0
  170. package/dist/documents/index.js +65 -0
  171. package/dist/documents/parsers/pdf.d.ts +47 -0
  172. package/dist/documents/parsers/pdf.js +508 -0
  173. package/dist/documents/parsers/text.d.ts +59 -0
  174. package/dist/documents/parsers/text.js +325 -0
  175. package/dist/documents/read-tool.d.ts +52 -0
  176. package/dist/documents/read-tool.js +109 -0
  177. package/dist/documents/render.d.ts +29 -0
  178. package/dist/documents/render.js +59 -0
  179. package/dist/documents/vision.d.ts +159 -0
  180. package/dist/documents/vision.js +214 -0
  181. package/dist/entries/adapters-bullmq.d.ts +8 -0
  182. package/dist/entries/adapters-bullmq.js +8 -0
  183. package/dist/entries/adapters-otel.d.ts +13 -0
  184. package/dist/entries/adapters-otel.js +13 -0
  185. package/dist/entries/adapters-postgres.d.ts +10 -0
  186. package/dist/entries/adapters-postgres.js +10 -0
  187. package/dist/entries/adapters-redis.d.ts +3 -0
  188. package/dist/entries/adapters-redis.js +3 -0
  189. package/dist/entries/context.d.ts +20 -0
  190. package/dist/entries/context.js +20 -0
  191. package/dist/entries/flows.d.ts +15 -0
  192. package/dist/entries/flows.js +15 -0
  193. package/dist/entries/hitl.d.ts +10 -0
  194. package/dist/entries/hitl.js +10 -0
  195. package/dist/entries/knowledge.d.ts +18 -0
  196. package/dist/entries/knowledge.js +19 -0
  197. package/dist/entries/mcp.d.ts +10 -0
  198. package/dist/entries/mcp.js +10 -0
  199. package/dist/entries/observability.d.ts +14 -0
  200. package/dist/entries/observability.js +16 -0
  201. package/dist/entries/persistence.d.ts +11 -0
  202. package/dist/entries/persistence.js +11 -0
  203. package/dist/entries/providers.d.ts +14 -0
  204. package/dist/entries/providers.js +14 -0
  205. package/dist/entries/runtime.d.ts +13 -0
  206. package/dist/entries/runtime.js +13 -0
  207. package/dist/entries/server.d.ts +24 -0
  208. package/dist/entries/server.js +24 -0
  209. package/dist/entries/tools.d.ts +21 -0
  210. package/dist/entries/tools.js +21 -0
  211. package/dist/entries/usage.d.ts +10 -0
  212. package/dist/entries/usage.js +10 -0
  213. package/dist/evaluation/gate.d.ts +168 -0
  214. package/dist/evaluation/gate.js +180 -0
  215. package/dist/evaluation/graders.d.ts +125 -0
  216. package/dist/evaluation/graders.js +203 -0
  217. package/dist/evaluation/index.d.ts +120 -0
  218. package/dist/evaluation/index.js +183 -0
  219. package/dist/evaluation/judge.d.ts +75 -0
  220. package/dist/evaluation/judge.js +111 -0
  221. package/dist/export/index.d.ts +162 -0
  222. package/dist/export/index.js +363 -0
  223. package/dist/export/markdown.d.ts +19 -0
  224. package/dist/export/markdown.js +29 -0
  225. package/dist/export/pdf.d.ts +73 -0
  226. package/dist/export/pdf.js +407 -0
  227. package/dist/files/context.d.ts +97 -0
  228. package/dist/files/context.js +185 -0
  229. package/dist/files/index.d.ts +210 -0
  230. package/dist/files/index.js +338 -0
  231. package/dist/files/read-tool.d.ts +81 -0
  232. package/dist/files/read-tool.js +163 -0
  233. package/dist/files/turn-parts.d.ts +96 -0
  234. package/dist/files/turn-parts.js +171 -0
  235. package/dist/flows/index.d.ts +270 -0
  236. package/dist/flows/index.js +62 -0
  237. package/dist/flows/interpreter.d.ts +146 -0
  238. package/dist/flows/interpreter.js +426 -0
  239. package/dist/flows/runner.d.ts +145 -0
  240. package/dist/flows/runner.js +270 -0
  241. package/dist/graphql/index.d.ts +8 -0
  242. package/dist/graphql/index.js +8 -0
  243. package/dist/graphql/resolvers.d.ts +237 -0
  244. package/dist/graphql/resolvers.js +253 -0
  245. package/dist/graphql/schema.d.ts +11 -0
  246. package/dist/graphql/schema.js +258 -0
  247. package/dist/graphql/sse.d.ts +77 -0
  248. package/dist/graphql/sse.js +100 -0
  249. package/dist/hitl/approved-execution.d.ts +127 -0
  250. package/dist/hitl/approved-execution.js +177 -0
  251. package/dist/hitl/index.d.ts +79 -0
  252. package/dist/hitl/index.js +12 -0
  253. package/dist/hitl/service.d.ts +221 -0
  254. package/dist/hitl/service.js +268 -0
  255. package/dist/idempotency/index.d.ts +70 -0
  256. package/dist/idempotency/index.js +59 -0
  257. package/dist/index.d.ts +103 -0
  258. package/dist/index.js +60 -0
  259. package/dist/knowledge/chunking.d.ts +57 -0
  260. package/dist/knowledge/chunking.js +158 -0
  261. package/dist/knowledge/index.d.ts +119 -0
  262. package/dist/knowledge/index.js +166 -0
  263. package/dist/knowledge/retrieval.d.ts +146 -0
  264. package/dist/knowledge/retrieval.js +170 -0
  265. package/dist/loadtest/harness.d.ts +168 -0
  266. package/dist/loadtest/harness.js +507 -0
  267. package/dist/loadtest/index.d.ts +13 -0
  268. package/dist/loadtest/index.js +13 -0
  269. package/dist/loadtest/injection.d.ts +89 -0
  270. package/dist/loadtest/injection.js +147 -0
  271. package/dist/loadtest/metrics.d.ts +197 -0
  272. package/dist/loadtest/metrics.js +160 -0
  273. package/dist/loadtest/runbooks.d.ts +28 -0
  274. package/dist/loadtest/runbooks.js +159 -0
  275. package/dist/loadtest/scenario.d.ts +104 -0
  276. package/dist/loadtest/scenario.js +208 -0
  277. package/dist/mcp/egress.d.ts +53 -0
  278. package/dist/mcp/egress.js +115 -0
  279. package/dist/mcp/index.d.ts +93 -0
  280. package/dist/mcp/index.js +33 -0
  281. package/dist/mcp/provider.d.ts +62 -0
  282. package/dist/mcp/provider.js +0 -0
  283. package/dist/models/index.d.ts +98 -0
  284. package/dist/models/index.js +74 -0
  285. package/dist/models/pricing.d.ts +24 -0
  286. package/dist/models/pricing.js +37 -0
  287. package/dist/models/provider-factory.d.ts +31 -0
  288. package/dist/models/provider-factory.js +67 -0
  289. package/dist/models/streaming.d.ts +145 -0
  290. package/dist/models/streaming.js +272 -0
  291. package/dist/models/vision.d.ts +38 -0
  292. package/dist/models/vision.js +62 -0
  293. package/dist/persistence/index.d.ts +1654 -0
  294. package/dist/persistence/index.js +226 -0
  295. package/dist/principal-memory/index.d.ts +106 -0
  296. package/dist/principal-memory/index.js +89 -0
  297. package/dist/retention/index.d.ts +89 -0
  298. package/dist/retention/index.js +70 -0
  299. package/dist/runtime/checkpoint.d.ts +37 -0
  300. package/dist/runtime/checkpoint.js +22 -0
  301. package/dist/runtime/index.d.ts +118 -0
  302. package/dist/runtime/index.js +69 -0
  303. package/dist/runtime/retry.d.ts +95 -0
  304. package/dist/runtime/retry.js +126 -0
  305. package/dist/runtime/serialization.d.ts +85 -0
  306. package/dist/runtime/serialization.js +95 -0
  307. package/dist/runtime/streaming.d.ts +54 -0
  308. package/dist/runtime/streaming.js +115 -0
  309. package/dist/runtime/worker.d.ts +130 -0
  310. package/dist/runtime/worker.js +405 -0
  311. package/dist/security/checklist.d.ts +53 -0
  312. package/dist/security/checklist.js +204 -0
  313. package/dist/security/findings.d.ts +56 -0
  314. package/dist/security/findings.js +168 -0
  315. package/dist/security/index.d.ts +14 -0
  316. package/dist/security/index.js +14 -0
  317. package/dist/security/prompt-safety.d.ts +100 -0
  318. package/dist/security/prompt-safety.js +133 -0
  319. package/dist/server/boot.d.ts +32 -0
  320. package/dist/server/boot.js +36 -0
  321. package/dist/server/cli-worker.d.ts +37 -0
  322. package/dist/server/cli-worker.js +151 -0
  323. package/dist/server/cli.d.ts +27 -0
  324. package/dist/server/cli.js +74 -0
  325. package/dist/server/config.d.ts +42 -0
  326. package/dist/server/config.js +127 -0
  327. package/dist/server/health.d.ts +59 -0
  328. package/dist/server/health.js +90 -0
  329. package/dist/server/host.d.ts +39 -0
  330. package/dist/server/host.js +124 -0
  331. package/dist/server/index.d.ts +15 -0
  332. package/dist/server/index.js +15 -0
  333. package/dist/server/main.d.ts +16 -0
  334. package/dist/server/main.js +31 -0
  335. package/dist/server/sse-route.d.ts +21 -0
  336. package/dist/server/sse-route.js +282 -0
  337. package/dist/skills/index.d.ts +67 -0
  338. package/dist/skills/index.js +31 -0
  339. package/dist/skills/resolver.d.ts +54 -0
  340. package/dist/skills/resolver.js +121 -0
  341. package/dist/teams/index.d.ts +93 -0
  342. package/dist/teams/index.js +207 -0
  343. package/dist/telemetry/index.d.ts +157 -0
  344. package/dist/telemetry/index.js +71 -0
  345. package/dist/telemetry/instrument.d.ts +108 -0
  346. package/dist/telemetry/instrument.js +232 -0
  347. package/dist/telemetry/log-events.d.ts +17 -0
  348. package/dist/telemetry/log-events.js +58 -0
  349. package/dist/telemetry/metrics.d.ts +123 -0
  350. package/dist/telemetry/metrics.js +135 -0
  351. package/dist/telemetry/noop.d.ts +39 -0
  352. package/dist/telemetry/noop.js +143 -0
  353. package/dist/telemetry/redaction.d.ts +64 -0
  354. package/dist/telemetry/redaction.js +153 -0
  355. package/dist/telemetry/spans.d.ts +56 -0
  356. package/dist/telemetry/spans.js +78 -0
  357. package/dist/telemetry/trace-context.d.ts +55 -0
  358. package/dist/telemetry/trace-context.js +60 -0
  359. package/dist/toolkit/compute.d.ts +53 -0
  360. package/dist/toolkit/compute.js +152 -0
  361. package/dist/toolkit/data.d.ts +98 -0
  362. package/dist/toolkit/data.js +235 -0
  363. package/dist/toolkit/http.d.ts +113 -0
  364. package/dist/toolkit/http.js +205 -0
  365. package/dist/toolkit/index.d.ts +21 -0
  366. package/dist/toolkit/index.js +17 -0
  367. package/dist/toolkit/web.d.ts +107 -0
  368. package/dist/toolkit/web.js +147 -0
  369. package/dist/tools/define.d.ts +25 -0
  370. package/dist/tools/define.js +45 -0
  371. package/dist/tools/delegating.d.ts +132 -0
  372. package/dist/tools/delegating.js +211 -0
  373. package/dist/tools/index.d.ts +129 -0
  374. package/dist/tools/index.js +33 -0
  375. package/dist/tools/library/compute.d.ts +11 -0
  376. package/dist/tools/library/compute.js +46 -0
  377. package/dist/tools/library/data.d.ts +16 -0
  378. package/dist/tools/library/data.js +92 -0
  379. package/dist/tools/library/http.d.ts +28 -0
  380. package/dist/tools/library/http.js +71 -0
  381. package/dist/tools/library/index.d.ts +97 -0
  382. package/dist/tools/library/index.js +134 -0
  383. package/dist/tools/library/knowledge.d.ts +39 -0
  384. package/dist/tools/library/knowledge.js +58 -0
  385. package/dist/tools/library/web.d.ts +19 -0
  386. package/dist/tools/library/web.js +65 -0
  387. package/dist/tools/meta-tools.d.ts +19 -0
  388. package/dist/tools/meta-tools.js +36 -0
  389. package/dist/tools/registry.d.ts +146 -0
  390. package/dist/tools/registry.js +291 -0
  391. package/dist/usage/index.d.ts +105 -0
  392. package/dist/usage/index.js +20 -0
  393. package/dist/usage/quota.d.ts +258 -0
  394. package/dist/usage/quota.js +510 -0
  395. package/dist/usage/recorder.d.ts +29 -0
  396. package/dist/usage/recorder.js +96 -0
  397. package/dist/usage/rollups.d.ts +121 -0
  398. package/dist/usage/rollups.js +157 -0
  399. package/dist/worker/export.d.ts +57 -0
  400. package/dist/worker/export.js +81 -0
  401. package/dist/worker/extraction.d.ts +57 -0
  402. package/dist/worker/extraction.js +84 -0
  403. package/dist/worker/main.d.ts +103 -0
  404. package/dist/worker/main.js +159 -0
  405. package/package.json +187 -0
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Postgres `EvaluationStore` (#141).
3
+ *
4
+ * Two properties are in the SQL.
5
+ *
6
+ * **`recordCase` upserts on the primary key**, so a resumed run re-recording a case it already scored replaces
7
+ * rather than double-counts it in the aggregate. The key is the idempotency; there is no applied-case set to
8
+ * keep right.
9
+ *
10
+ * **`completeRun` computes the aggregate from the case rows in one statement.** Accumulating as cases arrive
11
+ * would let a run's totals disagree with its own evidence after a re-record — and the number that gates a
12
+ * release must be derivable from what it was derived from.
13
+ */
14
+ import { AgentPlatformError } from "../../core/errors.js";
15
+ const iso = (v) => (v instanceof Date ? v.toISOString() : v);
16
+ /** `jsonb` arrives parsed from node-postgres and as a string from some drivers. Both, or it works only in tests. */
17
+ const json = (value, fallback) => value === null || value === undefined ? fallback : typeof value === "string" ? JSON.parse(value) : value;
18
+ const toRun = (r) => ({
19
+ id: r.id,
20
+ release: r.release,
21
+ startedAt: iso(r.started_at),
22
+ ...(r.finished_at === null ? {} : { finishedAt: iso(r.finished_at) }),
23
+ total: Number(r.total),
24
+ passed: Number(r.passed),
25
+ meanScore: Number(r.mean_score),
26
+ byDimension: json(r.by_dimension, []),
27
+ costMinorUnits: Number(r.cost_minor_units),
28
+ graderVersions: json(r.grader_versions, {}),
29
+ });
30
+ const toCase = (r) => ({
31
+ caseId: r.case_id,
32
+ dimension: r.dimension,
33
+ expectKind: r.expect_kind,
34
+ verdict: { pass: r.passed, score: Number(r.score), reason: r.reason },
35
+ graderId: r.grader_id,
36
+ graderVersion: r.grader_version,
37
+ ...(r.model_id === null ? {} : { modelId: r.model_id }),
38
+ ...(r.prompt_version === null ? {} : { promptVersion: r.prompt_version }),
39
+ costMinorUnits: Number(r.cost_minor_units),
40
+ });
41
+ const RUN_COLUMNS = `id, release, started_at, finished_at, total, passed, mean_score, by_dimension,
42
+ cost_minor_units, grader_versions`;
43
+ const CASE_COLUMNS = `case_id, dimension, expect_kind, passed, score, reason, grader_id, grader_version,
44
+ model_id, prompt_version, cost_minor_units`;
45
+ export const createPostgresEvaluationStore = (sql) => ({
46
+ async startRun({ tenantId, id, release, startedAt }) {
47
+ const rows = await sql.query(`INSERT INTO evaluation_runs (tenant_id, id, release, started_at, total, passed, mean_score,
48
+ by_dimension, cost_minor_units, grader_versions)
49
+ VALUES ($1,$2,$3,$4,0,0,0,'[]'::jsonb,0,'{}'::jsonb)
50
+ -- Nothing updated: a duplicate run id is a caller bug, and silently reopening a completed run would
51
+ -- discard the numbers a release was gated on.
52
+ ON CONFLICT (tenant_id, id) DO NOTHING
53
+ RETURNING ${RUN_COLUMNS}`, [tenantId, id, release, startedAt]);
54
+ const created = rows[0];
55
+ if (created === undefined)
56
+ throw new AgentPlatformError({
57
+ code: "conflict",
58
+ message: `evaluation run ${id} already exists`,
59
+ retryable: false,
60
+ });
61
+ return toRun(created);
62
+ },
63
+ async recordCase({ tenantId, runId, result }) {
64
+ const rows = await sql.query(`INSERT INTO evaluation_case_results (tenant_id, run_id, case_id, dimension, expect_kind, passed, score,
65
+ reason, grader_id, grader_version, model_id, prompt_version,
66
+ cost_minor_units)
67
+ SELECT $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13
68
+ -- Only when the run exists. Without this the foreign key would raise on a deleted run, and a harness
69
+ -- losing that race would abandon a whole scoring pass over an ordinary outcome.
70
+ WHERE EXISTS (SELECT 1 FROM evaluation_runs WHERE tenant_id = $1 AND id = $2)
71
+ -- The key is the idempotency: a resumed run re-recording a case replaces rather than double-counts it.
72
+ ON CONFLICT (tenant_id, run_id, case_id) DO UPDATE SET
73
+ dimension = EXCLUDED.dimension,
74
+ expect_kind = EXCLUDED.expect_kind,
75
+ passed = EXCLUDED.passed,
76
+ score = EXCLUDED.score,
77
+ reason = EXCLUDED.reason,
78
+ grader_id = EXCLUDED.grader_id,
79
+ grader_version = EXCLUDED.grader_version,
80
+ model_id = EXCLUDED.model_id,
81
+ prompt_version = EXCLUDED.prompt_version,
82
+ cost_minor_units = EXCLUDED.cost_minor_units
83
+ RETURNING case_id`, [
84
+ tenantId,
85
+ runId,
86
+ result.caseId,
87
+ result.dimension,
88
+ result.expectKind,
89
+ result.verdict.pass,
90
+ result.verdict.score,
91
+ result.verdict.reason,
92
+ result.graderId,
93
+ result.graderVersion,
94
+ result.modelId ?? null,
95
+ result.promptVersion ?? null,
96
+ result.costMinorUnits,
97
+ ]);
98
+ return { recorded: rows.length > 0 };
99
+ },
100
+ async completeRun({ tenantId, runId, finishedAt, graderVersions }) {
101
+ const rows = await sql.query(`WITH agg AS (
102
+ SELECT COUNT(*) AS agg_total,
103
+ COUNT(*) FILTER (WHERE passed) AS agg_passed,
104
+ COALESCE(AVG(score), 0) AS agg_mean_score,
105
+ COALESCE(SUM(cost_minor_units), 0) AS agg_cost
106
+ FROM evaluation_case_results
107
+ WHERE tenant_id = $1 AND run_id = $2
108
+ ),
109
+ dims AS (
110
+ SELECT COALESCE(
111
+ jsonb_agg(d ORDER BY d->>'dimension'),
112
+ '[]'::jsonb
113
+ ) AS dim_json
114
+ FROM (
115
+ SELECT jsonb_build_object(
116
+ 'dimension', dimension,
117
+ 'total', COUNT(*),
118
+ 'passed', COUNT(*) FILTER (WHERE passed),
119
+ 'meanScore', COALESCE(AVG(score), 0)
120
+ ) AS d
121
+ FROM evaluation_case_results
122
+ WHERE tenant_id = $1 AND run_id = $2
123
+ GROUP BY dimension
124
+ ) grouped
125
+ )
126
+ UPDATE evaluation_runs SET
127
+ finished_at = $3::timestamptz,
128
+ -- Every CTE column aliased: total, passed and cost_minor_units all exist on the target table
129
+ -- too, and an unqualified reference is ambiguous rather than defaulting to either.
130
+ total = agg.agg_total,
131
+ passed = agg.agg_passed,
132
+ mean_score = agg.agg_mean_score,
133
+ by_dimension = dims.dim_json,
134
+ cost_minor_units = agg.agg_cost,
135
+ grader_versions = $4::jsonb
136
+ FROM agg, dims
137
+ WHERE evaluation_runs.tenant_id = $1 AND evaluation_runs.id = $2
138
+ RETURNING ${RUN_COLUMNS}`, [tenantId, runId, finishedAt, JSON.stringify(graderVersions)]);
139
+ const run = rows[0];
140
+ if (run === undefined)
141
+ throw new AgentPlatformError({
142
+ code: "not_found",
143
+ message: `no such evaluation run ${runId}`,
144
+ retryable: false,
145
+ });
146
+ return toRun(run);
147
+ },
148
+ async get({ tenantId, runId }) {
149
+ const rows = await sql.query(`SELECT ${RUN_COLUMNS} FROM evaluation_runs WHERE tenant_id = $1 AND id = $2`, [tenantId, runId]);
150
+ // A foreign tenant's run yields no row: one tenant's quality gate is not another's business.
151
+ return rows[0] === undefined ? null : toRun(rows[0]);
152
+ },
153
+ async latest({ tenantId, release }) {
154
+ const rows = await sql.query(`SELECT ${RUN_COLUMNS} FROM evaluation_runs
155
+ WHERE tenant_id = $1
156
+ -- Completed only: an in-flight run's totals are partial, and comparing against one reports every case
157
+ -- it has not reached yet as a regression.
158
+ AND finished_at IS NOT NULL
159
+ AND ($2::text IS NULL OR release = $2::text)
160
+ -- Tie-broken by start then id: two runs can finish in the same instant, and "the latest run" must not
161
+ -- depend on physical row order. An unstable answer is a gate comparing against an arbitrary one of two.
162
+ ORDER BY finished_at DESC, started_at DESC, id DESC
163
+ LIMIT 1`, [tenantId, release ?? null]);
164
+ return rows[0] === undefined ? null : toRun(rows[0]);
165
+ },
166
+ async list({ tenantId, limit, cursor }) {
167
+ const rows = await sql.query(`SELECT ${RUN_COLUMNS} FROM evaluation_runs
168
+ WHERE tenant_id = $1
169
+ AND ($2::text IS NULL OR id > $2::text)
170
+ -- Newest first, the order a release history is read in.
171
+ ORDER BY started_at DESC, id
172
+ LIMIT $3`, [tenantId, cursor ?? null, limit + 1]);
173
+ const items = rows.slice(0, limit).map(toRun);
174
+ const last = items[items.length - 1];
175
+ return rows.length > limit && last !== undefined
176
+ ? { items, nextCursor: last.id }
177
+ : { items };
178
+ },
179
+ async listCaseResults({ tenantId, runId, limit, cursor }) {
180
+ const rows = await sql.query(`SELECT ${CASE_COLUMNS} FROM evaluation_case_results
181
+ WHERE tenant_id = $1 AND run_id = $2
182
+ AND ($3::text IS NULL OR case_id > $3::text)
183
+ -- By case id, so two runs' results zip together for a comparison without either side sorting.
184
+ ORDER BY case_id
185
+ LIMIT $4`, [tenantId, runId, cursor ?? null, limit + 1]);
186
+ const items = rows.slice(0, limit).map(toCase);
187
+ const last = items[items.length - 1];
188
+ return rows.length > limit && last !== undefined
189
+ ? { items, nextCursor: last.caseId }
190
+ : { items };
191
+ },
192
+ });
193
+ //# sourceMappingURL=evaluation.js.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * File bytes in Postgres — REQ-036 (#185).
3
+ *
4
+ * The platform had two content stores: in-memory, which loses everything on restart and is not shared between
5
+ * the API and the worker, and Supabase Storage, which needs a Supabase project. So a deployment running plain
6
+ * Postgres and Redis — the arrangement the example and the Nest service both use — could not accept an
7
+ * attachment at all. That is what made the multimodal path unreachable in practice rather than in principle.
8
+ *
9
+ * ## `bytea`, and when that is the wrong answer
10
+ *
11
+ * Bytes in a row are not how anyone should store a hundred megabytes: TOAST compresses and out-of-lines them, so
12
+ * a large object costs a second read and sits in the same backup, the same replication stream and the same
13
+ * connection memory as the data you actually query. This adapter exists because "no object storage" is a real
14
+ * deployment shape and "no attachments" is a worse answer, not because a database is a good filesystem.
15
+ *
16
+ * `signedUrl` therefore returns `null` — deliberately, and it is the honest answer rather than a gap. There is no
17
+ * URL that reaches a row without going through this process, so reads are proxied through `readFile` and the
18
+ * caller streams them. An adapter that returned a URL it could not actually sign would be worse than one that
19
+ * says it cannot.
20
+ *
21
+ * ## What the port demands and this respects
22
+ *
23
+ * `maxBytes` is enforced **while reading**, and nothing is written when it is passed. The declared size is a
24
+ * claim; the cap is the defence. Buffering to the cap and then refusing keeps the failure bounded, and inserting
25
+ * a partial row would leave an orphan on every oversized upload — precisely what reconciliation then has to hunt.
26
+ */
27
+ import type { FileContentStore } from "../../persistence/index.js";
28
+ import type { SqlExecutor } from "./sql.js";
29
+ export declare const createPostgresFileContentStore: (sql: SqlExecutor) => FileContentStore;
30
+ //# sourceMappingURL=file-content.d.ts.map
@@ -0,0 +1,111 @@
1
+ /**
2
+ * File bytes in Postgres — REQ-036 (#185).
3
+ *
4
+ * The platform had two content stores: in-memory, which loses everything on restart and is not shared between
5
+ * the API and the worker, and Supabase Storage, which needs a Supabase project. So a deployment running plain
6
+ * Postgres and Redis — the arrangement the example and the Nest service both use — could not accept an
7
+ * attachment at all. That is what made the multimodal path unreachable in practice rather than in principle.
8
+ *
9
+ * ## `bytea`, and when that is the wrong answer
10
+ *
11
+ * Bytes in a row are not how anyone should store a hundred megabytes: TOAST compresses and out-of-lines them, so
12
+ * a large object costs a second read and sits in the same backup, the same replication stream and the same
13
+ * connection memory as the data you actually query. This adapter exists because "no object storage" is a real
14
+ * deployment shape and "no attachments" is a worse answer, not because a database is a good filesystem.
15
+ *
16
+ * `signedUrl` therefore returns `null` — deliberately, and it is the honest answer rather than a gap. There is no
17
+ * URL that reaches a row without going through this process, so reads are proxied through `readFile` and the
18
+ * caller streams them. An adapter that returned a URL it could not actually sign would be worse than one that
19
+ * says it cannot.
20
+ *
21
+ * ## What the port demands and this respects
22
+ *
23
+ * `maxBytes` is enforced **while reading**, and nothing is written when it is passed. The declared size is a
24
+ * claim; the cap is the defence. Buffering to the cap and then refusing keeps the failure bounded, and inserting
25
+ * a partial row would leave an orphan on every oversized upload — precisely what reconciliation then has to hunt.
26
+ */
27
+ import { createHash } from "node:crypto";
28
+ import { AgentPlatformError } from "../../core/errors.js";
29
+ const int = (value) => (typeof value === "number" ? value : Number.parseInt(value, 10));
30
+ export const createPostgresFileContentStore = (sql) => ({
31
+ async putFile({ tenantId, contentKey, mediaType, bytes, maxBytes }) {
32
+ const chunks = [];
33
+ let size = 0;
34
+ for await (const chunk of bytes) {
35
+ size += chunk.byteLength;
36
+ if (size > maxBytes) {
37
+ /**
38
+ * Nothing has been written yet, and that is the point of buffering rather than streaming into the row.
39
+ *
40
+ * A streaming insert would have to either write and then delete — leaving the oversized object in the
41
+ * WAL and briefly readable — or hold an open transaction for the duration of an upload. Buffering to the
42
+ * cap bounds the memory at exactly `maxBytes`, which is the number the caller already agreed to.
43
+ */
44
+ throw new AgentPlatformError({
45
+ code: "invalid_input",
46
+ message: `the file exceeds the ${maxBytes} byte limit`,
47
+ retryable: false,
48
+ });
49
+ }
50
+ chunks.push(chunk);
51
+ }
52
+ const joined = Buffer.concat(chunks.map((c) => Buffer.from(c)));
53
+ const checksum = createHash("sha256").update(joined).digest("hex");
54
+ /**
55
+ * `ON CONFLICT … DO UPDATE`, not `DO NOTHING`.
56
+ *
57
+ * A content key is minted per upload, so a conflict means a retry of the *same* upload — and the bytes may
58
+ * differ from the first attempt if that attempt was truncated. Keeping the first write would leave a partial
59
+ * object that every later read returns, so the last complete write wins.
60
+ */
61
+ await sql.query(`INSERT INTO file_objects (tenant_id, content_key, media_type, byte_size, checksum, bytes)
62
+ VALUES ($1, $2, $3, $4, $5, $6)
63
+ ON CONFLICT (tenant_id, content_key)
64
+ DO UPDATE SET media_type = EXCLUDED.media_type, byte_size = EXCLUDED.byte_size,
65
+ checksum = EXCLUDED.checksum, bytes = EXCLUDED.bytes`, [tenantId, contentKey, mediaType, joined.byteLength, checksum, joined]);
66
+ return { contentKey, byteSize: joined.byteLength, checksum };
67
+ },
68
+ async readFile({ tenantId, contentKey }) {
69
+ // Tenant in the predicate, always: a content key from one tenant must not resolve another's bytes, and the
70
+ // key is opaque precisely so that guessing one is useless.
71
+ const rows = await sql.query(`SELECT bytes FROM file_objects WHERE tenant_id = $1 AND content_key = $2`, [tenantId, contentKey]);
72
+ const row = rows[0];
73
+ if (row === undefined)
74
+ return null;
75
+ const bytes = Buffer.isBuffer(row.bytes) ? new Uint8Array(row.bytes) : row.bytes;
76
+ return (async function* () {
77
+ yield bytes;
78
+ })();
79
+ },
80
+ /**
81
+ * `null`, because there is no URL that reaches a database row.
82
+ *
83
+ * The port documents this as the answer for an adapter with no signing mechanism, and the caller streams
84
+ * through `readFile` instead. Returning a URL this process would have to serve itself would be a durable URL
85
+ * wearing a signed one's name — the thing #129's AC-6 made structurally impossible.
86
+ */
87
+ async signedUrl() {
88
+ return null;
89
+ },
90
+ async deleteFile({ tenantId, contentKey }) {
91
+ // Idempotent: deleting an absent row affects nothing, and a retried sweep depends on that.
92
+ await sql.query(`DELETE FROM file_objects WHERE tenant_id = $1 AND content_key = $2`, [tenantId, contentKey]);
93
+ },
94
+ async listObjects({ tenantId, prefix, limit, cursor }) {
95
+ const take = Math.max(1, Math.min(limit ?? 100, 1_000));
96
+ const rows = await sql.query(`SELECT content_key, byte_size FROM file_objects
97
+ WHERE tenant_id = $1
98
+ AND ($2::text IS NULL OR content_key LIKE $2 || '%')
99
+ AND ($3::text IS NULL OR content_key > $3)
100
+ ORDER BY content_key
101
+ LIMIT $4`, [tenantId, prefix ?? null, cursor ?? null, take + 1]);
102
+ // One more than asked for, so "there is another page" is a fact rather than an inference from a full page.
103
+ const page = rows.slice(0, take);
104
+ const items = page.map((r) => ({ contentKey: r.content_key, byteSize: int(r.byte_size) }));
105
+ return {
106
+ items,
107
+ ...(rows.length > take && page.length > 0 ? { nextCursor: page[page.length - 1].content_key } : {}),
108
+ };
109
+ },
110
+ });
111
+ //# sourceMappingURL=file-content.js.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Postgres `FileMetadataStore` (#129).
3
+ *
4
+ * Metadata only. `FileContentStore` has no Postgres implementation and is classified `notApplicable` in the
5
+ * conformance matrix, because a relational adapter holding file bytes means base64 in a column — the
6
+ * antipattern #102 rejected when it declined to make `blobs` a pointer table.
7
+ *
8
+ * Two properties are in the SQL rather than above it, for the reason every other adapter here puts them
9
+ * there: a check the caller performs is a check a second caller can skip.
10
+ *
11
+ * - **`transition` is a compare-and-set in the statement**, `WHERE state = $from`, so two workers racing a
12
+ * resumed upload and a conversation delete cannot both succeed. `rowCount` is the answer.
13
+ * - **Deletion is scheduled, not cascaded.** The foreign key is `ON DELETE RESTRICT`: a cascade would drop
14
+ * the metadata and leave the bytes, which is exactly the orphan this design exists to avoid.
15
+ */
16
+ import type { FileMetadataStore } from "../../persistence/index.js";
17
+ import type { SqlExecutor } from "./sql.js";
18
+ export declare const createPostgresFileMetadataStore: (sql: SqlExecutor) => FileMetadataStore;
19
+ //# sourceMappingURL=files.d.ts.map
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Postgres `FileMetadataStore` (#129).
3
+ *
4
+ * Metadata only. `FileContentStore` has no Postgres implementation and is classified `notApplicable` in the
5
+ * conformance matrix, because a relational adapter holding file bytes means base64 in a column — the
6
+ * antipattern #102 rejected when it declined to make `blobs` a pointer table.
7
+ *
8
+ * Two properties are in the SQL rather than above it, for the reason every other adapter here puts them
9
+ * there: a check the caller performs is a check a second caller can skip.
10
+ *
11
+ * - **`transition` is a compare-and-set in the statement**, `WHERE state = $from`, so two workers racing a
12
+ * resumed upload and a conversation delete cannot both succeed. `rowCount` is the answer.
13
+ * - **Deletion is scheduled, not cascaded.** The foreign key is `ON DELETE RESTRICT`: a cascade would drop
14
+ * the metadata and leave the bytes, which is exactly the orphan this design exists to avoid.
15
+ */
16
+ import { AgentPlatformError } from "../../core/errors.js";
17
+ import { asId } from "../../core/ids.js";
18
+ const iso = (v) => (v instanceof Date ? v.toISOString() : v);
19
+ const toFile = (r) => ({
20
+ id: asId(r.id),
21
+ conversationId: asId(r.conversation_id),
22
+ filename: r.filename,
23
+ mediaType: r.media_type,
24
+ // `bigint` comes back as a string from node-postgres, which is correct for values past 2^53 and wrong
25
+ // for arithmetic. A file size is well inside the safe range, so it is narrowed here rather than left as
26
+ // a string a caller would have to remember to convert.
27
+ byteSize: Number(r.byte_size),
28
+ contentKey: r.content_key,
29
+ ...(r.checksum === null ? {} : { checksum: r.checksum }),
30
+ state: r.state,
31
+ uploadedBy: asId(r.uploaded_by),
32
+ createdAt: iso(r.created_at),
33
+ ...(r.deleted_at === null ? {} : { deletedAt: iso(r.deleted_at) }),
34
+ // Absent rather than a record with a null state: "nothing has tried to extract this" and "extraction is
35
+ // pending" are different facts, and the column being null is how the first one is spelled.
36
+ ...(r.extraction_state === null
37
+ ? {}
38
+ : {
39
+ extraction: {
40
+ state: r.extraction_state,
41
+ ...(r.extraction_ref === null ? {} : { ref: asId(r.extraction_ref) }),
42
+ ...(r.extraction_failure_reason === null ? {} : { failureReason: r.extraction_failure_reason }),
43
+ ...(r.extraction_failure_message === null ? {} : { failureMessage: r.extraction_failure_message }),
44
+ ...(r.extraction_pages === null ? {} : { pageCount: Number(r.extraction_pages) }),
45
+ ...(r.extraction_blocks === null ? {} : { blockCount: Number(r.extraction_blocks) }),
46
+ ...(r.extraction_truncated === null ? {} : { truncated: r.extraction_truncated }),
47
+ ...(r.extraction_confidence === null ? {} : { confidence: Number(r.extraction_confidence) }),
48
+ ...(r.extracted_at === null ? {} : { at: iso(r.extracted_at) }),
49
+ },
50
+ }),
51
+ });
52
+ const COLUMNS = `id, conversation_id, filename, media_type, byte_size, content_key, checksum, state,
53
+ uploaded_by, created_at, deleted_at, extraction_state, extraction_ref,
54
+ extraction_failure_reason, extraction_failure_message, extraction_pages,
55
+ extraction_blocks, extraction_truncated, extraction_confidence, extracted_at`;
56
+ /**
57
+ * Keyset cursor on `(created_at, id)`.
58
+ *
59
+ * The same encoding the other Postgres stores use. An offset cursor shifts when a row is inserted, so a
60
+ * caller paging a conversation's files while one uploads either sees a file twice or misses one.
61
+ */
62
+ const encodeCursor = (f) => Buffer.from(`${f.createdAt} ${f.id}`, "utf8").toString("base64url");
63
+ const decodeCursor = (cursor) => {
64
+ const [createdAt, id] = Buffer.from(cursor, "base64url").toString("utf8").split(" ");
65
+ return createdAt === undefined || id === undefined ? null : { createdAt, id };
66
+ };
67
+ const page = (rows, limit) => {
68
+ // One row over the limit is fetched, so "is there more" is answered by its presence rather than by a
69
+ // second COUNT that could disagree with the page it describes.
70
+ const items = rows.slice(0, limit).map(toFile);
71
+ const last = items[items.length - 1];
72
+ return rows.length > limit && last !== undefined
73
+ ? { items, nextCursor: encodeCursor(last) }
74
+ : { items };
75
+ };
76
+ export const createPostgresFileMetadataStore = (sql) => ({
77
+ async create({ tenantId, file }) {
78
+ const rows = await sql.query(`INSERT INTO files (tenant_id, id, conversation_id, filename, media_type, byte_size,
79
+ content_key, checksum, state, uploaded_by, created_at, deleted_at)
80
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)
81
+ -- Nothing is updated on conflict: a duplicate id is a caller bug, and quietly overwriting the
82
+ -- existing row would repoint a file at different bytes.
83
+ ON CONFLICT (tenant_id, id) DO NOTHING
84
+ RETURNING id`, [
85
+ tenantId,
86
+ file.id,
87
+ file.conversationId,
88
+ file.filename,
89
+ file.mediaType,
90
+ file.byteSize,
91
+ file.contentKey,
92
+ file.checksum ?? null,
93
+ file.state,
94
+ file.uploadedBy,
95
+ file.createdAt,
96
+ file.deletedAt ?? null,
97
+ ]);
98
+ if (rows.length === 0)
99
+ throw new AgentPlatformError({
100
+ code: "conflict",
101
+ message: `file ${file.id} already exists`,
102
+ retryable: false,
103
+ });
104
+ },
105
+ async get({ tenantId, id }) {
106
+ const rows = await sql.query(`SELECT ${COLUMNS} FROM files WHERE tenant_id = $1 AND id = $2`, [tenantId, id]);
107
+ // A foreign id yields no row, so it is null without a comparison anyone could get wrong.
108
+ return rows[0] === undefined ? null : toFile(rows[0]);
109
+ },
110
+ async listByConversation({ tenantId, conversationId, limit, cursor }) {
111
+ const after = cursor === undefined ? null : decodeCursor(cursor);
112
+ const rows = await sql.query(`SELECT ${COLUMNS} FROM files
113
+ WHERE tenant_id = $1 AND conversation_id = $2
114
+ -- Live rows only: this is what a user sees, and a soft-deleted row here would be a deleted
115
+ -- attachment coming back. 'deleting' is excluded as well as 'deleted', because deletion is
116
+ -- scheduled before the bytes go and a file listed in between is one a read would fail on.
117
+ AND deleted_at IS NULL AND state NOT IN ('deleting','deleted')
118
+ AND ($3::text IS NULL OR (created_at, id) > ($3::timestamptz, $4::text))
119
+ ORDER BY created_at, id
120
+ LIMIT $5`, [tenantId, conversationId, after?.createdAt ?? null, after?.id ?? null, limit + 1]);
121
+ return page(rows, limit);
122
+ },
123
+ async transition({ tenantId, id, from, to, at, checksum }) {
124
+ const rows = await sql.query(`UPDATE files
125
+ SET state = $4,
126
+ checksum = COALESCE($5, checksum),
127
+ -- Soft delete is a property of reaching 'deleted', so a row can never be deleted without a
128
+ -- time. The schema's CHECK says the same thing, twice on purpose.
129
+ deleted_at = CASE WHEN $4 = 'deleted' THEN $6::timestamptz ELSE deleted_at END
130
+ WHERE tenant_id = $1 AND id = $2
131
+ -- The compare, in the statement. A read-then-write would let a conversation delete land between
132
+ -- the two and leave a 'stored' file whose bytes are scheduled for removal -- and nothing sweeps
133
+ -- a stored file's object.
134
+ AND state = $3
135
+ RETURNING id`, [tenantId, id, from, to, checksum ?? null, at]);
136
+ return { moved: rows.length > 0 };
137
+ },
138
+ async scheduleConversationDeletion({ tenantId, conversationId, at }) {
139
+ const rows = await sql.query(`UPDATE files
140
+ SET state = 'deleting', deleted_at = $3::timestamptz
141
+ WHERE tenant_id = $1 AND conversation_id = $2
142
+ -- 'pending' as well as 'stored': an upload in flight when the conversation is deleted must not
143
+ -- complete into a conversation that no longer exists, and moving it here is what makes its own
144
+ -- transition to 'stored' fail.
145
+ AND state IN ('stored','pending')
146
+ RETURNING id`, [tenantId, conversationId, at]);
147
+ // One statement rather than a list and a loop: a file uploaded between the two would be missed, and
148
+ // missed silently.
149
+ return { scheduled: rows.length };
150
+ },
151
+ async recordExtraction({ tenantId, id, extraction }) {
152
+ const rows = await sql.query(`UPDATE files
153
+ SET extraction_state = $3,
154
+ extraction_ref = $4,
155
+ extraction_failure_reason = $5,
156
+ extraction_failure_message = $6,
157
+ extraction_pages = $7,
158
+ extraction_blocks = $8,
159
+ extraction_truncated = $9,
160
+ extraction_confidence = $10,
161
+ extracted_at = $11::timestamptz
162
+ -- No compare on the previous extraction state, unlike transition(). A worker retrying after a crash
163
+ -- does not know what it wrote before the crash, and requiring it to would make recovery impossible.
164
+ WHERE tenant_id = $1 AND id = $2
165
+ RETURNING id`, [
166
+ tenantId,
167
+ id,
168
+ extraction.state,
169
+ extraction.ref ?? null,
170
+ extraction.failureReason ?? null,
171
+ extraction.failureMessage ?? null,
172
+ extraction.pageCount ?? null,
173
+ extraction.blockCount ?? null,
174
+ extraction.truncated ?? null,
175
+ extraction.confidence ?? null,
176
+ extraction.at ?? null,
177
+ ]);
178
+ // Zero rows means the file is gone -- a conversation deleted while extraction ran. An ordinary race, so
179
+ // it is reported rather than thrown; a worker that threw would retry it forever.
180
+ return { recorded: rows.length > 0 };
181
+ },
182
+ async listByExtractionState({ tenantId, state, olderThan, limit, cursor }) {
183
+ const after = cursor === undefined ? null : decodeCursor(cursor);
184
+ const rows = await sql.query(`SELECT ${COLUMNS} FROM files
185
+ WHERE tenant_id = $1
186
+ -- A file nothing has touched has a NULL state, and that is the same fact as 'pending': the row is
187
+ -- waiting. Coalescing here is what lets this query find the files a lost enqueue dropped, which is
188
+ -- the whole reason the method exists.
189
+ AND COALESCE(extraction_state, 'pending') = $2
190
+ AND created_at < $3::timestamptz
191
+ AND ($4::text IS NULL OR (created_at, id) > ($4::timestamptz, $5::text))
192
+ ORDER BY created_at, id
193
+ LIMIT $6`, [tenantId, state, olderThan, after?.createdAt ?? null, after?.id ?? null, limit + 1]);
194
+ return page(rows, limit);
195
+ },
196
+ async listByState({ tenantId, state, olderThan, limit, cursor }) {
197
+ const after = cursor === undefined ? null : decodeCursor(cursor);
198
+ const rows = await sql.query(`SELECT ${COLUMNS} FROM files
199
+ WHERE tenant_id = $1 AND state = $2
200
+ -- Strictly older, so a file that entered the state a second ago is an upload in progress rather
201
+ -- than an orphan. Without it the job reports every upload happening while it runs.
202
+ AND created_at < $3::timestamptz
203
+ AND ($4::text IS NULL OR (created_at, id) > ($4::timestamptz, $5::text))
204
+ ORDER BY created_at, id
205
+ LIMIT $6`, [tenantId, state, olderThan, after?.createdAt ?? null, after?.id ?? null, limit + 1]);
206
+ return page(rows, limit);
207
+ },
208
+ });
209
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Flow definitions and executions in Postgres — #187, #186.
3
+ *
4
+ * Two things are enforced by the schema rather than by this file, because a rule in code is a rule one code path
5
+ * can miss:
6
+ *
7
+ * - **A version cannot be overwritten.** `(tenant_id, flow_id, version)` is the primary key and the insert has no
8
+ * `ON CONFLICT`, so a second write of the same version is a constraint violation. An execution pins a version
9
+ * and reads it for its whole life; a definition that could change under it would change an automation's shape
10
+ * halfway through.
11
+ * - **A save cannot move an execution backwards.** The update carries `WHERE steps <= $n`, so a stale document
12
+ * from a slower worker updates nothing. A flow that goes backwards re-performs external writes, which is the
13
+ * failure the whole module exists to prevent — and doing it in the `WHERE` means two concurrent saves are
14
+ * ordered by the database rather than by whichever read happened first.
15
+ */
16
+ import type { FlowDefinitionStore, FlowExecutionStore } from "../../persistence/index.js";
17
+ import type { SqlExecutor } from "./sql.js";
18
+ export declare const createPostgresFlowDefinitionStore: (sql: SqlExecutor) => FlowDefinitionStore;
19
+ export declare const createPostgresFlowExecutionStore: (sql: SqlExecutor) => FlowExecutionStore;
20
+ //# sourceMappingURL=flows.d.ts.map