@k2b/cloud 0.6.0-rc.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 (540) hide show
  1. package/README.md +98 -0
  2. package/package.json +128 -0
  3. package/public/logo.svg +1 -0
  4. package/scripts/app-favicon.test.ts +49 -0
  5. package/scripts/app-favicon.ts +51 -0
  6. package/scripts/build.ts +205 -0
  7. package/scripts/preload.ts +164 -0
  8. package/scripts/sync-recovery-smoke.ts +200 -0
  9. package/src/_internal/app-favicon.ts +2 -0
  10. package/src/_internal/app-presentation.ts +61 -0
  11. package/src/_internal/app-workspace-state.ts +24 -0
  12. package/src/_internal/bounded-json.ts +47 -0
  13. package/src/_internal/build-metadata.ts +9 -0
  14. package/src/_internal/capabilities.ts +1118 -0
  15. package/src/_internal/define-app.ts +801 -0
  16. package/src/_internal/heartbeat.ts +167 -0
  17. package/src/_internal/help-catalog.ts +241 -0
  18. package/src/_internal/help.ts +95 -0
  19. package/src/_internal/nats-connection.ts +34 -0
  20. package/src/_internal/page-responses.ts +40 -0
  21. package/src/_internal/process-sync.ts +117 -0
  22. package/src/_internal/readiness.ts +3 -0
  23. package/src/_internal/registry-validation.ts +136 -0
  24. package/src/_internal/registry-watch.ts +52 -0
  25. package/src/_internal/registry.ts +338 -0
  26. package/src/_internal/runtime-compatibility.ts +64 -0
  27. package/src/_internal/runtime-context.ts +84 -0
  28. package/src/_internal/runtime-watcher.ts +78 -0
  29. package/src/_internal/static-assets.ts +86 -0
  30. package/src/_internal/status-preserving-ssr.ts +49 -0
  31. package/src/access/PermissionEditor.tsx +486 -0
  32. package/src/access/ResourceApiKeys.tsx +271 -0
  33. package/src/access/messages.ts +121 -0
  34. package/src/access/ui.ts +16 -0
  35. package/src/account/Avatar.tsx +28 -0
  36. package/src/account/EntitySearch.tsx +363 -0
  37. package/src/account/avatar-upload-dialog.tsx +197 -0
  38. package/src/account/avatar-upload.ts +149 -0
  39. package/src/account/ui.ts +3 -0
  40. package/src/ai/admin.ts +47 -0
  41. package/src/ai/approval-routes.ts +130 -0
  42. package/src/ai/approvals.ts +139 -0
  43. package/src/ai/assistant-models.ts +61 -0
  44. package/src/ai/attachments.ts +40 -0
  45. package/src/ai/browser.ts +92 -0
  46. package/src/ai/capabilities.ts +714 -0
  47. package/src/ai/capability-execution.ts +151 -0
  48. package/src/ai/chat/blocks.render.test.tsx +1108 -0
  49. package/src/ai/chat/blocks.tsx +730 -0
  50. package/src/ai/chat/builtin-tools.tsx +428 -0
  51. package/src/ai/chat/capability-table.tsx +87 -0
  52. package/src/ai/chat/composer-adapter.ts +169 -0
  53. package/src/ai/chat/file-tools.tsx +81 -0
  54. package/src/ai/chat/message-actions.tsx +370 -0
  55. package/src/ai/chat/message-utils.ts +479 -0
  56. package/src/ai/chat/messages.ts +43 -0
  57. package/src/ai/chat/presentation.tsx +395 -0
  58. package/src/ai/chat/primitives.tsx +3 -0
  59. package/src/ai/chat/tool-disclosure.tsx +41 -0
  60. package/src/ai/chat/turn-navigator-utils.ts +28 -0
  61. package/src/ai/chat/turn-navigator.tsx +164 -0
  62. package/src/ai/chat/user-message.tsx +149 -0
  63. package/src/ai/chat/visual-tools.tsx +435 -0
  64. package/src/ai/chat/web-tools.tsx +202 -0
  65. package/src/ai/chat-task-contracts.ts +113 -0
  66. package/src/ai/chat-tasks.ts +952 -0
  67. package/src/ai/client/controller.ts +1211 -0
  68. package/src/ai/client/file-source.ts +95 -0
  69. package/src/ai/client/live-connection.ts +140 -0
  70. package/src/ai/client/projection.ts +178 -0
  71. package/src/ai/client/transport.ts +111 -0
  72. package/src/ai/compaction.ts +182 -0
  73. package/src/ai/credentials.ts +124 -0
  74. package/src/ai/default-tool-contracts.ts +108 -0
  75. package/src/ai/default-tools.ts +127 -0
  76. package/src/ai/enrich.ts +280 -0
  77. package/src/ai/executor.ts +1624 -0
  78. package/src/ai/fetch-file-tool.ts +313 -0
  79. package/src/ai/file-context.ts +92 -0
  80. package/src/ai/file-media-type.ts +28 -0
  81. package/src/ai/file-mount.ts +16 -0
  82. package/src/ai/file-tools.ts +566 -0
  83. package/src/ai/files-store.ts +390 -0
  84. package/src/ai/firecrawl-tools.ts +305 -0
  85. package/src/ai/grids-skill.ts +126 -0
  86. package/src/ai/http.ts +265 -0
  87. package/src/ai/index.ts +337 -0
  88. package/src/ai/kit-skill.ts +22 -0
  89. package/src/ai/limits.ts +5 -0
  90. package/src/ai/live-events.ts +289 -0
  91. package/src/ai/live-messages.ts +45 -0
  92. package/src/ai/live-outbox.ts +102 -0
  93. package/src/ai/live-routes.ts +412 -0
  94. package/src/ai/live.ts +2 -0
  95. package/src/ai/maintenance.ts +302 -0
  96. package/src/ai/markdown-pdf-tool.ts +95 -0
  97. package/src/ai/memories.ts +605 -0
  98. package/src/ai/memory-learning-runs.ts +222 -0
  99. package/src/ai/memory-learning.ts +619 -0
  100. package/src/ai/memory-tool.ts +120 -0
  101. package/src/ai/memory-workflow-evidence.ts +158 -0
  102. package/src/ai/migrate.ts +2082 -0
  103. package/src/ai/model-access-migrate.ts +30 -0
  104. package/src/ai/model-access.ts +191 -0
  105. package/src/ai/personal-agent.ts +6 -0
  106. package/src/ai/prefs.ts +72 -0
  107. package/src/ai/project-tool.ts +94 -0
  108. package/src/ai/projects-routes.ts +277 -0
  109. package/src/ai/projects.ts +902 -0
  110. package/src/ai/protocol.ts +279 -0
  111. package/src/ai/provider.ts +73 -0
  112. package/src/ai/public-projection.ts +25 -0
  113. package/src/ai/resource-markers.ts +31 -0
  114. package/src/ai/resource-refs.ts +70 -0
  115. package/src/ai/routes.ts +1131 -0
  116. package/src/ai/runtime-tools.ts +39 -0
  117. package/src/ai/runtime.ts +544 -0
  118. package/src/ai/settings.ts +529 -0
  119. package/src/ai/short-id.ts +45 -0
  120. package/src/ai/skill-catalog.ts +73 -0
  121. package/src/ai/skill-format.ts +145 -0
  122. package/src/ai/skill-seeds.ts +430 -0
  123. package/src/ai/skill-tool.ts +81 -0
  124. package/src/ai/skills-routes.ts +143 -0
  125. package/src/ai/skills.ts +934 -0
  126. package/src/ai/solid.ts +27 -0
  127. package/src/ai/store.ts +3113 -0
  128. package/src/ai/stream.ts +308 -0
  129. package/src/ai/structured-runs.ts +66 -0
  130. package/src/ai/structured.ts +186 -0
  131. package/src/ai/system-prompt.ts +170 -0
  132. package/src/ai/task-prompt.ts +22 -0
  133. package/src/ai/timeline.ts +148 -0
  134. package/src/ai/tool-audit.ts +177 -0
  135. package/src/ai/tool-result-budget.ts +16 -0
  136. package/src/ai/tools.ts +155 -0
  137. package/src/ai/types.ts +967 -0
  138. package/src/ai/ui.tsx +39 -0
  139. package/src/ai/usage-migrate.ts +81 -0
  140. package/src/ai/usage.ts +315 -0
  141. package/src/ai/validate.ts +54 -0
  142. package/src/ai/vision-tool.ts +79 -0
  143. package/src/api/accounts-entities.ts +174 -0
  144. package/src/api/admin-ai-projects.ts +74 -0
  145. package/src/api/admin-ai-skills.ts +76 -0
  146. package/src/api/admin-ai-usage.ts +42 -0
  147. package/src/api/admin-core-settings.ts +508 -0
  148. package/src/api/admin-identity.ts +148 -0
  149. package/src/api/admin-lifecycle.ts +206 -0
  150. package/src/api/admin-linux-identities.ts +66 -0
  151. package/src/api/announcements.ts +131 -0
  152. package/src/api/app-approval.ts +129 -0
  153. package/src/api/apps.ts +51 -0
  154. package/src/api/auth/schemas.ts +54 -0
  155. package/src/api/auth.ts +370 -0
  156. package/src/api/capabilities.ts +658 -0
  157. package/src/api/help-messages.ts +19 -0
  158. package/src/api/help.ts +78 -0
  159. package/src/api/index.ts +108 -0
  160. package/src/api/mcp.ts +744 -0
  161. package/src/api/me-rail.ts +45 -0
  162. package/src/api/me.ts +753 -0
  163. package/src/api/search/schemas.ts +79 -0
  164. package/src/api/search.bench.ts +185 -0
  165. package/src/api/search.ts +407 -0
  166. package/src/api/sync-ops.ts +79 -0
  167. package/src/api/widgets.ts +143 -0
  168. package/src/browser/CloudResourceSearch.behavior.test.tsx +74 -0
  169. package/src/browser/CloudResourceSearch.tsx +791 -0
  170. package/src/browser/app-approval-vault.ts +305 -0
  171. package/src/browser/app-approval.ts +234 -0
  172. package/src/browser/live-websocket.ts +268 -0
  173. package/src/browser/locale-preference.ts +25 -0
  174. package/src/browser/notification-target.ts +27 -0
  175. package/src/browser/notification-timeout.ts +14 -0
  176. package/src/browser/notifications.ts +181 -0
  177. package/src/browser/resource-clipboard.ts +70 -0
  178. package/src/browser/resource-picker.tsx +39 -0
  179. package/src/browser/resource-search-messages.ts +55 -0
  180. package/src/browser/resource-search.ts +27 -0
  181. package/src/capabilities/client.ts +149 -0
  182. package/src/capabilities/response.ts +39 -0
  183. package/src/capabilities/server.ts +215 -0
  184. package/src/capabilities/signals.ts +4 -0
  185. package/src/capabilities/testing.ts +12 -0
  186. package/src/capabilities/types.ts +32 -0
  187. package/src/cli/access.ts +499 -0
  188. package/src/cli/account.ts +382 -0
  189. package/src/cli/admin/account-administration.ts +45 -0
  190. package/src/cli/admin/account-categories.ts +49 -0
  191. package/src/cli/admin/ai-usage.ts +118 -0
  192. package/src/cli/admin/app-sign-in.ts +49 -0
  193. package/src/cli/admin/data.ts +343 -0
  194. package/src/cli/admin/documentation.ts +34 -0
  195. package/src/cli/admin/gateway.ts +155 -0
  196. package/src/cli/admin/index.ts +61 -0
  197. package/src/cli/admin/instance.ts +250 -0
  198. package/src/cli/admin/jobs.ts +223 -0
  199. package/src/cli/admin/legal.ts +122 -0
  200. package/src/cli/admin/linux.ts +53 -0
  201. package/src/cli/admin/logs.ts +324 -0
  202. package/src/cli/admin/metrics.ts +142 -0
  203. package/src/cli/admin/nats.ts +167 -0
  204. package/src/cli/admin/notifications.ts +516 -0
  205. package/src/cli/admin/shared.ts +113 -0
  206. package/src/cli/admin/sync.ts +188 -0
  207. package/src/cli/admin/telemetry.ts +314 -0
  208. package/src/cli/admin/webhooks.ts +222 -0
  209. package/src/cli/admin/workflows.ts +367 -0
  210. package/src/cli/apps.ts +40 -0
  211. package/src/cli/capabilities.ts +239 -0
  212. package/src/cli/commands.ts +604 -0
  213. package/src/cli/index.ts +61 -0
  214. package/src/cli/locale.ts +31 -0
  215. package/src/cli/output.ts +58 -0
  216. package/src/clients/core.ts +20 -0
  217. package/src/config/env.ts +54 -0
  218. package/src/config/index.ts +6 -0
  219. package/src/contracts/account-categories.ts +62 -0
  220. package/src/contracts/announcements.ts +121 -0
  221. package/src/contracts/app-approval.ts +197 -0
  222. package/src/contracts/app.ts +162 -0
  223. package/src/contracts/capabilities.ts +484 -0
  224. package/src/contracts/index.ts +14 -0
  225. package/src/contracts/notification-live.ts +66 -0
  226. package/src/contracts/notification-types.ts +235 -0
  227. package/src/contracts/posix.ts +69 -0
  228. package/src/contracts/profile.ts +85 -0
  229. package/src/contracts/rail-preferences.ts +53 -0
  230. package/src/contracts/registry.ts +110 -0
  231. package/src/contracts/resource-clipboard.ts +33 -0
  232. package/src/contracts/settings-types.ts +98 -0
  233. package/src/contracts/shared.ts +408 -0
  234. package/src/contracts/user-notifications.ts +101 -0
  235. package/src/contracts/widgets.ts +183 -0
  236. package/src/desktop/index.ts +704 -0
  237. package/src/desktop/solid.tsx +938 -0
  238. package/src/index.ts +58 -0
  239. package/src/server/actor.ts +41 -0
  240. package/src/server/api/index.ts +2 -0
  241. package/src/server/api/respond.ts +96 -0
  242. package/src/server/api-client.ts +54 -0
  243. package/src/server/app-context.ts +39 -0
  244. package/src/server/help.ts +187 -0
  245. package/src/server/index.ts +89 -0
  246. package/src/server/locale.ts +55 -0
  247. package/src/server/middleware/auth.ts +386 -0
  248. package/src/server/middleware/index.ts +28 -0
  249. package/src/server/middleware/invocation.ts +55 -0
  250. package/src/server/middleware/middleware.ts +65 -0
  251. package/src/server/middleware/openapi.ts +126 -0
  252. package/src/server/middleware/rate-limit.ts +125 -0
  253. package/src/server/middleware/request-logger.ts +41 -0
  254. package/src/server/middleware/route-template.ts +46 -0
  255. package/src/server/middleware/runtime.ts +22 -0
  256. package/src/server/middleware/settings.ts +55 -0
  257. package/src/server/middleware/validator.ts +49 -0
  258. package/src/server/middleware/workload.ts +22 -0
  259. package/src/server/ratelimit.ts +151 -0
  260. package/src/server/request-cookies.ts +18 -0
  261. package/src/server/services/access.ts +644 -0
  262. package/src/server/services/freeipa/client.ts +137 -0
  263. package/src/server/services/freeipa/index.ts +9 -0
  264. package/src/server/services/freeipa/session.ts +123 -0
  265. package/src/server/services/freeipa/test-certificates.ts +44 -0
  266. package/src/server/services/freeipa/tls.ts +44 -0
  267. package/src/server/services/freeipa/transport.ts +116 -0
  268. package/src/server/services/freeipa/util.ts +59 -0
  269. package/src/server/services/geo.ts +198 -0
  270. package/src/server/services/index.ts +48 -0
  271. package/src/server/services/pagination.ts +22 -0
  272. package/src/server/services/services.ts +13 -0
  273. package/src/server/time.ts +33 -0
  274. package/src/services/account-category-policy.ts +51 -0
  275. package/src/services/account-lifecycle/audit.ts +41 -0
  276. package/src/services/account-lifecycle/index.ts +940 -0
  277. package/src/services/account-lifecycle/ipa-backfill.ts +152 -0
  278. package/src/services/account-lifecycle/notification-sender.ts +13 -0
  279. package/src/services/account-lifecycle/scheduler.ts +506 -0
  280. package/src/services/account-model.ts +22 -0
  281. package/src/services/accounts/app.ts +1671 -0
  282. package/src/services/accounts/authz.ts +44 -0
  283. package/src/services/accounts/avatar.ts +120 -0
  284. package/src/services/accounts/base-group.ts +11 -0
  285. package/src/services/accounts/base-user.ts +46 -0
  286. package/src/services/accounts/duplicate-emails.ts +82 -0
  287. package/src/services/accounts/entities.ts +622 -0
  288. package/src/services/accounts/group-sql.ts +106 -0
  289. package/src/services/accounts/groups.ts +272 -0
  290. package/src/services/accounts/index.ts +14 -0
  291. package/src/services/accounts/ipa-data.ts +66 -0
  292. package/src/services/accounts/lifecycle.ts +2 -0
  293. package/src/services/accounts/local-groups.ts +499 -0
  294. package/src/services/accounts/model.ts +129 -0
  295. package/src/services/accounts/notification-sender.ts +31 -0
  296. package/src/services/accounts/posix.ts +386 -0
  297. package/src/services/accounts/request-policy.ts +11 -0
  298. package/src/services/accounts/switching.ts +126 -0
  299. package/src/services/accounts/users.ts +745 -0
  300. package/src/services/announcements/index.ts +224 -0
  301. package/src/services/app-approval.ts +498 -0
  302. package/src/services/audit/index.ts +438 -0
  303. package/src/services/auth-flows/index.ts +15 -0
  304. package/src/services/auth-flows/ipa.ts +197 -0
  305. package/src/services/auth-flows/magic-link.ts +185 -0
  306. package/src/services/auth-flows/notification-sender.ts +10 -0
  307. package/src/services/auth-flows/password-reset.ts +268 -0
  308. package/src/services/auth-flows/proxy-return.ts +49 -0
  309. package/src/services/document-extraction.ts +148 -0
  310. package/src/services/freeipa-config.ts +179 -0
  311. package/src/services/gateway.ts +186 -0
  312. package/src/services/identity/constants.ts +18 -0
  313. package/src/services/identity/index.ts +52 -0
  314. package/src/services/identity/invocation-actor.ts +120 -0
  315. package/src/services/identity/invocation-authority.ts +53 -0
  316. package/src/services/identity/invocation-operations.ts +15 -0
  317. package/src/services/identity/invocation-token.ts +276 -0
  318. package/src/services/identity/key-config.ts +41 -0
  319. package/src/services/identity/key-ring.ts +530 -0
  320. package/src/services/identity/metrics.ts +29 -0
  321. package/src/services/identity/routes.ts +18 -0
  322. package/src/services/identity/runtime-config.ts +88 -0
  323. package/src/services/identity/session-token.ts +152 -0
  324. package/src/services/identity/signer-refresh.ts +44 -0
  325. package/src/services/identity/workload-auth.ts +64 -0
  326. package/src/services/index.ts +187 -0
  327. package/src/services/ipa/auth.ts +216 -0
  328. package/src/services/ipa/connection.ts +57 -0
  329. package/src/services/ipa/effective-groups.ts +70 -0
  330. package/src/services/ipa/groups.ts +722 -0
  331. package/src/services/ipa/guard.ts +17 -0
  332. package/src/services/ipa/index.ts +17 -0
  333. package/src/services/ipa/posix.ts +19 -0
  334. package/src/services/ipa/profile.ts +132 -0
  335. package/src/services/ipa/runtime.ts +28 -0
  336. package/src/services/ipa/search.ts +154 -0
  337. package/src/services/ipa/service-account.ts +15 -0
  338. package/src/services/ipa/sync-planning.ts +100 -0
  339. package/src/services/ipa/sync.ts +862 -0
  340. package/src/services/ipa/users.ts +806 -0
  341. package/src/services/legal-consent.ts +76 -0
  342. package/src/services/logging/index.ts +477 -0
  343. package/src/services/logging/redaction.ts +20 -0
  344. package/src/services/logging/trace.ts +1231 -0
  345. package/src/services/mandates/index.ts +855 -0
  346. package/src/services/mandates/policy.ts +85 -0
  347. package/src/services/network-security.ts +31 -0
  348. package/src/services/notifications/batches.ts +722 -0
  349. package/src/services/notifications/browser.ts +252 -0
  350. package/src/services/notifications/catalog.ts +113 -0
  351. package/src/services/notifications/channels.ts +104 -0
  352. package/src/services/notifications/dispatcher.ts +139 -0
  353. package/src/services/notifications/email.ts +118 -0
  354. package/src/services/notifications/index.ts +633 -0
  355. package/src/services/notifications/live.ts +50 -0
  356. package/src/services/notifications/observability.ts +407 -0
  357. package/src/services/notifications/platform.ts +421 -0
  358. package/src/services/notifications/runtime.ts +120 -0
  359. package/src/services/notifications/user.ts +248 -0
  360. package/src/services/notifications/web-push-transport.ts +135 -0
  361. package/src/services/oauth-tokens.ts +209 -0
  362. package/src/services/outbox.ts +187 -0
  363. package/src/services/pdf/gotenberg.ts +290 -0
  364. package/src/services/pdf/index.ts +43 -0
  365. package/src/services/pdf/markdown.ts +209 -0
  366. package/src/services/pdf/template-preview.ts +122 -0
  367. package/src/services/postgres.ts +76 -0
  368. package/src/services/providers/index.ts +27 -0
  369. package/src/services/providers/local/auth.ts +51 -0
  370. package/src/services/providers/local/index.ts +4 -0
  371. package/src/services/providers/local/users.ts +263 -0
  372. package/src/services/rail-preferences.ts +27 -0
  373. package/src/services/runtime-lifecycle.ts +191 -0
  374. package/src/services/secrets.ts +10 -0
  375. package/src/services/service-account-credentials.ts +891 -0
  376. package/src/services/service-accounts.ts +184 -0
  377. package/src/services/session/index.ts +236 -0
  378. package/src/services/session/test-fixture.ts +18 -0
  379. package/src/services/session/user.ts +144 -0
  380. package/src/services/settings/api.ts +61 -0
  381. package/src/services/settings/app.ts +86 -0
  382. package/src/services/settings/core-settings.ts +796 -0
  383. package/src/services/settings/crypto.ts +69 -0
  384. package/src/services/settings/defaults.ts +374 -0
  385. package/src/services/settings/index.ts +215 -0
  386. package/src/services/settings/namespace.ts +9 -0
  387. package/src/services/settings/snapshot.ts +49 -0
  388. package/src/services/settings/store.ts +241 -0
  389. package/src/services/settings/templates.ts +4 -0
  390. package/src/services/sync-ops.ts +403 -0
  391. package/src/services/topic-cursor.ts +22 -0
  392. package/src/services/weather/forecast.ts +323 -0
  393. package/src/services/weather/geo.ts +119 -0
  394. package/src/services/weather/index.ts +67 -0
  395. package/src/services/weather/location.ts +24 -0
  396. package/src/services/weather/locations.ts +133 -0
  397. package/src/services/weather/migrate.ts +45 -0
  398. package/src/services/weather/settings.ts +66 -0
  399. package/src/services/weather/short-id.ts +57 -0
  400. package/src/services/weather/types.ts +61 -0
  401. package/src/services/weather/ui.ts +50 -0
  402. package/src/services/webauthn.ts +385 -0
  403. package/src/shared/account-action-notice.ts +70 -0
  404. package/src/shared/account-display.ts +15 -0
  405. package/src/shared/account-session.ts +15 -0
  406. package/src/shared/ai-platform-prompt.ts +134 -0
  407. package/src/shared/ai-usage.ts +49 -0
  408. package/src/shared/app-presentation.ts +48 -0
  409. package/src/shared/app-url.ts +12 -0
  410. package/src/shared/branding.ts +14 -0
  411. package/src/shared/capability-messages.ts +70 -0
  412. package/src/shared/email-html.ts +54 -0
  413. package/src/shared/format.ts +91 -0
  414. package/src/shared/help.ts +68 -0
  415. package/src/shared/icons.ts +400 -0
  416. package/src/shared/index.ts +54 -0
  417. package/src/shared/locale.ts +48 -0
  418. package/src/shared/login-method.ts +44 -0
  419. package/src/shared/markdown/client.ts +130 -0
  420. package/src/shared/markdown/extensions/code.ts +105 -0
  421. package/src/shared/markdown/extensions/guided-help.ts +100 -0
  422. package/src/shared/markdown/extensions/images.ts +79 -0
  423. package/src/shared/markdown/extensions/info-blocks.ts +97 -0
  424. package/src/shared/markdown/extensions/katex.ts +120 -0
  425. package/src/shared/markdown/extensions/links.ts +43 -0
  426. package/src/shared/markdown/extensions/mark.ts +48 -0
  427. package/src/shared/markdown/extensions/sub-sup.ts +60 -0
  428. package/src/shared/markdown/extensions/tables.ts +109 -0
  429. package/src/shared/markdown/extensions/task-list.ts +53 -0
  430. package/src/shared/markdown/formula.ts +1187 -0
  431. package/src/shared/markdown/index.ts +193 -0
  432. package/src/shared/markdown/shared.ts +36 -0
  433. package/src/shared/mock-cover.ts +130 -0
  434. package/src/shared/network-address.ts +106 -0
  435. package/src/shared/redirect.ts +56 -0
  436. package/src/shared/template-rendering.ts +140 -0
  437. package/src/shared/theme.ts +68 -0
  438. package/src/shared/time.ts +13 -0
  439. package/src/ssr/AdminLayout.tsx +53 -0
  440. package/src/ssr/AdminSidebar.tsx +50 -0
  441. package/src/ssr/AppLaunchpad.island.tsx +236 -0
  442. package/src/ssr/AppWorkspaceController.island.tsx +21 -0
  443. package/src/ssr/BrowserNotifications.island.tsx +135 -0
  444. package/src/ssr/GlobalAnnouncements.island.tsx +142 -0
  445. package/src/ssr/GlobalSearchDialog.behavior.test.tsx +96 -0
  446. package/src/ssr/GlobalSearchDialog.tsx +35 -0
  447. package/src/ssr/GlobalSearchHelpDialog.tsx +104 -0
  448. package/src/ssr/GlobalSearchTrigger.island.tsx +55 -0
  449. package/src/ssr/HotkeysHelpRail.island.tsx +55 -0
  450. package/src/ssr/Layout.render.test.tsx +148 -0
  451. package/src/ssr/Layout.tsx +292 -0
  452. package/src/ssr/LayoutBreadcrumbs.island.tsx +44 -0
  453. package/src/ssr/LayoutFooter.tsx +22 -0
  454. package/src/ssr/LayoutHeader.tsx +81 -0
  455. package/src/ssr/LayoutHelp.tsx +869 -0
  456. package/src/ssr/LayoutPreferences.island.tsx +36 -0
  457. package/src/ssr/LayoutRail.tsx +58 -0
  458. package/src/ssr/MinimalLayout.tsx +46 -0
  459. package/src/ssr/PageError.tsx +29 -0
  460. package/src/ssr/ProfilePreferences.island.tsx +92 -0
  461. package/src/ssr/RailApps.island.tsx +61 -0
  462. package/src/ssr/RailEditor.tsx +293 -0
  463. package/src/ssr/RegisteredHelpDocuments.island.tsx +3 -0
  464. package/src/ssr/TimezoneCookie.island.tsx +23 -0
  465. package/src/ssr/admin-active-link.ts +47 -0
  466. package/src/ssr/admin-navigation.ts +78 -0
  467. package/src/ssr/app-appearance.ts +23 -0
  468. package/src/ssr/app-navigation.ts +41 -0
  469. package/src/ssr/help-messages.ts +104 -0
  470. package/src/ssr/index.ts +14 -0
  471. package/src/ssr/islands/SearchBar.island.tsx +80 -0
  472. package/src/ssr/islands/index.ts +16 -0
  473. package/src/ssr/layout-context.ts +17 -0
  474. package/src/ssr/layout-help-markdown.ts +9 -0
  475. package/src/ssr/layout-help-navigation.ts +24 -0
  476. package/src/ssr/layout-help-url.ts +5 -0
  477. package/src/ssr/layout-runtime.ts +22 -0
  478. package/src/ssr/page-error-messages.ts +25 -0
  479. package/src/ssr/platform-messages.ts +135 -0
  480. package/src/ssr/preference-controller.ts +44 -0
  481. package/src/ssr/profile-preferences-messages.ts +29 -0
  482. package/src/ssr/rail-context.ts +21 -0
  483. package/src/ssr/rail-messages.ts +66 -0
  484. package/src/ssr/rail-navigation.ts +60 -0
  485. package/src/ssr/runtime.ts +31 -0
  486. package/src/ssr/url-filter.ts +127 -0
  487. package/src/styles/base-popover.css +36 -0
  488. package/src/styles/effects.css +851 -0
  489. package/src/styles/global.css +268 -0
  490. package/src/styles/input.css +58 -0
  491. package/src/styles/tokens.css +230 -0
  492. package/src/styles/utilities-buttons.css +405 -0
  493. package/src/styles/utilities-data.css +33 -0
  494. package/src/styles/utilities-feedback.css +232 -0
  495. package/src/styles/utilities-layout.css +603 -0
  496. package/src/styles/utilities-markdown-editor.css +525 -0
  497. package/src/styles/utilities-markdown-table.css +234 -0
  498. package/src/styles/utilities-navigation.css +1054 -0
  499. package/src/types/ambient.d.ts +24 -0
  500. package/src/workflows/ai/index.ts +17 -0
  501. package/src/workflows/ai/runtime.ts +286 -0
  502. package/src/workflows/ai/store.ts +314 -0
  503. package/src/workflows/ai/types.ts +111 -0
  504. package/src/workflows/ai-actions.ts +269 -0
  505. package/src/workflows/authoring.ts +88 -0
  506. package/src/workflows/builtins.ts +193 -0
  507. package/src/workflows/contracts.ts +280 -0
  508. package/src/workflows/definition.ts +334 -0
  509. package/src/workflows/editor.ts +113 -0
  510. package/src/workflows/index.ts +71 -0
  511. package/src/workflows/language/binder.ts +45 -0
  512. package/src/workflows/language/canonical.ts +29 -0
  513. package/src/workflows/language/compiler.ts +396 -0
  514. package/src/workflows/language/expressions.ts +34 -0
  515. package/src/workflows/language/index.ts +20 -0
  516. package/src/workflows/language/references.ts +55 -0
  517. package/src/workflows/language/schema.ts +134 -0
  518. package/src/workflows/language/strict-yaml.ts +131 -0
  519. package/src/workflows/manifest.ts +24 -0
  520. package/src/workflows/module.ts +12 -0
  521. package/src/workflows/runtime/coordinator.ts +115 -0
  522. package/src/workflows/runtime/executor.ts +1003 -0
  523. package/src/workflows/runtime/index.ts +60 -0
  524. package/src/workflows/runtime/instant.ts +8 -0
  525. package/src/workflows/runtime/ports.ts +213 -0
  526. package/src/workflows/runtime/schedule.ts +203 -0
  527. package/src/workflows/runtime/trigger.ts +37 -0
  528. package/src/workflows/runtime/wake.ts +13 -0
  529. package/src/workflows/store/actions.ts +438 -0
  530. package/src/workflows/store/budget.ts +145 -0
  531. package/src/workflows/store/definitions.ts +345 -0
  532. package/src/workflows/store/events.ts +383 -0
  533. package/src/workflows/store/index.ts +107 -0
  534. package/src/workflows/store/observability.ts +594 -0
  535. package/src/workflows/store/runs.ts +975 -0
  536. package/src/workflows/store/transaction.ts +23 -0
  537. package/src/workflows/store/worker.ts +439 -0
  538. package/src/workflows/testing/conformance.ts +72 -0
  539. package/src/workflows/testing/index.ts +11 -0
  540. package/src/workflows/testing/process-fixtures.ts +275 -0
@@ -0,0 +1,594 @@
1
+ /**
2
+ * Reading runs, for operators.
3
+ *
4
+ * Once the kernel owns runs, "is any workflow broken right now" is one query
5
+ * across every app rather than an aggregation protocol between them. The
6
+ * previous arrangement needed a per-app health port purely because storage was
7
+ * split; there is nothing left for that port to do.
8
+ *
9
+ * The four findings below are the ones that stayed invisible: an effect that
10
+ * escaped and was never settled, a run waiting on a human, an event that
11
+ * matched nothing and said nothing, and a run that started long after the
12
+ * occurrence that caused it.
13
+ */
14
+ import { type SQL, sql } from "bun";
15
+ import type { WorkflowInvocationMode, WorkflowJsonValue, WorkflowRunState } from "../contracts";
16
+ import type { WorkflowEffectBudget } from "./budget";
17
+
18
+ export type WorkflowRunSummary = {
19
+ id: string;
20
+ appId: string;
21
+ scopeId: string;
22
+ workflowId: string;
23
+ workflowName: string;
24
+ revision: number;
25
+ mode: WorkflowInvocationMode;
26
+ state: WorkflowRunState;
27
+ attempt: number;
28
+ eventType: string | null;
29
+ parentRunId: string | null;
30
+ occurredAt: Date;
31
+ createdAt: Date;
32
+ startedAt: Date | null;
33
+ finishedAt: Date | null;
34
+ /** Milliseconds between the occurrence and the run starting. The drift an operator hunts. */
35
+ startLagMs: number | null;
36
+ durationMs: number | null;
37
+ error: WorkflowJsonValue | null;
38
+ /** What the run said about itself, distinct from what it produced. */
39
+ resultMessage: string | null;
40
+ };
41
+
42
+ export type WorkflowRunFilter = {
43
+ appId?: string;
44
+ scopeId?: string;
45
+ workflowId?: string;
46
+ /** Restrict the list to the direct children of one fan-out parent. */
47
+ parentRunId?: string;
48
+ state?: WorkflowRunState;
49
+ mode?: WorkflowInvocationMode;
50
+ /** Children are noise in a list view until you are looking at their parent. */
51
+ includeChildren?: boolean;
52
+ since?: Date;
53
+ limit?: number;
54
+ offset?: number;
55
+ };
56
+
57
+ type RunRow = {
58
+ id: string;
59
+ app_id: string;
60
+ scope_id: string;
61
+ workflow_id: string;
62
+ workflow_name: string;
63
+ revision: number;
64
+ mode: WorkflowInvocationMode;
65
+ state: WorkflowRunState;
66
+ attempt: number;
67
+ event_type: string | null;
68
+ parent_run_id: string | null;
69
+ occurred_at: Date;
70
+ created_at: Date;
71
+ started_at: Date | null;
72
+ finished_at: Date | null;
73
+ error: WorkflowJsonValue | null;
74
+ result_message: string | null;
75
+ };
76
+
77
+ const millisBetween = (from: Date | null, to: Date | null): number | null =>
78
+ from && to ? Math.max(0, to.getTime() - from.getTime()) : null;
79
+
80
+ const toSummary = (row: RunRow): WorkflowRunSummary => ({
81
+ id: row.id,
82
+ appId: row.app_id,
83
+ scopeId: row.scope_id,
84
+ workflowId: row.workflow_id,
85
+ workflowName: row.workflow_name,
86
+ revision: row.revision,
87
+ mode: row.mode,
88
+ state: row.state,
89
+ attempt: row.attempt,
90
+ eventType: row.event_type,
91
+ parentRunId: row.parent_run_id,
92
+ occurredAt: row.occurred_at,
93
+ createdAt: row.created_at,
94
+ startedAt: row.started_at,
95
+ finishedAt: row.finished_at,
96
+ startLagMs: millisBetween(row.occurred_at, row.started_at),
97
+ durationMs: millisBetween(row.started_at, row.finished_at),
98
+ error: row.error,
99
+ resultMessage: row.result_message,
100
+ });
101
+
102
+ const RUN_SELECT = `
103
+ r.id, r.app_id, r.scope_id, r.workflow_id, w.name AS workflow_name, v.revision,
104
+ r.mode, r.state, r.attempt, e.type AS event_type, r.parent_run_id,
105
+ r.occurred_at, r.created_at, r.started_at, r.finished_at, r.error, r.result_message
106
+ `;
107
+
108
+ const RUN_FROM = `
109
+ FROM workflows.run AS r
110
+ JOIN workflows.workflow AS w ON w.id = r.workflow_id
111
+ JOIN workflows.version AS v ON v.id = r.workflow_version_id
112
+ LEFT JOIN workflows.event AS e ON e.id = r.event_id
113
+ `;
114
+
115
+ export const listWorkflowRuns = async (filter: WorkflowRunFilter = {}, options: { db?: SQL } = {}): Promise<WorkflowRunSummary[]> => {
116
+ const db = options.db ?? sql;
117
+ const rows = await db<RunRow[]>`
118
+ SELECT ${db.unsafe(RUN_SELECT)} ${db.unsafe(RUN_FROM)}
119
+ WHERE (${filter.appId ?? null}::text IS NULL OR r.app_id = ${filter.appId ?? null})
120
+ AND (${filter.scopeId ?? null}::text IS NULL OR r.scope_id = ${filter.scopeId ?? null})
121
+ AND (${filter.workflowId ?? null}::uuid IS NULL OR r.workflow_id = ${filter.workflowId ?? null}::uuid)
122
+ AND (${filter.parentRunId ?? null}::uuid IS NULL OR r.parent_run_id = ${filter.parentRunId ?? null}::uuid)
123
+ AND (${filter.state ?? null}::text IS NULL OR r.state = ${filter.state ?? null})
124
+ AND (${filter.mode ?? null}::text IS NULL OR r.mode = ${filter.mode ?? null})
125
+ AND (${filter.parentRunId !== undefined || filter.includeChildren === true} OR r.parent_run_id IS NULL)
126
+ AND (${filter.since ?? null}::timestamptz IS NULL OR r.created_at >= ${filter.since ?? null}::timestamptz)
127
+ ORDER BY r.created_at DESC, r.id DESC
128
+ LIMIT ${filter.limit ?? 50} OFFSET ${filter.offset ?? 0}
129
+ `;
130
+ return rows.map(toSummary);
131
+ };
132
+
133
+ export type WorkflowFamilySummary = {
134
+ workflowId: string;
135
+ appId: string;
136
+ scopeId: string;
137
+ workflowName: string;
138
+ eventTypes: string[];
139
+ latestRunId: string;
140
+ latestRevision: number;
141
+ latestState: WorkflowRunState;
142
+ latestRunAt: Date;
143
+ runs: number;
144
+ failed: number;
145
+ active: number;
146
+ needsAttention: number;
147
+ avgDurationMs: number | null;
148
+ p99DurationMs: number | null;
149
+ oldestQueuedAt: Date | null;
150
+ };
151
+
152
+ export type WorkflowFamilyFilter = Pick<WorkflowRunFilter, "appId" | "workflowId" | "state" | "mode" | "since" | "limit" | "offset">;
153
+
154
+ type WorkflowFamilyRow = {
155
+ workflow_id: string;
156
+ app_id: string;
157
+ scope_id: string;
158
+ workflow_name: string;
159
+ event_types: string[];
160
+ latest_run_id: string;
161
+ latest_revision: number;
162
+ latest_state: WorkflowRunState;
163
+ latest_run_at: Date;
164
+ runs: number | string;
165
+ failed: number | string;
166
+ active: number | string;
167
+ needs_attention: number | string;
168
+ avg_duration_ms: number | string | null;
169
+ p99_duration_ms: number | string | null;
170
+ oldest_queued_at: Date | null;
171
+ };
172
+
173
+ const numberOrNull = (value: number | string | null): number | null => (value === null ? null : Number(value));
174
+
175
+ /**
176
+ * One row per workflow definition, analogous to the job-family overview.
177
+ *
178
+ * The filter is applied before aggregation, so every number describes the
179
+ * selected URL scope. Individual runs remain a separate drill-down.
180
+ */
181
+ export const listWorkflowFamilies = async (
182
+ filter: WorkflowFamilyFilter = {},
183
+ options: { db?: SQL } = {},
184
+ ): Promise<WorkflowFamilySummary[]> => {
185
+ const db = options.db ?? sql;
186
+ const rows = await db<WorkflowFamilyRow[]>`
187
+ WITH filtered AS (
188
+ SELECT
189
+ r.id, r.app_id, r.scope_id, r.workflow_id, w.name AS workflow_name,
190
+ v.revision, r.state, r.created_at, r.started_at, r.finished_at,
191
+ e.type AS event_type,
192
+ EXTRACT(EPOCH FROM (r.finished_at - r.started_at)) * 1000 AS duration_ms
193
+ ${db.unsafe(RUN_FROM)}
194
+ WHERE (${filter.appId ?? null}::text IS NULL OR r.app_id = ${filter.appId ?? null})
195
+ AND (${filter.workflowId ?? null}::uuid IS NULL OR r.workflow_id = ${filter.workflowId ?? null}::uuid)
196
+ AND (${filter.state ?? null}::text IS NULL OR r.state = ${filter.state ?? null})
197
+ AND (${filter.mode ?? null}::text IS NULL OR r.mode = ${filter.mode ?? null})
198
+ AND r.parent_run_id IS NULL
199
+ AND (${filter.since ?? null}::timestamptz IS NULL OR r.created_at >= ${filter.since ?? null}::timestamptz)
200
+ ),
201
+ latest AS (
202
+ SELECT DISTINCT ON (workflow_id)
203
+ workflow_id, id AS latest_run_id, revision AS latest_revision,
204
+ state AS latest_state, created_at AS latest_run_at
205
+ FROM filtered
206
+ ORDER BY workflow_id, created_at DESC, id DESC
207
+ ),
208
+ families AS (
209
+ SELECT
210
+ workflow_id, app_id, scope_id, workflow_name,
211
+ COALESCE(array_agg(DISTINCT event_type) FILTER (WHERE event_type IS NOT NULL), ARRAY[]::text[]) AS event_types,
212
+ COUNT(*)::int AS runs,
213
+ COUNT(*) FILTER (WHERE state = 'failed')::int AS failed,
214
+ COUNT(*) FILTER (WHERE state IN ('queued', 'running', 'waiting'))::int AS active,
215
+ COUNT(*) FILTER (WHERE state = 'needs_attention')::int AS needs_attention,
216
+ AVG(duration_ms)::float AS avg_duration_ms,
217
+ (percentile_cont(0.99) WITHIN GROUP (ORDER BY duration_ms)
218
+ FILTER (WHERE duration_ms IS NOT NULL))::float AS p99_duration_ms,
219
+ MIN(created_at) FILTER (WHERE state = 'queued') AS oldest_queued_at
220
+ FROM filtered
221
+ GROUP BY workflow_id, app_id, scope_id, workflow_name
222
+ )
223
+ SELECT families.*, latest.latest_run_id, latest.latest_revision, latest.latest_state, latest.latest_run_at
224
+ FROM families
225
+ JOIN latest USING (workflow_id)
226
+ ORDER BY
227
+ CASE latest.latest_state
228
+ WHEN 'needs_attention' THEN 0
229
+ WHEN 'failed' THEN 1
230
+ WHEN 'running' THEN 2
231
+ WHEN 'waiting' THEN 2
232
+ WHEN 'queued' THEN 2
233
+ ELSE 3
234
+ END,
235
+ latest.latest_run_at DESC,
236
+ families.workflow_name,
237
+ families.workflow_id
238
+ LIMIT ${filter.limit ?? 50} OFFSET ${filter.offset ?? 0}
239
+ `;
240
+
241
+ return rows.map((row) => ({
242
+ workflowId: row.workflow_id,
243
+ appId: row.app_id,
244
+ scopeId: row.scope_id,
245
+ workflowName: row.workflow_name,
246
+ eventTypes: [...row.event_types].sort(),
247
+ latestRunId: row.latest_run_id,
248
+ latestRevision: row.latest_revision,
249
+ latestState: row.latest_state,
250
+ latestRunAt: row.latest_run_at,
251
+ runs: Number(row.runs),
252
+ failed: Number(row.failed),
253
+ active: Number(row.active),
254
+ needsAttention: Number(row.needs_attention),
255
+ avgDurationMs: numberOrNull(row.avg_duration_ms),
256
+ p99DurationMs: numberOrNull(row.p99_duration_ms),
257
+ oldestQueuedAt: row.oldest_queued_at,
258
+ }));
259
+ };
260
+
261
+ export type WorkflowRunTimeline = {
262
+ runs: WorkflowRunSummary[];
263
+ total: number;
264
+ };
265
+
266
+ /**
267
+ * A bounded set of individual runs for charts, plus the uncapped total so the
268
+ * UI can say when the chart is a sample rather than the complete window.
269
+ */
270
+ export const listWorkflowRunTimeline = async (
271
+ filter: Omit<WorkflowRunFilter, "limit" | "offset"> = {},
272
+ options: { db?: SQL; limit?: number } = {},
273
+ ): Promise<WorkflowRunTimeline> => {
274
+ const db = options.db ?? sql;
275
+ const limit = Math.min(5_000, Math.max(1, Math.trunc(options.limit ?? 2_000)));
276
+ const [runs, countRows] = await Promise.all([
277
+ listWorkflowRuns({ ...filter, limit, offset: 0 }, { db }),
278
+ db<{ count: number | string }[]>`
279
+ SELECT COUNT(*)::int AS count FROM workflows.run AS r
280
+ WHERE (${filter.appId ?? null}::text IS NULL OR r.app_id = ${filter.appId ?? null})
281
+ AND (${filter.scopeId ?? null}::text IS NULL OR r.scope_id = ${filter.scopeId ?? null})
282
+ AND (${filter.workflowId ?? null}::uuid IS NULL OR r.workflow_id = ${filter.workflowId ?? null}::uuid)
283
+ AND (${filter.parentRunId ?? null}::uuid IS NULL OR r.parent_run_id = ${filter.parentRunId ?? null}::uuid)
284
+ AND (${filter.state ?? null}::text IS NULL OR r.state = ${filter.state ?? null})
285
+ AND (${filter.mode ?? null}::text IS NULL OR r.mode = ${filter.mode ?? null})
286
+ AND (${filter.parentRunId !== undefined || filter.includeChildren === true} OR r.parent_run_id IS NULL)
287
+ AND (${filter.since ?? null}::timestamptz IS NULL OR r.created_at >= ${filter.since ?? null}::timestamptz)
288
+ `,
289
+ ]);
290
+ return { runs, total: Number(countRows[0]?.count ?? 0) };
291
+ };
292
+
293
+ export type WorkflowStepSummary = {
294
+ stepKey: string;
295
+ /** Where the step sits in the written source, so a view can point at it. */
296
+ sourcePath: WorkflowJsonValue;
297
+ /** Which iteration of an enclosing loop this was. Empty outside one. */
298
+ iterationPath: WorkflowJsonValue;
299
+ kind: string;
300
+ action: string | null;
301
+ state: string;
302
+ attempt: number;
303
+ /** What the step reported — the output a later step referenced, or why it stopped. */
304
+ outcome: WorkflowJsonValue | null;
305
+ effectKey: string | null;
306
+ effectState: string | null;
307
+ effectStartedAt: Date | null;
308
+ dependency: WorkflowJsonValue | null;
309
+ startedAt: Date;
310
+ finishedAt: Date | null;
311
+ durationMs: number | null;
312
+ };
313
+
314
+ /** One step's row, shaped for a run view. Shared by the detail read and the live one. */
315
+ type StepRow = {
316
+ step_key: string;
317
+ source_path: WorkflowJsonValue;
318
+ iteration_path: WorkflowJsonValue;
319
+ kind: string;
320
+ action: string | null;
321
+ state: string;
322
+ attempt: number;
323
+ outcome: WorkflowJsonValue | null;
324
+ effect_key: string | null;
325
+ effect_state: string | null;
326
+ effect_started_at: Date | null;
327
+ dependency: WorkflowJsonValue | null;
328
+ started_at: Date;
329
+ finished_at: Date | null;
330
+ };
331
+
332
+ const STEP_SELECT = `
333
+ step_key, source_path, iteration_path, kind, action, state, attempt, outcome,
334
+ effect_key, effect_state, effect_started_at, dependency, started_at, finished_at
335
+ `;
336
+
337
+ const toStepSummary = (step: StepRow): WorkflowStepSummary => ({
338
+ stepKey: step.step_key,
339
+ sourcePath: step.source_path,
340
+ iterationPath: step.iteration_path,
341
+ kind: step.kind,
342
+ action: step.action,
343
+ state: step.state,
344
+ attempt: step.attempt,
345
+ outcome: step.outcome,
346
+ effectKey: step.effect_key,
347
+ effectState: step.effect_state,
348
+ effectStartedAt: step.effect_started_at,
349
+ dependency: step.dependency,
350
+ startedAt: step.started_at,
351
+ finishedAt: step.finished_at,
352
+ durationMs: millisBetween(step.started_at, step.finished_at),
353
+ });
354
+
355
+ /**
356
+ * The steps of one run, or just the ones named.
357
+ *
358
+ * Naming them is what a live update needs: a step finished, and the stream
359
+ * carries that step rather than re-reading a plan that may have thousands.
360
+ */
361
+ export const listWorkflowRunSteps = async (
362
+ runId: string,
363
+ options: { stepKeys?: readonly string[]; db?: SQL } = {},
364
+ ): Promise<WorkflowStepSummary[]> => {
365
+ const db = options.db ?? sql;
366
+ const keys = options.stepKeys;
367
+ if (keys && keys.length === 0) return [];
368
+ const rows = keys
369
+ ? await db<StepRow[]>`
370
+ SELECT ${db.unsafe(STEP_SELECT)} FROM workflows.step_outcome
371
+ WHERE run_id = ${runId}::uuid AND step_key IN ${db(keys)}
372
+ ORDER BY started_at, step_key
373
+ `
374
+ : await db<StepRow[]>`
375
+ SELECT ${db.unsafe(STEP_SELECT)} FROM workflows.step_outcome
376
+ WHERE run_id = ${runId}::uuid
377
+ ORDER BY started_at, step_key
378
+ `;
379
+ return rows.map(toStepSummary);
380
+ };
381
+
382
+ export type WorkflowRunDetail = WorkflowRunSummary & {
383
+ inputs: Record<string, WorkflowJsonValue>;
384
+ result: WorkflowJsonValue | null;
385
+ effectsUsed: WorkflowEffectBudget;
386
+ effectBudget: WorkflowEffectBudget;
387
+ source: string;
388
+ eventId: string | null;
389
+ eventData: Record<string, WorkflowJsonValue> | null;
390
+ steps: WorkflowStepSummary[];
391
+ children: Record<WorkflowRunState, number>;
392
+ };
393
+
394
+ const EMPTY_CHILD_COUNTS: Record<WorkflowRunState, number> = {
395
+ queued: 0,
396
+ running: 0,
397
+ waiting: 0,
398
+ succeeded: 0,
399
+ failed: 0,
400
+ canceled: 0,
401
+ needs_attention: 0,
402
+ };
403
+
404
+ export const getWorkflowRun = async (runId: string, options: { db?: SQL } = {}): Promise<WorkflowRunDetail | null> => {
405
+ const db = options.db ?? sql;
406
+ const [row] = await db<
407
+ (RunRow & {
408
+ inputs: Record<string, WorkflowJsonValue>;
409
+ result: WorkflowJsonValue | null;
410
+ effects_used: WorkflowEffectBudget;
411
+ effect_budget: WorkflowEffectBudget;
412
+ source: string;
413
+ event_id: string | null;
414
+ event_data: Record<string, WorkflowJsonValue> | null;
415
+ })[]
416
+ >`
417
+ SELECT ${db.unsafe(RUN_SELECT)},
418
+ r.inputs, r.result, r.effects_used, v.effect_budget, v.source, r.event_id, e.data AS event_data
419
+ ${db.unsafe(RUN_FROM)}
420
+ WHERE r.id = ${runId}::uuid
421
+ `;
422
+ if (!row) return null;
423
+
424
+ const steps = await listWorkflowRunSteps(runId, { db });
425
+
426
+ const childCounts = await db<{ state: WorkflowRunState; count: string }[]>`
427
+ SELECT state, count(*) AS count FROM workflows.run WHERE parent_run_id = ${runId}::uuid GROUP BY state
428
+ `;
429
+ const children = { ...EMPTY_CHILD_COUNTS };
430
+ for (const child of childCounts) children[child.state] = Number(child.count);
431
+
432
+ return {
433
+ ...toSummary(row),
434
+ inputs: row.inputs,
435
+ result: row.result,
436
+ effectsUsed: row.effects_used,
437
+ effectBudget: row.effect_budget,
438
+ source: row.source,
439
+ eventId: row.event_id,
440
+ eventData: row.event_data,
441
+ children,
442
+ steps,
443
+ };
444
+ };
445
+
446
+ export type StrandedWorkflowEffect = {
447
+ runId: string;
448
+ appId: string;
449
+ workflowName: string;
450
+ stepKey: string;
451
+ action: string | null;
452
+ effectKey: string;
453
+ effectState: string;
454
+ effectStartedAt: Date;
455
+ ageMs: number;
456
+ };
457
+
458
+ /**
459
+ * Effects that left the process and never reported back.
460
+ *
461
+ * These are the ones a replay refuses to repeat, so each is a message that may
462
+ * or may not have gone out and a run that cannot proceed on its own. Nothing
463
+ * surfaced them before.
464
+ */
465
+ export const listStrandedWorkflowEffects = async (
466
+ options: { appId?: string; olderThanMs?: number; limit?: number; offset?: number; db?: SQL } = {},
467
+ ): Promise<StrandedWorkflowEffect[]> => {
468
+ const db = options.db ?? sql;
469
+ const rows = await db<
470
+ {
471
+ run_id: string;
472
+ app_id: string;
473
+ workflow_name: string;
474
+ step_key: string;
475
+ action: string | null;
476
+ effect_key: string;
477
+ effect_state: string;
478
+ effect_started_at: Date;
479
+ }[]
480
+ >`
481
+ SELECT s.run_id, r.app_id, w.name AS workflow_name, s.step_key, s.action, s.effect_key, s.effect_state, s.effect_started_at
482
+ FROM workflows.step_outcome AS s
483
+ JOIN workflows.run AS r ON r.id = s.run_id
484
+ JOIN workflows.workflow AS w ON w.id = r.workflow_id
485
+ WHERE s.effect_state IN ('executing', 'ambiguous')
486
+ AND s.effect_started_at < now() - ${`${options.olderThanMs ?? 0} milliseconds`}::interval
487
+ AND (${options.appId ?? null}::text IS NULL OR r.app_id = ${options.appId ?? null})
488
+ ORDER BY s.effect_started_at, s.run_id
489
+ LIMIT ${options.limit ?? 100} OFFSET ${options.offset ?? 0}
490
+ `;
491
+ const now = Date.now();
492
+ return rows.map((row) => ({
493
+ runId: row.run_id,
494
+ appId: row.app_id,
495
+ workflowName: row.workflow_name,
496
+ stepKey: row.step_key,
497
+ action: row.action,
498
+ effectKey: row.effect_key,
499
+ effectState: row.effect_state,
500
+ effectStartedAt: row.effect_started_at,
501
+ ageMs: Math.max(0, now - row.effect_started_at.getTime()),
502
+ }));
503
+ };
504
+
505
+ export type WorkflowAppHealth = {
506
+ appId: string;
507
+ runs: Record<WorkflowRunState, number>;
508
+ /** Effects that escaped and never settled — each one a possible duplicate. */
509
+ strandedEffects: number;
510
+ /** Events that matched nothing or could not be dispatched. */
511
+ undispatchedEvents: number;
512
+ /** How far behind the oldest undispatched event is, in milliseconds. */
513
+ oldestUndispatchedMs: number | null;
514
+ /** Worst gap between an occurrence and the run for it actually starting. */
515
+ worstStartLagMs: number | null;
516
+ /** How long the oldest run that never started has been queued. */
517
+ oldestQueuedMs: number | null;
518
+ };
519
+
520
+ /**
521
+ * One row per app, answering "is anything broken right now".
522
+ *
523
+ * Deliberately a single query per concern rather than a per-app port: the
524
+ * point of moving storage into the kernel is that this stops being a protocol.
525
+ */
526
+ export const workflowHealth = async (options: { since?: Date; db?: SQL } = {}): Promise<WorkflowAppHealth[]> => {
527
+ const db = options.db ?? sql;
528
+ const since = options.since ?? new Date(Date.now() - 24 * 60 * 60 * 1000);
529
+
530
+ const [runs, stranded, events] = await Promise.all([
531
+ db<
532
+ {
533
+ app_id: string;
534
+ state: WorkflowRunState;
535
+ count: string;
536
+ worst_lag_ms: number | null;
537
+ oldest_queued_ms: number | null;
538
+ }[]
539
+ >`
540
+ SELECT app_id, state, count(*) AS count,
541
+ max(EXTRACT(EPOCH FROM (started_at - occurred_at)) * 1000)::bigint AS worst_lag_ms,
542
+ (max(EXTRACT(EPOCH FROM (now() - created_at)) * 1000)
543
+ FILTER (WHERE state = 'queued'))::bigint AS oldest_queued_ms
544
+ FROM workflows.run WHERE created_at >= ${since} GROUP BY app_id, state
545
+ `,
546
+ db<{ app_id: string; count: string }[]>`
547
+ SELECT r.app_id, count(*) AS count FROM workflows.step_outcome AS s
548
+ JOIN workflows.run AS r ON r.id = s.run_id
549
+ WHERE s.effect_state IN ('executing', 'ambiguous') GROUP BY r.app_id
550
+ `,
551
+ db<{ app_id: string; count: string; oldest: Date }[]>`
552
+ SELECT app_id, count(*) AS count, min(occurred_at) AS oldest
553
+ FROM workflows.event
554
+ WHERE matched_count = 0 OR dispatched_at IS NULL
555
+ GROUP BY app_id
556
+ `,
557
+ ]);
558
+
559
+ const byApp = new Map<string, WorkflowAppHealth>();
560
+ const health = (appId: string): WorkflowAppHealth => {
561
+ let entry = byApp.get(appId);
562
+ if (!entry) {
563
+ entry = {
564
+ appId,
565
+ runs: { ...EMPTY_CHILD_COUNTS },
566
+ strandedEffects: 0,
567
+ undispatchedEvents: 0,
568
+ oldestUndispatchedMs: null,
569
+ worstStartLagMs: null,
570
+ oldestQueuedMs: null,
571
+ };
572
+ byApp.set(appId, entry);
573
+ }
574
+ return entry;
575
+ };
576
+
577
+ const now = Date.now();
578
+ for (const row of runs) {
579
+ const entry = health(row.app_id);
580
+ entry.runs[row.state] = Number(row.count);
581
+ const lag = row.worst_lag_ms === null ? null : Number(row.worst_lag_ms);
582
+ if (lag !== null) entry.worstStartLagMs = Math.max(entry.worstStartLagMs ?? 0, lag);
583
+ const queued = row.oldest_queued_ms === null ? null : Number(row.oldest_queued_ms);
584
+ if (queued !== null) entry.oldestQueuedMs = Math.max(entry.oldestQueuedMs ?? 0, queued);
585
+ }
586
+ for (const row of stranded) health(row.app_id).strandedEffects = Number(row.count);
587
+ for (const row of events) {
588
+ const entry = health(row.app_id);
589
+ entry.undispatchedEvents = Number(row.count);
590
+ entry.oldestUndispatchedMs = Math.max(0, now - row.oldest.getTime());
591
+ }
592
+
593
+ return [...byApp.values()].sort((left, right) => left.appId.localeCompare(right.appId));
594
+ };