@segmentstream/ontology 0.0.0-stage → 0.92.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 (483) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +2 -0
  3. package/README.md +1655 -2
  4. package/dist/action.d.ts +193 -0
  5. package/dist/action.js +41 -0
  6. package/dist/agent-chat.d.ts +44 -0
  7. package/dist/agent-chat.js +91 -0
  8. package/dist/agent-task-contract.d.ts +64 -0
  9. package/dist/agent-task-contract.js +15 -0
  10. package/dist/agent-task.d.ts +39 -0
  11. package/dist/agent-task.js +112 -0
  12. package/dist/analyze.d.ts +215 -0
  13. package/dist/analyze.js +2438 -0
  14. package/dist/app-feedback.d.ts +167 -0
  15. package/dist/app-feedback.js +176 -0
  16. package/dist/authoring-input.d.ts +3 -0
  17. package/dist/authoring-input.js +107 -0
  18. package/dist/authoring-names.d.ts +4 -0
  19. package/dist/authoring-names.js +13 -0
  20. package/dist/authoring-records.d.ts +35 -0
  21. package/dist/authoring-records.js +264 -0
  22. package/dist/authoring.d.ts +15 -0
  23. package/dist/authoring.js +331 -0
  24. package/dist/bin/check.d.ts +30 -0
  25. package/dist/bin/check.js +99 -0
  26. package/dist/bin/load.d.ts +48 -0
  27. package/dist/bin/load.js +139 -0
  28. package/dist/bin/main.d.ts +31 -0
  29. package/dist/bin/main.js +121 -0
  30. package/dist/bin/migrate.d.ts +57 -0
  31. package/dist/bin/migrate.js +170 -0
  32. package/dist/bin/output.d.ts +31 -0
  33. package/dist/bin/output.js +84 -0
  34. package/dist/bin.d.ts +2 -0
  35. package/dist/bin.js +31 -0
  36. package/dist/blob.d.ts +52 -0
  37. package/dist/blob.js +87 -0
  38. package/dist/channel-session.d.ts +16 -0
  39. package/dist/channel-session.js +16 -0
  40. package/dist/chat.d.ts +43 -0
  41. package/dist/chat.js +34 -0
  42. package/dist/confirmation.d.ts +46 -0
  43. package/dist/confirmation.js +41 -0
  44. package/dist/connection.d.ts +213 -0
  45. package/dist/connection.js +289 -0
  46. package/dist/deployment.d.ts +35 -0
  47. package/dist/deployment.js +76 -0
  48. package/dist/diagnostics.d.ts +27 -0
  49. package/dist/diagnostics.js +106 -0
  50. package/dist/emails.d.ts +70 -0
  51. package/dist/emails.js +221 -0
  52. package/dist/entity.d.ts +331 -0
  53. package/dist/entity.js +153 -0
  54. package/dist/errors.d.ts +154 -0
  55. package/dist/errors.js +109 -0
  56. package/dist/feedback-config.d.ts +24 -0
  57. package/dist/feedback-config.js +126 -0
  58. package/dist/files.d.ts +39 -0
  59. package/dist/files.js +4 -0
  60. package/dist/groups.d.ts +95 -0
  61. package/dist/groups.js +280 -0
  62. package/dist/index.d.ts +109 -0
  63. package/dist/index.js +70 -0
  64. package/dist/internal/diagnostics.js +594 -0
  65. package/dist/legacy.d.ts +160 -0
  66. package/dist/legacy.js +270 -0
  67. package/dist/manifest-compatibility.d.ts +8 -0
  68. package/dist/manifest-compatibility.js +87 -0
  69. package/dist/manifest.d.ts +3329 -0
  70. package/dist/manifest.js +757 -0
  71. package/dist/me.d.ts +46 -0
  72. package/dist/me.js +109 -0
  73. package/dist/metric-filters.d.ts +48 -0
  74. package/dist/metric-filters.js +252 -0
  75. package/dist/metric-routes.d.ts +132 -0
  76. package/dist/metric-routes.js +498 -0
  77. package/dist/metric.d.ts +172 -0
  78. package/dist/metric.js +132 -0
  79. package/dist/migrate/apply.d.ts +94 -0
  80. package/dist/migrate/apply.js +526 -0
  81. package/dist/migrate/errors.d.ts +46 -0
  82. package/dist/migrate/errors.js +38 -0
  83. package/dist/migrate/index.d.ts +10 -0
  84. package/dist/migrate/index.js +7 -0
  85. package/dist/migrate/model.d.ts +2311 -0
  86. package/dist/migrate/model.js +148 -0
  87. package/dist/migrate/plan.d.ts +51 -0
  88. package/dist/migrate/plan.js +1856 -0
  89. package/dist/migrate/preview.d.ts +13 -0
  90. package/dist/migrate/preview.js +65 -0
  91. package/dist/migrate/storage.d.ts +226 -0
  92. package/dist/migrate/storage.js +774 -0
  93. package/dist/model-metrics.d.ts +22 -0
  94. package/dist/model-metrics.js +545 -0
  95. package/dist/names.d.ts +49 -0
  96. package/dist/names.js +110 -0
  97. package/dist/navigation-artifact.d.ts +13 -0
  98. package/dist/navigation-artifact.js +11 -0
  99. package/dist/navigation-catalog.d.ts +55 -0
  100. package/dist/navigation-catalog.js +297 -0
  101. package/dist/nitro-navigation.d.ts +3 -0
  102. package/dist/nitro-navigation.js +64 -0
  103. package/dist/nitro-workflows.d.ts +108 -0
  104. package/dist/nitro-workflows.js +286 -0
  105. package/dist/nitro.d.ts +40 -0
  106. package/dist/nitro.js +50 -0
  107. package/dist/ontology.d.ts +41 -0
  108. package/dist/ontology.js +46 -0
  109. package/dist/permissions.d.ts +198 -0
  110. package/dist/permissions.js +272 -0
  111. package/dist/property.d.ts +188 -0
  112. package/dist/property.js +301 -0
  113. package/dist/query.d.ts +245 -0
  114. package/dist/query.js +615 -0
  115. package/dist/relationship.d.ts +99 -0
  116. package/dist/relationship.js +33 -0
  117. package/dist/render.d.ts +81 -0
  118. package/dist/render.js +512 -0
  119. package/dist/replication/baseline.d.ts +51 -0
  120. package/dist/replication/baseline.js +215 -0
  121. package/dist/replication/bigquery.d.ts +37 -0
  122. package/dist/replication/bigquery.js +211 -0
  123. package/dist/replication/coverage.d.ts +14 -0
  124. package/dist/replication/coverage.js +53 -0
  125. package/dist/replication/evolution.d.ts +25 -0
  126. package/dist/replication/evolution.js +184 -0
  127. package/dist/replication/identities.d.ts +6 -0
  128. package/dist/replication/identities.js +47 -0
  129. package/dist/replication/legacy-baseline.d.ts +27 -0
  130. package/dist/replication/legacy-baseline.js +109 -0
  131. package/dist/replication/legacy-store.d.ts +19 -0
  132. package/dist/replication/legacy-store.js +63 -0
  133. package/dist/replication/lineage.d.ts +36 -0
  134. package/dist/replication/lineage.js +72 -0
  135. package/dist/replication/migrate.d.ts +21 -0
  136. package/dist/replication/migrate.js +84 -0
  137. package/dist/replication/model.d.ts +52 -0
  138. package/dist/replication/model.js +99 -0
  139. package/dist/replication/scans.d.ts +34 -0
  140. package/dist/replication/scans.js +158 -0
  141. package/dist/replication/schema.d.ts +11 -0
  142. package/dist/replication/schema.js +226 -0
  143. package/dist/replication/shadow-schema.d.ts +4 -0
  144. package/dist/replication/shadow-schema.js +61 -0
  145. package/dist/replication/shadow.d.ts +127 -0
  146. package/dist/replication/shadow.js +614 -0
  147. package/dist/replication/source-replacement.d.ts +33 -0
  148. package/dist/replication/source-replacement.js +76 -0
  149. package/dist/replication/store.d.ts +36 -0
  150. package/dist/replication/store.js +382 -0
  151. package/dist/replication/transaction.d.ts +23 -0
  152. package/dist/replication/transaction.js +88 -0
  153. package/dist/role.d.ts +61 -0
  154. package/dist/role.js +76 -0
  155. package/dist/runtime/action-deadlocks.d.ts +21 -0
  156. package/dist/runtime/action-deadlocks.js +130 -0
  157. package/dist/runtime/action-transactions.d.ts +35 -0
  158. package/dist/runtime/action-transactions.js +54 -0
  159. package/dist/runtime/agent-counters.d.ts +26 -0
  160. package/dist/runtime/agent-counters.js +98 -0
  161. package/dist/runtime/agent-tasks.d.ts +63 -0
  162. package/dist/runtime/agent-tasks.js +120 -0
  163. package/dist/runtime/agents.d.ts +15 -0
  164. package/dist/runtime/agents.js +457 -0
  165. package/dist/runtime/batch-diagnostics.d.ts +22 -0
  166. package/dist/runtime/batch-diagnostics.js +89 -0
  167. package/dist/runtime/batch.d.ts +52 -0
  168. package/dist/runtime/batch.js +534 -0
  169. package/dist/runtime/channel-session-revocation.d.ts +3 -0
  170. package/dist/runtime/channel-session-revocation.js +5 -0
  171. package/dist/runtime/channel-sessions.d.ts +4 -0
  172. package/dist/runtime/channel-sessions.js +61 -0
  173. package/dist/runtime/confirmations.d.ts +29 -0
  174. package/dist/runtime/confirmations.js +57 -0
  175. package/dist/runtime/cursor.d.ts +23 -0
  176. package/dist/runtime/cursor.js +100 -0
  177. package/dist/runtime/deployment.d.ts +7 -0
  178. package/dist/runtime/deployment.js +4 -0
  179. package/dist/runtime/errors.d.ts +118 -0
  180. package/dist/runtime/errors.js +109 -0
  181. package/dist/runtime/federation-tables.d.ts +2 -0
  182. package/dist/runtime/federation-tables.js +20 -0
  183. package/dist/runtime/federation.d.ts +42 -0
  184. package/dist/runtime/federation.js +222 -0
  185. package/dist/runtime/files.d.ts +39 -0
  186. package/dist/runtime/files.js +44 -0
  187. package/dist/runtime/generated-actions.d.ts +37 -0
  188. package/dist/runtime/generated-actions.js +307 -0
  189. package/dist/runtime/history.d.ts +91 -0
  190. package/dist/runtime/history.js +77 -0
  191. package/dist/runtime/ids.d.ts +6 -0
  192. package/dist/runtime/ids.js +39 -0
  193. package/dist/runtime/member-tables.d.ts +8 -0
  194. package/dist/runtime/member-tables.js +33 -0
  195. package/dist/runtime/member-writes.d.ts +78 -0
  196. package/dist/runtime/member-writes.js +202 -0
  197. package/dist/runtime/metric-query.d.ts +19 -0
  198. package/dist/runtime/metric-query.js +411 -0
  199. package/dist/runtime/model.d.ts +67 -0
  200. package/dist/runtime/model.js +150 -0
  201. package/dist/runtime/new-user-hook.d.ts +6 -0
  202. package/dist/runtime/new-user-hook.js +37 -0
  203. package/dist/runtime/oauth.d.ts +9 -0
  204. package/dist/runtime/oauth.js +299 -0
  205. package/dist/runtime/pipeline.d.ts +155 -0
  206. package/dist/runtime/pipeline.js +597 -0
  207. package/dist/runtime/postgres-tls.d.ts +10 -0
  208. package/dist/runtime/postgres-tls.js +14 -0
  209. package/dist/runtime/principals.d.ts +302 -0
  210. package/dist/runtime/principals.js +1307 -0
  211. package/dist/runtime/reads.d.ts +55 -0
  212. package/dist/runtime/reads.js +313 -0
  213. package/dist/runtime/records.d.ts +42 -0
  214. package/dist/runtime/records.js +245 -0
  215. package/dist/runtime/resolve-agent-identity.d.ts +6 -0
  216. package/dist/runtime/resolve-agent-identity.js +181 -0
  217. package/dist/runtime/service-binding.d.ts +18 -0
  218. package/dist/runtime/service-binding.js +266 -0
  219. package/dist/runtime/sign-in-links.d.ts +68 -0
  220. package/dist/runtime/sign-in-links.js +147 -0
  221. package/dist/runtime/sql-runtime.d.ts +131 -0
  222. package/dist/runtime/sql-runtime.js +362 -0
  223. package/dist/runtime/statement.d.ts +18 -0
  224. package/dist/runtime/statement.js +31 -0
  225. package/dist/runtime/user-identities.d.ts +66 -0
  226. package/dist/runtime/user-identities.js +106 -0
  227. package/dist/runtime/user-identity-sql.d.ts +86 -0
  228. package/dist/runtime/user-identity-sql.js +297 -0
  229. package/dist/runtime/user-roles.d.ts +16 -0
  230. package/dist/runtime/user-roles.js +32 -0
  231. package/dist/runtime/validation.d.ts +16 -0
  232. package/dist/runtime/validation.js +26 -0
  233. package/dist/runtime/where.d.ts +57 -0
  234. package/dist/runtime/where.js +404 -0
  235. package/dist/runtime/workflow-admission.d.ts +49 -0
  236. package/dist/runtime/workflow-admission.js +92 -0
  237. package/dist/runtime/workflow-argument-codec.d.ts +10 -0
  238. package/dist/runtime/workflow-argument-codec.js +120 -0
  239. package/dist/runtime/workflow-arguments.d.ts +17 -0
  240. package/dist/runtime/workflow-arguments.js +99 -0
  241. package/dist/runtime/workflow-starts.d.ts +203 -0
  242. package/dist/runtime/workflow-starts.js +493 -0
  243. package/dist/runtime/workflow-users.d.ts +9 -0
  244. package/dist/runtime/workflow-users.js +17 -0
  245. package/dist/runtime/workload.d.ts +25 -0
  246. package/dist/runtime/workload.js +24 -0
  247. package/dist/runtime/writes.d.ts +108 -0
  248. package/dist/runtime/writes.js +491 -0
  249. package/dist/runtime-source.d.ts +22 -0
  250. package/dist/runtime-source.js +28 -0
  251. package/dist/runtime.d.ts +310 -0
  252. package/dist/runtime.js +31 -0
  253. package/dist/server.d.ts +23 -0
  254. package/dist/server.js +107 -0
  255. package/dist/service/agent/attachments.d.ts +33 -0
  256. package/dist/service/agent/attachments.js +228 -0
  257. package/dist/service/agent/authentication.d.ts +22 -0
  258. package/dist/service/agent/authentication.js +58 -0
  259. package/dist/service/agent/confirmations.d.ts +5 -0
  260. package/dist/service/agent/confirmations.js +73 -0
  261. package/dist/service/agent/conversation-routes.d.ts +14 -0
  262. package/dist/service/agent/conversation-routes.js +45 -0
  263. package/dist/service/agent/conversations.d.ts +40 -0
  264. package/dist/service/agent/conversations.js +69 -0
  265. package/dist/service/agent/files.d.ts +12 -0
  266. package/dist/service/agent/files.js +69 -0
  267. package/dist/service/agent/protocol.d.ts +90 -0
  268. package/dist/service/agent/protocol.js +114 -0
  269. package/dist/service/agent/server.d.ts +28 -0
  270. package/dist/service/agent/server.js +374 -0
  271. package/dist/service/agent/task-caller.d.ts +21 -0
  272. package/dist/service/agent/task-caller.js +35 -0
  273. package/dist/service/agent/task-start.d.ts +53 -0
  274. package/dist/service/agent/task-start.js +161 -0
  275. package/dist/service/agent/tasks.d.ts +40 -0
  276. package/dist/service/agent/tasks.js +155 -0
  277. package/dist/service/api.d.ts +50 -0
  278. package/dist/service/api.js +288 -0
  279. package/dist/service/authentication.d.ts +227 -0
  280. package/dist/service/authentication.js +767 -0
  281. package/dist/service/caller-context.d.ts +24 -0
  282. package/dist/service/caller-context.js +6 -0
  283. package/dist/service/catalog.d.ts +42 -0
  284. package/dist/service/catalog.js +66 -0
  285. package/dist/service/channel/provider.d.ts +10 -0
  286. package/dist/service/channel/provider.js +1 -0
  287. package/dist/service/channel/providers/telegram.d.ts +18 -0
  288. package/dist/service/channel/providers/telegram.js +108 -0
  289. package/dist/service/channel/sessions.d.ts +5 -0
  290. package/dist/service/channel/sessions.js +53 -0
  291. package/dist/service/channel/sign-in.d.ts +7 -0
  292. package/dist/service/channel/sign-in.js +78 -0
  293. package/dist/service/chat/archive.d.ts +64 -0
  294. package/dist/service/chat/archive.js +62 -0
  295. package/dist/service/chat/attribution.d.ts +26 -0
  296. package/dist/service/chat/attribution.js +28 -0
  297. package/dist/service/chat/delivery.d.ts +18 -0
  298. package/dist/service/chat/delivery.js +251 -0
  299. package/dist/service/chat/diagnostics.d.ts +2 -0
  300. package/dist/service/chat/diagnostics.js +35 -0
  301. package/dist/service/chat/envelope.d.ts +96 -0
  302. package/dist/service/chat/envelope.js +39 -0
  303. package/dist/service/chat/idempotency.d.ts +19 -0
  304. package/dist/service/chat/idempotency.js +37 -0
  305. package/dist/service/chat/providers/telegram.d.ts +36 -0
  306. package/dist/service/chat/providers/telegram.js +133 -0
  307. package/dist/service/chat/reads.d.ts +25 -0
  308. package/dist/service/chat/reads.js +75 -0
  309. package/dist/service/chat/resolve.d.ts +34 -0
  310. package/dist/service/chat/resolve.js +150 -0
  311. package/dist/service/chat/routes.d.ts +23 -0
  312. package/dist/service/chat/routes.js +75 -0
  313. package/dist/service/chat/schemas.d.ts +43 -0
  314. package/dist/service/chat/schemas.js +31 -0
  315. package/dist/service/config.d.ts +218 -0
  316. package/dist/service/config.js +265 -0
  317. package/dist/service/connection-stream.d.ts +10 -0
  318. package/dist/service/connection-stream.js +191 -0
  319. package/dist/service/connections.d.ts +60 -0
  320. package/dist/service/connections.js +271 -0
  321. package/dist/service/decisions.d.ts +35 -0
  322. package/dist/service/decisions.js +63 -0
  323. package/dist/service/descriptions.d.ts +8 -0
  324. package/dist/service/descriptions.js +20 -0
  325. package/dist/service/discovery-schemas.d.ts +92 -0
  326. package/dist/service/discovery-schemas.js +59 -0
  327. package/dist/service/discovery-search.d.ts +30 -0
  328. package/dist/service/discovery-search.js +95 -0
  329. package/dist/service/discovery.d.ts +37 -0
  330. package/dist/service/discovery.js +436 -0
  331. package/dist/service/docs.d.ts +9 -0
  332. package/dist/service/docs.js +197 -0
  333. package/dist/service/errors.d.ts +145 -0
  334. package/dist/service/errors.js +704 -0
  335. package/dist/service/federation-provider.d.ts +36 -0
  336. package/dist/service/federation-provider.js +110 -0
  337. package/dist/service/federation-server.d.ts +20 -0
  338. package/dist/service/federation-server.js +249 -0
  339. package/dist/service/federation-status.d.ts +40 -0
  340. package/dist/service/federation-status.js +151 -0
  341. package/dist/service/handlers.d.ts +18 -0
  342. package/dist/service/handlers.js +287 -0
  343. package/dist/service/mcp/confirmation.d.ts +68 -0
  344. package/dist/service/mcp/confirmation.js +178 -0
  345. package/dist/service/mcp/find-input.d.ts +12 -0
  346. package/dist/service/mcp/find-input.js +122 -0
  347. package/dist/service/mcp/input-schema.d.ts +6 -0
  348. package/dist/service/mcp/input-schema.js +126 -0
  349. package/dist/service/mcp/listing.d.ts +26 -0
  350. package/dist/service/mcp/listing.js +92 -0
  351. package/dist/service/mcp/metric-tools.d.ts +85 -0
  352. package/dist/service/mcp/metric-tools.js +258 -0
  353. package/dist/service/mcp/request-log.d.ts +5 -0
  354. package/dist/service/mcp/request-log.js +53 -0
  355. package/dist/service/mcp/runtime-policy.d.ts +27 -0
  356. package/dist/service/mcp/runtime-policy.js +62 -0
  357. package/dist/service/mcp/server.d.ts +52 -0
  358. package/dist/service/mcp/server.js +861 -0
  359. package/dist/service/mcp/tools.d.ts +99 -0
  360. package/dist/service/mcp/tools.js +938 -0
  361. package/dist/service/member-file-routes.d.ts +22 -0
  362. package/dist/service/member-file-routes.js +52 -0
  363. package/dist/service/member-files.d.ts +245 -0
  364. package/dist/service/member-files.js +258 -0
  365. package/dist/service/metric-query-platform.d.ts +77 -0
  366. package/dist/service/metric-query-platform.js +198 -0
  367. package/dist/service/metric-query-scopes.d.ts +43 -0
  368. package/dist/service/metric-query-scopes.js +147 -0
  369. package/dist/service/metric-query.d.ts +48 -0
  370. package/dist/service/metric-query.js +200 -0
  371. package/dist/service/name-suggestions.d.ts +3 -0
  372. package/dist/service/name-suggestions.js +38 -0
  373. package/dist/service/navigation-schemas.d.ts +80 -0
  374. package/dist/service/navigation-schemas.js +49 -0
  375. package/dist/service/navigation.d.ts +20 -0
  376. package/dist/service/navigation.js +165 -0
  377. package/dist/service/oauth/protocol.d.ts +70 -0
  378. package/dist/service/oauth/protocol.js +157 -0
  379. package/dist/service/oauth/refresh-replay.d.ts +2 -0
  380. package/dist/service/oauth/refresh-replay.js +20 -0
  381. package/dist/service/oauth/server.d.ts +28 -0
  382. package/dist/service/oauth/server.js +769 -0
  383. package/dist/service/operations.d.ts +170 -0
  384. package/dist/service/operations.js +392 -0
  385. package/dist/service/plan.d.ts +57 -0
  386. package/dist/service/plan.js +96 -0
  387. package/dist/service/rate-limit.d.ts +22 -0
  388. package/dist/service/rate-limit.js +52 -0
  389. package/dist/service/record-reads.d.ts +21 -0
  390. package/dist/service/record-reads.js +155 -0
  391. package/dist/service/replication.d.ts +5 -0
  392. package/dist/service/replication.js +39 -0
  393. package/dist/service/runtime-contract.d.ts +508 -0
  394. package/dist/service/runtime-contract.js +18 -0
  395. package/dist/service/runtime-identity.d.ts +71 -0
  396. package/dist/service/runtime-identity.js +122 -0
  397. package/dist/service/schemas.d.ts +961 -0
  398. package/dist/service/schemas.js +639 -0
  399. package/dist/service/service.d.ts +21 -0
  400. package/dist/service/service.js +526 -0
  401. package/dist/service/warehouse-platform.d.ts +17 -0
  402. package/dist/service/warehouse-platform.js +122 -0
  403. package/dist/service/warehouse-query.d.ts +24 -0
  404. package/dist/service/warehouse-query.js +280 -0
  405. package/dist/service/webhooks.d.ts +106 -0
  406. package/dist/service/webhooks.js +233 -0
  407. package/dist/service/workflow-admission-provider.d.ts +31 -0
  408. package/dist/service/workflow-admission-provider.js +205 -0
  409. package/dist/service/workflow-argument-serialization.d.ts +2 -0
  410. package/dist/service/workflow-argument-serialization.js +14 -0
  411. package/dist/service/workflow-callback.d.ts +22 -0
  412. package/dist/service/workflow-callback.js +75 -0
  413. package/dist/service/workflow-deployment.d.ts +8 -0
  414. package/dist/service/workflow-deployment.js +131 -0
  415. package/dist/service/workflow-diagnostics.d.ts +23 -0
  416. package/dist/service/workflow-diagnostics.js +263 -0
  417. package/dist/service/workflow-errors.d.ts +26 -0
  418. package/dist/service/workflow-errors.js +181 -0
  419. package/dist/service/workflow-logging.d.ts +2 -0
  420. package/dist/service/workflow-logging.js +50 -0
  421. package/dist/service/workflow-queue-fence.d.ts +6 -0
  422. package/dist/service/workflow-queue-fence.js +66 -0
  423. package/dist/service/workflows.d.ts +200 -0
  424. package/dist/service/workflows.js +1549 -0
  425. package/dist/sign-in.d.ts +58 -0
  426. package/dist/sign-in.js +57 -0
  427. package/dist/source.d.ts +30 -0
  428. package/dist/source.js +107 -0
  429. package/dist/start-recovery-contract.d.ts +49 -0
  430. package/dist/start-recovery-contract.js +26 -0
  431. package/dist/start-recovery.d.ts +27 -0
  432. package/dist/start-recovery.js +81 -0
  433. package/dist/storage/api.d.ts +4 -0
  434. package/dist/storage/api.js +40 -0
  435. package/dist/storage/attachments.d.ts +146 -0
  436. package/dist/storage/attachments.js +733 -0
  437. package/dist/storage/authorization.d.ts +38 -0
  438. package/dist/storage/authorization.js +83 -0
  439. package/dist/storage/file-access.d.ts +17 -0
  440. package/dist/storage/file-access.js +8 -0
  441. package/dist/storage/limits.d.ts +12 -0
  442. package/dist/storage/limits.js +17 -0
  443. package/dist/storage/maintenance-auth.d.ts +20 -0
  444. package/dist/storage/maintenance-auth.js +24 -0
  445. package/dist/storage/maintenance.d.ts +11 -0
  446. package/dist/storage/maintenance.js +67 -0
  447. package/dist/storage/neon.d.ts +4 -0
  448. package/dist/storage/neon.js +318 -0
  449. package/dist/storage/operator.d.ts +11 -0
  450. package/dist/storage/operator.js +30 -0
  451. package/dist/storage/provider.d.ts +93 -0
  452. package/dist/storage/provider.js +36 -0
  453. package/dist/storage/sessions.d.ts +184 -0
  454. package/dist/storage/sessions.js +688 -0
  455. package/dist/storage/tables.d.ts +54 -0
  456. package/dist/storage/tables.js +205 -0
  457. package/dist/storage/transfers.d.ts +16 -0
  458. package/dist/storage/transfers.js +153 -0
  459. package/dist/storage/writes.d.ts +7 -0
  460. package/dist/storage/writes.js +88 -0
  461. package/dist/telegram.d.ts +35 -0
  462. package/dist/telegram.js +173 -0
  463. package/dist/user-identity.d.ts +28 -0
  464. package/dist/user-identity.js +106 -0
  465. package/dist/user.d.ts +75 -0
  466. package/dist/user.js +111 -0
  467. package/dist/warehouse-backed.d.ts +6 -0
  468. package/dist/warehouse-backed.js +495 -0
  469. package/dist/webhook-token.d.ts +11 -0
  470. package/dist/webhook-token.js +18 -0
  471. package/dist/webhooks.d.ts +40 -0
  472. package/dist/webhooks.js +55 -0
  473. package/dist/wire.d.ts +10 -0
  474. package/dist/wire.js +35 -0
  475. package/dist/workflow-runtime.d.ts +116 -0
  476. package/dist/workflow-runtime.js +109 -0
  477. package/dist/workflow-topics.d.ts +28 -0
  478. package/dist/workflow-topics.js +28 -0
  479. package/dist/workflow.d.ts +284 -0
  480. package/dist/workflow.js +259 -0
  481. package/dist/workflows.d.ts +24 -0
  482. package/dist/workflows.js +18 -0
  483. package/package.json +127 -4
@@ -0,0 +1,733 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { Effect } from "effect";
3
+ import { acceptsBlobContentType, isBlobContentType, BLOB_UPLOAD_ANNOTATION, MAX_BLOB_BYTES, } from "../blob.js";
4
+ import { ActionRejected, Conflict, NotFound } from "../errors.js";
5
+ import { canonicalJson } from "../manifest.js";
6
+ import { actionInputJsonSchema } from "../runtime/generated-actions.js";
7
+ import { resolveAction } from "../runtime/pipeline.js";
8
+ import { authorizeBlobOperation } from "./authorization.js";
9
+ import { storageQuotaRejection } from "./limits.js";
10
+ import { ATTACHMENT_SOURCES, ATTACHMENTS_TABLE } from "./tables.js";
11
+ /**
12
+ * The agent's temporary attachments: a file a member sends the app's agent is stored once, as a
13
+ * `_blobs` revision in the ontology's private file storage. Group files belong to the chat's current
14
+ * members; private files belong to their sender and the agent session that first reads them.
15
+ * Nothing about it is an ontology record. It expires 24 hours after receipt; the agent may read it,
16
+ * discard it, or promote it into a record through an action, which adopts the same stored object
17
+ * without copying it. Every operation derives chat access from the caller and registry, never from
18
+ * tool input.
19
+ */
20
+ /** The largest file the agent takes: what Telegram's Bot API lets a bot download. */
21
+ export const MAX_ATTACHMENT_BYTES = 20 * 1024 * 1024;
22
+ /** How long an attachment stays readable after it arrives; reads never extend it. */
23
+ export const ATTACHMENT_TTL_HOURS = 24;
24
+ /** Live attachments one member may hold at once, so a busy chat cannot fill the storage. */
25
+ export const MAX_LIVE_ATTACHMENTS = 50;
26
+ export { ATTACHMENT_SOURCES };
27
+ const SHA256 = /^[0-9a-f]{64}$/;
28
+ const SELECT = `SELECT a.id::text, a.blob_id::text, b.key, a.owner_id::text, a.conversation,
29
+ a.session_id, a.filename, a.content_type, a.size::text, a.sha256, a.state, a.provider_upload_id,
30
+ a.promotion,
31
+ to_char(a.created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS created_at,
32
+ to_char(a.expires_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS expires_at,
33
+ a.expires_at <= now() AS expired
34
+ FROM "${ATTACHMENTS_TABLE}" a JOIN "_blobs" b ON b.id = a.blob_id`;
35
+ const READABLE = new Set(["ready", "promoted"]);
36
+ /** The fields of an action's input that take an upload, with the property each one writes. */
37
+ export function uploadFields(input) {
38
+ const schema = actionInputJsonSchema(input);
39
+ const definitions = (schema.$defs ?? {});
40
+ const resolve = (value, depth = 0) => {
41
+ if (typeof value !== "object" || value === null || depth > 8) {
42
+ return undefined;
43
+ }
44
+ const object = value;
45
+ const reference = object.$ref;
46
+ return typeof reference === "string" && reference.startsWith("#/$defs/")
47
+ ? resolve(definitions[reference.slice("#/$defs/".length)], depth + 1)
48
+ : object;
49
+ };
50
+ const binding = (value, depth = 0) => {
51
+ const object = resolve(value);
52
+ if (object === undefined || depth > 8) {
53
+ return undefined;
54
+ }
55
+ const annotation = object[BLOB_UPLOAD_ANNOTATION];
56
+ if (typeof annotation?.entity === "string" && typeof annotation.property === "string") {
57
+ return { entity: annotation.entity, property: annotation.property };
58
+ }
59
+ for (const key of ["anyOf", "oneOf", "allOf"]) {
60
+ const options = object[key];
61
+ if (Array.isArray(options)) {
62
+ for (const option of options) {
63
+ const found = binding(option, depth + 1);
64
+ if (found !== undefined) {
65
+ return found;
66
+ }
67
+ }
68
+ }
69
+ }
70
+ return undefined;
71
+ };
72
+ const root = resolve(schema);
73
+ const properties = (root?.properties ?? {});
74
+ return Object.entries(properties).flatMap(([field, value]) => {
75
+ const found = binding(value);
76
+ return found === undefined ? [] : [{ field, ...found }];
77
+ });
78
+ }
79
+ /** Whether the action's input has a top-level `sha256` field the bridge may fill in. */
80
+ function takesSha256(input) {
81
+ const schema = actionInputJsonSchema(input);
82
+ const reference = schema.$ref;
83
+ const root = typeof reference === "string" && reference.startsWith("#/$defs/")
84
+ ? schema.$defs?.[reference.slice("#/$defs/".length)]
85
+ : schema;
86
+ const properties = root?.properties;
87
+ return properties !== undefined && "sha256" in properties;
88
+ }
89
+ function view(row) {
90
+ return {
91
+ id: row.id,
92
+ filename: row.filename,
93
+ contentType: row.content_type,
94
+ size: Number(row.size),
95
+ sha256: row.sha256,
96
+ state: row.expired && (row.state === "ready" || row.state === "received") ? "expired" : row.state,
97
+ createdAt: row.created_at,
98
+ expiresAt: row.expires_at,
99
+ promotion: row.promotion ?? null,
100
+ conversation: row.conversation,
101
+ senderId: row.owner_id,
102
+ };
103
+ }
104
+ /** Persisted phases surround every provider call; no external I/O holds a database transaction. */
105
+ export function attachmentRuntime(ontology, sql, provider) {
106
+ const binding = provider.binding;
107
+ const missing = (id) => new NotFound({ entity: "attachment", id, message: "Attachment not found." });
108
+ const unavailable = (message) => new Conflict({ entity: "attachment", message });
109
+ /** The registry contains groups only. Neither a tool argument nor a caller's roles name a group. */
110
+ const accessScope = (caller) => Effect.gen(function* () {
111
+ const [member] = yield* sql.unsafe(`SELECT kind::text, active FROM "user" WHERE id = $1::uuid FOR SHARE`, [caller.principal.id]);
112
+ if (member?.kind !== "member" || !member.active) {
113
+ return "denied";
114
+ }
115
+ yield* authorizeBlobOperation(ontology, sql, caller.principal.id, []);
116
+ if (caller.access === "member") {
117
+ const admin = yield* authorizeBlobOperation(ontology, sql, caller.principal.id, [
118
+ "agent_chat:update",
119
+ ]).pipe(Effect.as(true), Effect.catchTag("PermissionDenied", () => Effect.succeed(false)));
120
+ if (admin) {
121
+ return "administrator";
122
+ }
123
+ }
124
+ const separator = caller.conversation.indexOf(":");
125
+ const [chat] = yield* sql.unsafe(`SELECT id::text FROM "agent_chat"
126
+ WHERE provider = $1 AND external_id = $2 FOR SHARE`, [caller.conversation.slice(0, separator), caller.conversation.slice(separator + 1)]);
127
+ if (chat === undefined) {
128
+ return "private";
129
+ }
130
+ const [participant] = yield* sql.unsafe(`SELECT 1 FROM "agent_chat_members"
131
+ WHERE agent_chat_id = $1::uuid AND user_id = $2::uuid FOR SHARE`, [chat.id, caller.principal.id]);
132
+ if (participant !== undefined) {
133
+ return "group";
134
+ }
135
+ // App roles are authored by the workspace. Chat management grants its administrator access;
136
+ // re-read the roles here rather than trusting permissions cached on the principal.
137
+ const administrator = yield* authorizeBlobOperation(ontology, sql, caller.principal.id, [
138
+ "agent_chat:update",
139
+ ]).pipe(Effect.as(true), Effect.catchTag("PermissionDenied", () => Effect.succeed(false)));
140
+ return administrator ? "group" : "denied";
141
+ });
142
+ /**
143
+ * The caller's attachment, locked. Group access follows current chat membership. Private access
144
+ * requires the sender and their session. Source transfers always require the original sender.
145
+ */
146
+ const load = (id, caller,
147
+ /** `source`: the webhook redelivering the file it stored, before or after a session read it. */
148
+ scope = "session") => Effect.gen(function* () {
149
+ if (!/^[0-9a-f-]{36}$/i.test(id)) {
150
+ return yield* missing(id);
151
+ }
152
+ const access = yield* accessScope(caller);
153
+ if (access === "denied") {
154
+ return yield* missing(id);
155
+ }
156
+ const rows = yield* sql.unsafe(`${SELECT} WHERE a.id = $1::uuid AND (a.owner_id = $2::uuid OR $5) AND a.conversation = $3
157
+ AND a.branch_id = $4 FOR UPDATE OF a, b`, [
158
+ id,
159
+ caller.principal.id,
160
+ caller.conversation,
161
+ binding.branchId,
162
+ (access === "group" || access === "administrator") && scope === "session",
163
+ ]);
164
+ const row = rows[0];
165
+ if (!row) {
166
+ return yield* missing(id);
167
+ }
168
+ if (access === "private" &&
169
+ caller.access !== "member" &&
170
+ scope === "session" &&
171
+ row.session_id !== null &&
172
+ row.session_id !== caller.sessionId) {
173
+ return yield* missing(id);
174
+ }
175
+ if (access === "private" &&
176
+ caller.access !== "member" &&
177
+ scope === "session" &&
178
+ row.session_id === null &&
179
+ caller.sessionId !== undefined) {
180
+ yield* sql.unsafe(`UPDATE "${ATTACHMENTS_TABLE}" SET session_id = $1 WHERE id = $2::uuid`, [
181
+ caller.sessionId,
182
+ row.id,
183
+ ]);
184
+ }
185
+ return row;
186
+ });
187
+ /**
188
+ * Reserves the attachment and its revision, and answers where to put the bytes. A retry of the
189
+ * same source file answers the same attachment.
190
+ */
191
+ const receive = (input, caller) => Effect.gen(function* () {
192
+ if (!ATTACHMENT_SOURCES.includes(input.source) ||
193
+ input.sourceRef === "" ||
194
+ input.sourceRef.length > 200 ||
195
+ input.filename === "" ||
196
+ input.filename.length > 255 ||
197
+ /[\x00-\x1f\x7f/\\]/.test(input.filename) ||
198
+ !isBlobContentType(input.contentType) ||
199
+ !Number.isInteger(input.size) ||
200
+ input.size <= 0 ||
201
+ !SHA256.test(input.sha256)) {
202
+ return yield* new ActionRejected({ message: "The attachment's metadata is invalid." });
203
+ }
204
+ if (input.size > MAX_ATTACHMENT_BYTES) {
205
+ return yield* new ActionRejected({
206
+ message: `The file is larger than the ${MAX_ATTACHMENT_BYTES / 1024 / 1024} MB the agent accepts.`,
207
+ });
208
+ }
209
+ const reserved = yield* sql.withTransaction(Effect.gen(function* () {
210
+ yield* authorizeBlobOperation(ontology, sql, caller.principal.id, [], caller.principal.execution);
211
+ yield* sql.unsafe(`SELECT pg_advisory_xact_lock(818724)`);
212
+ const [existing] = yield* sql.unsafe(`SELECT id::text FROM "${ATTACHMENTS_TABLE}"
213
+ WHERE owner_id = $1::uuid AND conversation = $2 AND source_ref = $3`, [caller.principal.id, caller.conversation, input.sourceRef]);
214
+ if (existing) {
215
+ const row = yield* load(existing.id, caller, "source");
216
+ if (row.sha256 !== input.sha256 || Number(row.size) !== input.size) {
217
+ return yield* unavailable("Another file was already received for this message.");
218
+ }
219
+ return row;
220
+ }
221
+ const [usage] = yield* sql.unsafe(`SELECT (SELECT coalesce(sum(size), 0)::text FROM "_blobs" WHERE state <> 'deleted') AS bytes,
222
+ (SELECT count(*)::text FROM "${ATTACHMENTS_TABLE}" WHERE owner_id = $1::uuid
223
+ AND state IN ('received', 'ready') AND expires_at > now()) AS live`, [caller.principal.id]);
224
+ const overQuota = storageQuotaRejection({
225
+ usedBytes: Number(usage.bytes),
226
+ requestedBytes: input.size,
227
+ quotaBytes: binding.quotaBytes,
228
+ });
229
+ if (overQuota) {
230
+ return yield* overQuota;
231
+ }
232
+ if (Number(usage.live) >= MAX_LIVE_ATTACHMENTS) {
233
+ return yield* new ActionRejected({
234
+ message: `The member's attachment limit is reached: ${MAX_LIVE_ATTACHMENTS} are live.`,
235
+ limit: {
236
+ kind: "member_attachments",
237
+ live: Number(usage.live),
238
+ max: MAX_LIVE_ATTACHMENTS,
239
+ },
240
+ });
241
+ }
242
+ const id = randomUUID();
243
+ const blobId = randomUUID();
244
+ yield* sql.unsafe(`INSERT INTO "_blobs" (id, branch_id, key, filename, content_type, size, state)
245
+ VALUES ($1::uuid, $2, $3, $4, $5, $6::bigint, 'ready')`, [
246
+ blobId,
247
+ binding.branchId,
248
+ `revisions/${blobId}`,
249
+ input.filename,
250
+ input.contentType,
251
+ input.size,
252
+ ]);
253
+ yield* sql.unsafe(`INSERT INTO "${ATTACHMENTS_TABLE}"
254
+ (id, blob_id, branch_id, owner_id, conversation, session_id, source, source_ref,
255
+ filename, content_type, size, sha256, state, expires_at)
256
+ VALUES ($1::uuid, $2::uuid, $3, $4::uuid, $5, $6, $7, $8, $9, $10, $11::bigint, $12,
257
+ 'received', now() + make_interval(hours => ${ATTACHMENT_TTL_HOURS}))`, [
258
+ id,
259
+ blobId,
260
+ binding.branchId,
261
+ caller.principal.id,
262
+ caller.conversation,
263
+ caller.sessionId ?? null,
264
+ input.source,
265
+ input.sourceRef,
266
+ input.filename,
267
+ input.contentType,
268
+ input.size,
269
+ input.sha256,
270
+ ]);
271
+ return yield* load(id, caller, "source");
272
+ }));
273
+ if (reserved.state !== "received" || reserved.expired) {
274
+ return { attachment: view(reserved) };
275
+ }
276
+ const pending = reserved.provider_upload_id
277
+ ? [reserved.provider_upload_id]
278
+ : yield* provider.pending(reserved.key);
279
+ const uploadId = pending[0] ??
280
+ (yield* provider.initiate({
281
+ key: reserved.key,
282
+ filename: reserved.filename,
283
+ contentType: reserved.content_type,
284
+ sessionId: reserved.id,
285
+ }));
286
+ if (reserved.provider_upload_id !== uploadId) {
287
+ yield* sql.withTransaction(sql.unsafe(`UPDATE "${ATTACHMENTS_TABLE}" SET provider_upload_id = $1
288
+ WHERE id = $2::uuid AND state = 'received'`, [uploadId, reserved.id]));
289
+ }
290
+ const upload = yield* provider.signPart({
291
+ key: reserved.key,
292
+ uploadId,
293
+ partNumber: 1,
294
+ size: Number(reserved.size),
295
+ });
296
+ return { attachment: view(reserved), upload };
297
+ });
298
+ /** Completes the transfer, then verifies the stored bytes against the declared size and digest. */
299
+ const complete = (id, caller) => Effect.gen(function* () {
300
+ const row = yield* sql.withTransaction(load(id, caller, "source"));
301
+ if (row.state !== "received") {
302
+ return READABLE.has(row.state) && !row.expired
303
+ ? view(row)
304
+ : yield* unavailable("The attachment is no longer available.");
305
+ }
306
+ if (row.expired || !row.provider_upload_id) {
307
+ return yield* unavailable("The attachment's transfer did not start or has expired.");
308
+ }
309
+ let stored = yield* provider.inspect(row.key);
310
+ if (!stored) {
311
+ const parts = yield* provider.parts({ key: row.key, uploadId: row.provider_upload_id });
312
+ if (parts.length !== 1 || parts[0]?.number !== 1 || parts[0].size !== Number(row.size)) {
313
+ return yield* new ActionRejected({
314
+ message: "The transferred file does not match its declared size.",
315
+ });
316
+ }
317
+ yield* provider.complete({ key: row.key, uploadId: row.provider_upload_id, parts });
318
+ stored = yield* provider.inspect(row.key);
319
+ }
320
+ const bytes = stored ? yield* provider.read(row.key, MAX_ATTACHMENT_BYTES) : undefined;
321
+ const digest = bytes ? createHash("sha256").update(bytes).digest("hex") : undefined;
322
+ if (!stored ||
323
+ stored.size !== Number(row.size) ||
324
+ stored.sessionId !== row.id ||
325
+ digest !== row.sha256) {
326
+ yield* sql.withTransaction(sql.unsafe(`UPDATE "${ATTACHMENTS_TABLE}" SET state = 'discarded', expires_at = now()
327
+ WHERE id = $1::uuid AND state = 'received'`, [row.id]));
328
+ return yield* new ActionRejected({
329
+ message: "The transferred file does not match its declared size or digest.",
330
+ });
331
+ }
332
+ return yield* sql.withTransaction(Effect.gen(function* () {
333
+ yield* sql.unsafe(`UPDATE "${ATTACHMENTS_TABLE}" SET state = 'ready'
334
+ WHERE id = $1::uuid AND state = 'received'`, [row.id]);
335
+ return view(yield* load(row.id, caller, "source"));
336
+ }));
337
+ });
338
+ /** Accessible attachments in this chat that are still readable, newest first. */
339
+ const list = (caller) => sql.withTransaction(Effect.gen(function* () {
340
+ const access = yield* accessScope(caller);
341
+ if (access === "denied") {
342
+ return [];
343
+ }
344
+ const rows = yield* sql.unsafe(`${SELECT} WHERE (a.owner_id = $1::uuid OR $5) AND a.conversation = $2 AND a.branch_id = $3
345
+ AND a.state IN ('ready', 'promoted') AND a.expires_at > now()
346
+ AND ($5 OR a.session_id IS NULL OR a.session_id = $4)
347
+ ORDER BY a.created_at DESC LIMIT ${MAX_LIVE_ATTACHMENTS}`, [
348
+ caller.principal.id,
349
+ caller.conversation,
350
+ binding.branchId,
351
+ caller.sessionId ?? null,
352
+ access === "group",
353
+ ]);
354
+ return rows.map(view);
355
+ }));
356
+ /**
357
+ * A one-minute grant to read the attachment's bytes, for the agent's runtime only. An expired,
358
+ * discarded or unfinished attachment cannot be read.
359
+ */
360
+ const read = (id, caller) => Effect.gen(function* () {
361
+ const row = yield* sql.withTransaction(load(id, caller));
362
+ if (!READABLE.has(row.state) || row.expired) {
363
+ return yield* unavailable("The attachment expired or was discarded.");
364
+ }
365
+ const download = yield* provider.download({
366
+ key: row.key,
367
+ filename: row.filename,
368
+ contentType: row.content_type,
369
+ inline: false,
370
+ });
371
+ return { attachment: view(row), download };
372
+ });
373
+ /** Revokes the caller's access at once; maintenance removes the bytes. */
374
+ const discard = (id, caller) => sql.withTransaction(Effect.gen(function* () {
375
+ const row = yield* load(id, caller);
376
+ if (row.state === "promoted") {
377
+ return yield* unavailable("The attachment was promoted; its file belongs to the record.");
378
+ }
379
+ if (row.state === "received" || row.state === "ready") {
380
+ yield* sql.unsafe(`UPDATE "${ATTACHMENTS_TABLE}" SET state = 'discarded', expires_at = now()
381
+ WHERE id = $1::uuid`, [row.id]);
382
+ // A ticket a failed promotion left behind goes with it.
383
+ yield* sql.unsafe(`UPDATE "_blob_uploads" SET state = 'canceled', expires_at = now(), claim = NULL
384
+ WHERE blob_id = $1::uuid AND state = 'ready'`, [row.blob_id]);
385
+ }
386
+ return view(yield* load(row.id, caller));
387
+ }));
388
+ /**
389
+ * Adopts the attachment's stored object into a record through an action, as the caller and with
390
+ * their permissions: a completed upload ticket is minted on the existing revision, and the action
391
+ * consumes it in its own transaction. Nothing is copied or uploaded again. A file whose SHA-256
392
+ * an earlier attachment already promoted through the same action is not promoted twice: the
393
+ * earlier promotion is the answer. A retry with the same target replays the action's outcome.
394
+ */
395
+ const promote = (id, target, caller, run) => Effect.gen(function* () {
396
+ const found = resolveAction(ontology, target.entity, target.action);
397
+ if (!found) {
398
+ return yield* new ActionRejected({
399
+ message: `${target.entity}.${target.action} is not an action of the ontology.`,
400
+ });
401
+ }
402
+ const fields = uploadFields(found.action.input).filter((candidate) => target.field === undefined || candidate.field === target.field);
403
+ if (fields.length !== 1) {
404
+ const where = target.field === undefined ? "" : ` in ${target.field}`;
405
+ return yield* new ActionRejected({
406
+ message: fields.length === 0
407
+ ? `${target.entity}.${target.action} takes no file${where}.`
408
+ : `${target.entity}.${target.action} takes several files; name the field.`,
409
+ });
410
+ }
411
+ const field = fields[0];
412
+ const fileEntity = ontology.manifest.entities.find((item) => item.name === field.entity);
413
+ const property = fileEntity?.properties.find((item) => item.name === field.property && item.type === "blob");
414
+ if (!fileEntity || fileEntity.source || !property) {
415
+ return yield* new ActionRejected({ message: "The action's file field is invalid." });
416
+ }
417
+ if (target.recordId !== undefined && !/^[0-9a-f-]{36}$/i.test(target.recordId)) {
418
+ return yield* new ActionRejected({ message: "The record id is invalid." });
419
+ }
420
+ if (field.field in target.input) {
421
+ return yield* new ActionRejected({
422
+ message: `Leave ${field.field} out of the input; the attachment fills it.`,
423
+ });
424
+ }
425
+ const qualified = `${target.entity}.${target.action}`;
426
+ const prepared = yield* sql.withTransaction(Effect.gen(function* () {
427
+ const row = yield* load(id, caller);
428
+ yield* authorizeBlobOperation(ontology, sql, caller.principal.id, found.permissions, caller.principal.execution);
429
+ if (caller.access === "member" && row.expired) {
430
+ return yield* unavailable("The attachment expired or was purged.");
431
+ }
432
+ if (row.state === "promoted") {
433
+ return { kind: "done", row };
434
+ }
435
+ if (row.state !== "ready" || row.expired) {
436
+ return yield* unavailable("The attachment expired or was discarded.");
437
+ }
438
+ if (!acceptsBlobContentType(property.accept, row.content_type) ||
439
+ Number(row.size) > (property.maxBytes ?? MAX_BLOB_BYTES)) {
440
+ return yield* new ActionRejected({
441
+ message: `${field.entity}.${field.property} accepts ${(property.accept ?? []).join(", ")} ` +
442
+ `up to ${property.maxBytes ?? MAX_BLOB_BYTES} bytes; this file is ` +
443
+ `${row.content_type}, ${row.size} bytes.`,
444
+ });
445
+ }
446
+ const [earlier] = yield* sql.unsafe(`SELECT id::text, promotion FROM "${ATTACHMENTS_TABLE}"
447
+ WHERE sha256 = $1 AND branch_id = $2 AND state = 'promoted' AND id <> $3::uuid
448
+ AND promotion->>'action' = $4 AND promotion->>'deduplicated_from' IS NULL
449
+ AND conversation = $5 AND (owner_id = $6::uuid OR $7)
450
+ AND ($7 OR $9 OR session_id IS NULL OR session_id = $8)
451
+ ORDER BY created_at LIMIT 1`, [
452
+ row.sha256,
453
+ binding.branchId,
454
+ row.id,
455
+ qualified,
456
+ caller.conversation,
457
+ caller.principal.id,
458
+ ["group", "administrator"].includes(yield* accessScope(caller)),
459
+ caller.sessionId ?? null,
460
+ caller.access === "member",
461
+ ]);
462
+ if (earlier) {
463
+ const promotion = {
464
+ ...earlier.promotion,
465
+ deduplicated_from: earlier.id,
466
+ };
467
+ yield* sql.unsafe(`UPDATE "${ATTACHMENTS_TABLE}" SET state = 'promoted', promotion = $1::jsonb
468
+ WHERE id = $2::uuid`, [JSON.stringify(promotion), row.id]);
469
+ return { kind: "done", row: yield* load(row.id, caller) };
470
+ }
471
+ const [existing] = yield* sql.unsafe(`SELECT id::text, state, record_id::text, create_intent::text FROM "_blob_uploads"
472
+ WHERE blob_id = $1::uuid FOR UPDATE`, [row.blob_id]);
473
+ if (existing && existing.state === "consumed") {
474
+ // An earlier promotion committed but its answer was lost; the action replays it.
475
+ return { kind: "run", row, upload: existing };
476
+ }
477
+ if (existing && existing.state !== "ready") {
478
+ return yield* unavailable("The attachment's file is being removed.");
479
+ }
480
+ const upload = {
481
+ id: existing?.id ?? randomUUID(),
482
+ state: "ready",
483
+ record_id: target.recordId?.toLowerCase() ?? null,
484
+ create_intent: target.recordId === undefined ? randomUUID() : null,
485
+ };
486
+ const values = [
487
+ upload.id,
488
+ row.blob_id,
489
+ caller.principal.id,
490
+ binding.branchId,
491
+ ontology.manifest.hash,
492
+ qualified,
493
+ fileEntity.id,
494
+ property.name,
495
+ upload.record_id,
496
+ upload.create_intent,
497
+ row.filename,
498
+ row.content_type,
499
+ row.size,
500
+ row.id,
501
+ ];
502
+ if (existing) {
503
+ yield* sql.unsafe(`UPDATE "_blob_uploads" SET blob_id = $2::uuid, principal_id = $3::uuid,
504
+ branch_id = $4, manifest_hash = $5, action = $6, entity_id = $7, property_id = $8,
505
+ record_id = $9::uuid, create_intent = $10::uuid, filename = $11,
506
+ content_type = $12, size = $13::bigint,
507
+ expires_at = (SELECT expires_at FROM "${ATTACHMENTS_TABLE}" WHERE id = $14::uuid)
508
+ WHERE id = $1::uuid`, values);
509
+ }
510
+ else {
511
+ yield* sql.unsafe(`INSERT INTO "_blob_uploads"
512
+ (id, blob_id, principal_id, branch_id, manifest_hash, action, entity_id, property_id,
513
+ record_id, create_intent, filename, content_type, size, state, expires_at)
514
+ VALUES ($1::uuid, $2::uuid, $3::uuid, $4, $5, $6, $7, $8, $9::uuid, $10::uuid, $11,
515
+ $12, $13::bigint, 'ready',
516
+ (SELECT expires_at FROM "${ATTACHMENTS_TABLE}" WHERE id = $14::uuid))`, values);
517
+ }
518
+ return { kind: "run", row, upload };
519
+ }));
520
+ if (prepared.kind === "done") {
521
+ return {
522
+ attachment: view(prepared.row),
523
+ deduplicated: Boolean(prepared.row.promotion?.deduplicated_from),
524
+ result: prepared.row.promotion?.result ?? null,
525
+ };
526
+ }
527
+ const { row, upload } = prepared;
528
+ const input = {
529
+ ...target.input,
530
+ [field.field]: { upload_id: upload.id },
531
+ };
532
+ if (!("sha256" in input) && takesSha256(found.action.input)) {
533
+ input.sha256 = row.sha256;
534
+ }
535
+ // The same attachment, target and input always run under the same key, so a retry replays.
536
+ const idempotencyKey = createHash("sha256")
537
+ .update(canonicalJson({ attachment: row.id, qualified, input }))
538
+ .digest("hex");
539
+ const outcome = yield* run({
540
+ entity: target.entity,
541
+ action: target.action,
542
+ input,
543
+ options: {
544
+ idempotencyKey,
545
+ ...(upload.create_intent === null ? {} : { createIntent: upload.create_intent }),
546
+ },
547
+ });
548
+ const promotion = {
549
+ action: qualified,
550
+ result: outcome.result ?? null,
551
+ promoted_at: new Date().toISOString(),
552
+ };
553
+ const promoted = yield* sql.withTransaction(Effect.gen(function* () {
554
+ yield* sql.unsafe(`UPDATE "${ATTACHMENTS_TABLE}" SET state = 'promoted', promotion = $1::jsonb
555
+ WHERE id = $2::uuid AND state = 'ready'`, [JSON.stringify(promotion), row.id]);
556
+ return yield* load(row.id, caller);
557
+ }));
558
+ return { attachment: view(promoted), deduplicated: false, result: outcome.result ?? null };
559
+ });
560
+ /** Resolve the chat from storage, then authorize with the same locked loader. */
561
+ const memberCaller = (id, principal) => sql.withTransaction(Effect.gen(function* () {
562
+ if (!/^[0-9a-f-]{36}$/i.test(id)) {
563
+ return yield* missing(id);
564
+ }
565
+ const [row] = yield* sql.unsafe(`SELECT conversation FROM "${ATTACHMENTS_TABLE}" WHERE id = $1::uuid AND branch_id = $2`, [id, binding.branchId]);
566
+ if (!row) {
567
+ return yield* missing(id);
568
+ }
569
+ const caller = {
570
+ principal,
571
+ conversation: row.conversation,
572
+ access: "member",
573
+ };
574
+ yield* load(id, caller);
575
+ return caller;
576
+ }));
577
+ const getForMember = (id, principal) => Effect.gen(function* () {
578
+ const caller = yield* memberCaller(id, principal);
579
+ const row = yield* sql.withTransaction(load(id, caller));
580
+ if (!READABLE.has(row.state) || row.expired) {
581
+ return yield* unavailable("The attachment expired, was purged, or is not ready.");
582
+ }
583
+ return view(row);
584
+ });
585
+ const readForMember = (id, principal) => Effect.gen(function* () {
586
+ return yield* read(id, yield* memberCaller(id, principal));
587
+ });
588
+ const promoteForMember = (id, target, principal, run) => Effect.gen(function* () {
589
+ return yield* promote(id, target, yield* memberCaller(id, principal), run);
590
+ });
591
+ const listForMember = (filters, principal) => sql.withTransaction(Effect.gen(function* () {
592
+ const [member] = yield* sql.unsafe(`SELECT kind::text, active FROM "user" WHERE id = $1::uuid FOR SHARE`, [principal.id]);
593
+ if (member?.kind !== "member" || !member.active) {
594
+ return yield* missing("chat");
595
+ }
596
+ yield* authorizeBlobOperation(ontology, sql, principal.id, []);
597
+ const admin = yield* authorizeBlobOperation(ontology, sql, principal.id, [
598
+ "agent_chat:update",
599
+ ]).pipe(Effect.as(true), Effect.catchTag("PermissionDenied", () => Effect.succeed(false)));
600
+ if (filters.conversation !== undefined) {
601
+ const scope = yield* accessScope({
602
+ principal,
603
+ conversation: filters.conversation,
604
+ access: "member",
605
+ });
606
+ if (scope === "denied") {
607
+ return yield* missing("chat");
608
+ }
609
+ if (scope === "private") {
610
+ const [sent] = yield* sql.unsafe(`SELECT 1 FROM "${ATTACHMENTS_TABLE}" WHERE branch_id = $1 AND conversation = $2
611
+ AND owner_id = $3::uuid LIMIT 1`, [binding.branchId, filters.conversation, principal.id]);
612
+ if (sent === undefined) {
613
+ return yield* missing("chat");
614
+ }
615
+ }
616
+ }
617
+ const limit = filters.limit ?? 50;
618
+ const rows = yield* sql.unsafe(`${SELECT} WHERE a.branch_id = $1
619
+ AND ($2 OR EXISTS (
620
+ SELECT 1 FROM "agent_chat" c JOIN "agent_chat_members" m ON m.agent_chat_id = c.id
621
+ WHERE c.provider || ':' || c.external_id = a.conversation AND m.user_id = $3::uuid
622
+ ) OR (a.owner_id = $3::uuid AND NOT EXISTS (
623
+ SELECT 1 FROM "agent_chat" c WHERE c.provider || ':' || c.external_id = a.conversation
624
+ )))
625
+ AND ($4::text IS NULL OR a.conversation = $4)
626
+ AND ($5::uuid IS NULL OR a.owner_id = $5::uuid)
627
+ AND ($6::timestamptz IS NULL OR a.created_at >= $6::timestamptz)
628
+ AND ($7::timestamptz IS NULL OR a.created_at <= $7::timestamptz)
629
+ AND ($8::uuid IS NULL OR (a.created_at, a.id) < (
630
+ SELECT created_at, id FROM "${ATTACHMENTS_TABLE}" WHERE id = $8::uuid AND branch_id = $1
631
+ ))
632
+ ORDER BY a.created_at DESC, a.id DESC LIMIT $9`, [
633
+ binding.branchId,
634
+ admin,
635
+ principal.id,
636
+ filters.conversation ?? null,
637
+ filters.senderId ?? null,
638
+ filters.from ?? null,
639
+ filters.to ?? null,
640
+ filters.before ?? null,
641
+ limit + 1,
642
+ ]);
643
+ return {
644
+ attachments: rows.slice(0, limit).map(view),
645
+ nextCursor: rows.length > limit ? rows[limit - 1].id : null,
646
+ };
647
+ }));
648
+ return {
649
+ receive,
650
+ complete,
651
+ list,
652
+ read,
653
+ discard,
654
+ promote,
655
+ listForMember,
656
+ getForMember,
657
+ readForMember,
658
+ promoteForMember,
659
+ };
660
+ }
661
+ /**
662
+ * Expires attachments past their deadline and removes the bytes of every expired or discarded one
663
+ * that no record adopted. A revision a promotion ticket still points at is left to the upload
664
+ * maintenance, once the ticket is canceled. Runs from the storage maintenance schedule, so files
665
+ * expire while the agent is idle.
666
+ */
667
+ export function maintainAttachments(sql, provider, limit = 100) {
668
+ return Effect.gen(function* () {
669
+ const [table] = yield* sql `
670
+ SELECT to_regclass(${`public.${ATTACHMENTS_TABLE}`})::text AS present
671
+ `;
672
+ if (!table?.present) {
673
+ return { expired: 0, removed: 0, hasMore: false };
674
+ }
675
+ const expired = yield* sql.withTransaction(Effect.gen(function* () {
676
+ const rows = yield* sql.unsafe(`UPDATE "${ATTACHMENTS_TABLE}" SET state = 'expired'
677
+ WHERE state IN ('received', 'ready') AND expires_at <= now() RETURNING id`);
678
+ yield* sql.unsafe(`UPDATE "_blob_uploads" u SET state = 'canceled', expires_at = now(), claim = NULL
679
+ FROM "${ATTACHMENTS_TABLE}" a
680
+ WHERE u.blob_id = a.blob_id AND u.state = 'ready'
681
+ AND (a.state IN ('discarded', 'expired') OR a.expires_at <= now())`);
682
+ return rows.length;
683
+ }));
684
+ let removed = 0;
685
+ for (let count = 0; count < limit; count += 1) {
686
+ const candidate = yield* sql.withTransaction(Effect.gen(function* () {
687
+ const [row] = yield* sql.unsafe(`SELECT a.id::text, a.blob_id::text, b.key, a.provider_upload_id
688
+ FROM "${ATTACHMENTS_TABLE}" a JOIN "_blobs" b ON b.id = a.blob_id
689
+ WHERE a.removed_at IS NULL AND b.state IN ('ready', 'deleting')
690
+ AND (a.state IN ('discarded', 'expired') OR a.expires_at <= now()
691
+ OR a.branch_id <> $1)
692
+ AND NOT EXISTS (SELECT 1 FROM "_blob_references" r WHERE r.blob_id = b.id)
693
+ AND NOT EXISTS (SELECT 1 FROM "_blob_uploads" u WHERE u.blob_id = b.id)
694
+ ORDER BY a.created_at LIMIT 1 FOR UPDATE OF a, b SKIP LOCKED`, [provider.binding.branchId]);
695
+ if (!row) {
696
+ return undefined;
697
+ }
698
+ yield* sql.unsafe(`UPDATE "_blobs" SET state = 'deleting' WHERE id = $1::uuid`, [
699
+ row.blob_id,
700
+ ]);
701
+ return row;
702
+ }));
703
+ if (!candidate) {
704
+ break;
705
+ }
706
+ const handles = new Set([
707
+ ...(candidate.provider_upload_id ? [candidate.provider_upload_id] : []),
708
+ ...(yield* provider.pending(candidate.key)),
709
+ ]);
710
+ for (const uploadId of handles) {
711
+ yield* provider.abort({ key: candidate.key, uploadId });
712
+ }
713
+ yield* provider.remove(candidate.key);
714
+ yield* sql.withTransaction(Effect.gen(function* () {
715
+ yield* sql.unsafe(`UPDATE "_blobs" SET state = 'deleted' WHERE id = $1::uuid AND state = 'deleting'`, [candidate.blob_id]);
716
+ yield* sql.unsafe(`UPDATE "${ATTACHMENTS_TABLE}" SET removed_at = now(),
717
+ state = CASE WHEN state IN ('received', 'ready') THEN 'expired' ELSE state END
718
+ WHERE id = $1::uuid`, [candidate.id]);
719
+ }));
720
+ removed += 1;
721
+ }
722
+ // Include locked candidates: another pass must not postpone interrupted cleanup for hours.
723
+ const [backlog] = yield* sql.unsafe(`SELECT EXISTS (
724
+ SELECT 1 FROM "${ATTACHMENTS_TABLE}" a JOIN "_blobs" b ON b.id = a.blob_id
725
+ WHERE a.removed_at IS NULL AND b.state IN ('ready', 'deleting')
726
+ AND (a.state IN ('discarded', 'expired') OR a.expires_at <= now()
727
+ OR a.branch_id <> $1)
728
+ AND NOT EXISTS (SELECT 1 FROM "_blob_references" r WHERE r.blob_id = b.id)
729
+ AND NOT EXISTS (SELECT 1 FROM "_blob_uploads" u WHERE u.blob_id = b.id)
730
+ ) AS remaining`, [provider.binding.branchId]);
731
+ return { expired, removed, hasMore: backlog.remaining || removed >= limit };
732
+ });
733
+ }