@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,184 @@
1
+ import { err, fail, ok, type Result } from "@k2b/stdlib";
2
+ import { sql } from "bun";
3
+ import { isUniqueViolation } from "./postgres";
4
+
5
+ export type ServiceAccountKind = "user_delegated" | "resource_bound";
6
+ export type ServiceAccountStatus = "active" | "disabled";
7
+
8
+ export type ServiceAccount = {
9
+ id: string;
10
+ name: string;
11
+ kind: ServiceAccountKind;
12
+ status: ServiceAccountStatus;
13
+ delegatedUserId: string | null;
14
+ appId: string | null;
15
+ resourceType: string | null;
16
+ resourceId: string | null;
17
+ createdBy: string | null;
18
+ createdAt: string;
19
+ };
20
+
21
+ type DbServiceAccount = {
22
+ id: string;
23
+ name: string;
24
+ kind: ServiceAccountKind;
25
+ status: ServiceAccountStatus;
26
+ delegated_user_id: string | null;
27
+ app_id: string | null;
28
+ resource_type: string | null;
29
+ resource_id: string | null;
30
+ created_by: string | null;
31
+ created_at: Date;
32
+ };
33
+
34
+ const mapServiceAccount = (row: DbServiceAccount): ServiceAccount => ({
35
+ id: row.id,
36
+ name: row.name,
37
+ kind: row.kind,
38
+ status: row.status,
39
+ delegatedUserId: row.delegated_user_id,
40
+ appId: row.app_id,
41
+ resourceType: row.resource_type,
42
+ resourceId: row.resource_id,
43
+ createdBy: row.created_by,
44
+ createdAt: row.created_at.toISOString(),
45
+ });
46
+
47
+ const trimRequired = (value: string): string => value.trim();
48
+
49
+ const isForeignKeyViolation = (error: unknown): boolean => (error as { code?: string } | null)?.code === "23503";
50
+ const RESOURCE_BOUND_UNIQUE_CONSTRAINT = "uniq_service_accounts_resource_bound";
51
+
52
+ export const getByResource = async (params: {
53
+ appId: string;
54
+ resourceType: string;
55
+ resourceId: string;
56
+ }): Promise<ServiceAccount | null> => {
57
+ const [row] = await sql<DbServiceAccount[]>`
58
+ SELECT id, name, kind, status, delegated_user_id, app_id, resource_type, resource_id, created_by, created_at
59
+ FROM auth.service_accounts
60
+ WHERE kind = 'resource_bound'
61
+ AND app_id = ${params.appId}
62
+ AND resource_type = ${params.resourceType}
63
+ AND resource_id = ${params.resourceId}
64
+ ORDER BY created_at ASC
65
+ LIMIT 1
66
+ `;
67
+ return row ? mapServiceAccount(row) : null;
68
+ };
69
+
70
+ export const get = async (params: { id: string }): Promise<ServiceAccount | null> => {
71
+ const [row] = await sql<DbServiceAccount[]>`
72
+ SELECT id, name, kind, status, delegated_user_id, app_id, resource_type, resource_id, created_by, created_at
73
+ FROM auth.service_accounts
74
+ WHERE id = ${params.id}::uuid
75
+ `;
76
+ return row ? mapServiceAccount(row) : null;
77
+ };
78
+
79
+ export const createUserDelegated = async (params: {
80
+ name: string;
81
+ delegatedUserId: string;
82
+ createdBy?: string | null;
83
+ }): Promise<Result<ServiceAccount>> => {
84
+ const name = trimRequired(params.name);
85
+ if (!name) return fail(err.badInput("Service account name is required"));
86
+
87
+ try {
88
+ const [row] = await sql<DbServiceAccount[]>`
89
+ INSERT INTO auth.service_accounts (name, kind, delegated_user_id, created_by)
90
+ VALUES (${name}, 'user_delegated', ${params.delegatedUserId}::uuid, ${params.createdBy ?? null}::uuid)
91
+ RETURNING id, name, kind, status, delegated_user_id, app_id, resource_type, resource_id, created_by, created_at
92
+ `;
93
+ return row ? ok(mapServiceAccount(row)) : fail(err.internal("Failed to create service account"));
94
+ } catch (error) {
95
+ if (isForeignKeyViolation(error)) return fail(err.notFound("Delegated user"));
96
+ throw error;
97
+ }
98
+ };
99
+
100
+ export const createResourceBound = async (params: {
101
+ name: string;
102
+ appId: string;
103
+ resourceType: string;
104
+ resourceId: string;
105
+ createdBy?: string | null;
106
+ }): Promise<Result<ServiceAccount>> => {
107
+ const name = trimRequired(params.name);
108
+ const appId = trimRequired(params.appId);
109
+ const resourceType = trimRequired(params.resourceType);
110
+ const resourceId = trimRequired(params.resourceId);
111
+ if (!name) return fail(err.badInput("Service account name is required"));
112
+ if (!appId || !resourceType || !resourceId) return fail(err.badInput("Resource binding is required"));
113
+
114
+ try {
115
+ const [row] = await sql<DbServiceAccount[]>`
116
+ INSERT INTO auth.service_accounts (name, kind, app_id, resource_type, resource_id, created_by)
117
+ VALUES (${name}, 'resource_bound', ${appId}, ${resourceType}, ${resourceId}, ${params.createdBy ?? null}::uuid)
118
+ RETURNING id, name, kind, status, delegated_user_id, app_id, resource_type, resource_id, created_by, created_at
119
+ `;
120
+ return row ? ok(mapServiceAccount(row)) : fail(err.internal("Failed to create service account"));
121
+ } catch (error) {
122
+ if (isForeignKeyViolation(error)) return fail(err.notFound("Creator"));
123
+ if (isUniqueViolation(error, RESOURCE_BOUND_UNIQUE_CONSTRAINT)) return fail(err.conflict("Resource service account"));
124
+ throw error;
125
+ }
126
+ };
127
+
128
+ export const getOrCreateResourceBound = async (params: {
129
+ name: string;
130
+ appId: string;
131
+ resourceType: string;
132
+ resourceId: string;
133
+ createdBy?: string | null;
134
+ }): Promise<Result<ServiceAccount>> => {
135
+ const existing = await getByResource(params);
136
+ if (existing) return ok(existing);
137
+
138
+ const created = await createResourceBound(params);
139
+ if (created.ok || created.error.code !== "CONFLICT") return created;
140
+
141
+ const raced = await getByResource(params);
142
+ return raced ? ok(raced) : fail(err.internal("Failed to load resource service account"));
143
+ };
144
+
145
+ export const setStatus = async (params: { id: string; status: ServiceAccountStatus }): Promise<Result<void>> => {
146
+ const result = await sql`
147
+ UPDATE auth.service_accounts
148
+ SET status = ${params.status}
149
+ WHERE id = ${params.id}::uuid
150
+ `;
151
+ if (result.count === 0) return fail(err.notFound("Service account"));
152
+ return ok();
153
+ };
154
+
155
+ export const delete_ = async (params: { id: string }): Promise<Result<void>> => {
156
+ const result = await sql`
157
+ DELETE FROM auth.service_accounts
158
+ WHERE id = ${params.id}::uuid
159
+ `;
160
+ if (result.count === 0) return fail(err.notFound("Service account"));
161
+ return ok();
162
+ };
163
+
164
+ export const deleteForResource = async (params: { appId: string; resourceType: string; resourceId: string }): Promise<number> => {
165
+ const result = await sql`
166
+ DELETE FROM auth.service_accounts
167
+ WHERE kind = 'resource_bound'
168
+ AND app_id = ${params.appId}
169
+ AND resource_type = ${params.resourceType}
170
+ AND resource_id = ${params.resourceId}
171
+ `;
172
+ return result.count;
173
+ };
174
+
175
+ export const serviceAccounts = {
176
+ get,
177
+ createUserDelegated,
178
+ createResourceBound,
179
+ getByResource,
180
+ getOrCreateResourceBound,
181
+ setStatus,
182
+ delete: delete_,
183
+ deleteForResource,
184
+ };
@@ -0,0 +1,236 @@
1
+ import { sql } from "bun";
2
+ import type { Context } from "hono";
3
+ import { deleteCookie, getCookie, setCookie } from "hono/cookie";
4
+ import { HTTPException } from "hono/http-exception";
5
+ import { env } from "../../config/env";
6
+ import type { User } from "../../contracts/shared";
7
+ import { isAccountCategoryAllowed } from "../account-category-policy";
8
+ import { isAccountExpired } from "../account-model";
9
+ import { IDENTITY_CLOCK_TOLERANCE_SECONDS, IDENTITY_ROLLOUT_MARGIN_MS } from "../identity/constants";
10
+ import { invalidateIdentitySignerCache, prepareIdentitySigner } from "../identity/key-ring";
11
+ import { getIdentityRuntimeConfig } from "../identity/runtime-config";
12
+ import { type CloudSessionClaims, isSessionJwtCandidate, signSessionToken, verifySessionToken } from "../identity/session-token";
13
+ import * as settings from "../settings";
14
+ import { loadJwtSessionUser } from "./user";
15
+
16
+ export type SessionData = {
17
+ userId: string;
18
+ sid: string;
19
+ authEpoch: number;
20
+ expiresAt: string;
21
+ };
22
+
23
+ export type AuthenticatedSession = { data: SessionData; user: User };
24
+
25
+ const parseBearer = (header: string | undefined): string | null => {
26
+ if (!header) return null;
27
+ const match = header.match(/^Bearer\s+(\S+)$/i);
28
+ return match?.[1] ?? null;
29
+ };
30
+
31
+ const isCloudApiToken = (token: string | null): boolean => Boolean(token?.startsWith("cld_"));
32
+
33
+ const verifyCredential = async (token: string): Promise<CloudSessionClaims | null> => {
34
+ if (!isSessionJwtCandidate(token)) return null;
35
+ return verifySessionToken(token);
36
+ };
37
+
38
+ const verificationByRequest = new WeakMap<Context, Map<string, Promise<CloudSessionClaims | null>>>();
39
+ const authenticationByRequest = new WeakMap<Context, Map<string, Promise<AuthenticatedSession | null>>>();
40
+
41
+ const requestCached = <T>(
42
+ cache: WeakMap<Context, Map<string, Promise<T>>>,
43
+ c: Context,
44
+ token: string,
45
+ load: () => Promise<T>,
46
+ ): Promise<T> => {
47
+ let requests = cache.get(c);
48
+ if (!requests) {
49
+ requests = new Map();
50
+ cache.set(c, requests);
51
+ }
52
+ const existing = requests.get(token);
53
+ if (existing) return existing;
54
+ const pending = load();
55
+ requests.set(token, pending);
56
+ return pending;
57
+ };
58
+
59
+ const verifyForRequest = (c: Context, token: string): Promise<CloudSessionClaims | null> =>
60
+ requestCached(verificationByRequest, c, token, () => verifyCredential(token));
61
+
62
+ const authenticateVerified = async (credential: CloudSessionClaims): Promise<AuthenticatedSession | null> => {
63
+ const { groupsAdmin } = await getIdentityRuntimeConfig();
64
+ const user = await loadJwtSessionUser({
65
+ userId: credential.sub,
66
+ sid: credential.sid,
67
+ authEpoch: credential.auth_epoch,
68
+ groupsAdmin,
69
+ });
70
+ if (!user) return null;
71
+ if (!(await isAccountCategoryAllowed(user))) return null;
72
+ if (isAccountExpired(user.accountExpires)) {
73
+ await session.revokeAllForUser(user.id);
74
+ return null;
75
+ }
76
+ return {
77
+ user,
78
+ data: {
79
+ userId: user.id,
80
+ sid: credential.sid,
81
+ authEpoch: credential.auth_epoch,
82
+ expiresAt: new Date(credential.exp * 1_000).toISOString(),
83
+ },
84
+ };
85
+ };
86
+
87
+ const authenticateToken = async (token: string): Promise<AuthenticatedSession | null> => {
88
+ const credential = await verifyCredential(token);
89
+ return credential ? authenticateVerified(credential) : null;
90
+ };
91
+
92
+ const revokeToken = async (token: string, requestId?: string | null): Promise<void> => {
93
+ if (isSessionJwtCandidate(token)) {
94
+ const claims = await verifySessionToken(token);
95
+ if (!claims) return;
96
+ await sql`
97
+ UPDATE auth.session_families
98
+ SET revoked_at = COALESCE(revoked_at, now()),
99
+ revocation_reason = COALESCE(revocation_reason, 'logout'),
100
+ revoked_request_id = COALESCE(revoked_request_id, ${requestId?.slice(0, 64) ?? null})
101
+ WHERE sid = ${claims.sid}::uuid AND user_id = ${claims.sub}::uuid
102
+ `;
103
+ return;
104
+ }
105
+ };
106
+
107
+ const issueJwtSession = async (c: Context, userId: string, ttlSeconds: number, attempt = 0): Promise<string> => {
108
+ const issuedAt = new Date();
109
+ const expiresAt = new Date(issuedAt.getTime() + ttlSeconds * 1_000);
110
+ const sid = crypto.randomUUID();
111
+ const signer = await prepareIdentitySigner("session");
112
+ const requestId = c.req.header("x-request-id")?.slice(0, 64) ?? null;
113
+ const userAgent = c.req.header("user-agent")?.slice(0, 256) ?? null;
114
+
115
+ let authEpoch: number;
116
+ try {
117
+ authEpoch = await sql.begin(async (tx) => {
118
+ const [user] = await tx<Array<{ auth_epoch: string | number | bigint; provider: "local" | "ipa"; profile: "guest" | "user" }>>`
119
+ SELECT auth_epoch, provider, profile FROM auth.users WHERE id = ${userId}::uuid FOR UPDATE
120
+ `;
121
+ if (!user) throw new Error("Cannot create a session for an unknown user");
122
+ if (!(await isAccountCategoryAllowed(user, tx)))
123
+ throw new HTTPException(403, { message: "This account category is disabled. Contact an administrator." });
124
+ const epoch = Number(user.auth_epoch);
125
+ if (!Number.isSafeInteger(epoch) || epoch < 0) throw new Error("User auth_epoch is invalid");
126
+ const [activeKey] = await tx<Array<{ kid: string }>>`
127
+ UPDATE auth.signing_keys
128
+ SET verify_until = GREATEST(verify_until, ${new Date(
129
+ expiresAt.getTime() + IDENTITY_CLOCK_TOLERANCE_SECONDS * 1_000 + IDENTITY_ROLLOUT_MARGIN_MS,
130
+ )})
131
+ WHERE kid = ${signer.kid} AND purpose = 'session' AND state = 'active' AND sign_until > now()
132
+ RETURNING kid
133
+ `;
134
+ if (!activeKey) throw new Error("Prepared Cloud session signer is no longer active");
135
+ await tx`
136
+ INSERT INTO auth.session_families (
137
+ sid, user_id, auth_epoch, signing_kid, issued_at, expires_at, created_request_id, created_user_agent, legal_pending
138
+ ) VALUES (${sid}, ${userId}, ${epoch}, ${signer.kid}, ${issuedAt}, ${expiresAt}, ${requestId}, ${userAgent},
139
+ NOT EXISTS (SELECT 1 FROM auth.legal_acceptances WHERE user_id = ${userId}::uuid))
140
+ `;
141
+ await tx`UPDATE auth.users SET last_login_local = ${issuedAt} WHERE id = ${userId}::uuid`;
142
+ return epoch;
143
+ });
144
+ } catch (error) {
145
+ if (attempt === 0 && error instanceof Error && error.message === "Prepared Cloud session signer is no longer active") {
146
+ invalidateIdentitySignerCache("session", signer.kid);
147
+ return issueJwtSession(c, userId, ttlSeconds, 1);
148
+ }
149
+ throw error;
150
+ }
151
+
152
+ let token: string;
153
+ try {
154
+ token = (await signSessionToken({ userId, sid, authEpoch, issuedAt, expiresAt, signer })).token;
155
+ } catch (error) {
156
+ await sql`
157
+ UPDATE auth.session_families
158
+ SET revoked_at = now(), revocation_reason = 'issuance_failed'
159
+ WHERE sid = ${sid}::uuid AND revoked_at IS NULL
160
+ `;
161
+ throw error;
162
+ }
163
+
164
+ const [stillActive] = await sql<Array<{ active: boolean }>>`
165
+ SELECT EXISTS(
166
+ SELECT 1 FROM auth.signing_keys
167
+ WHERE kid = ${signer.kid} AND purpose = 'session' AND state = 'active'
168
+ ) AS active
169
+ `;
170
+ if (stillActive?.active) return token;
171
+
172
+ await sql`
173
+ UPDATE auth.session_families
174
+ SET revoked_at = now(), revocation_reason = 'signer_revoked_during_issuance'
175
+ WHERE sid = ${sid}::uuid AND revoked_at IS NULL
176
+ `;
177
+ invalidateIdentitySignerCache("session", signer.kid);
178
+ if (attempt === 0) return issueJwtSession(c, userId, ttlSeconds, 1);
179
+ throw new Error("Cloud session signer changed repeatedly during issuance");
180
+ };
181
+
182
+ export const session = {
183
+ getToken: (c: Context): string | null => {
184
+ const authorization = c.req.header("Authorization");
185
+ const bearer = parseBearer(authorization);
186
+ if (/^Bearer(?:\s|$)/i.test(authorization ?? "")) return isCloudApiToken(bearer) ? null : bearer;
187
+ return getCookie(c, "session_token") ?? null;
188
+ },
189
+
190
+ getBearerToken: (c: Context): string | null => parseBearer(c.req.header("Authorization")),
191
+
192
+ create: async (c: Context, userId: string): Promise<string> => {
193
+ const expiryHours = await settings.get<number>("user.session.expiry_hours");
194
+ if (!Number.isFinite(expiryHours) || expiryHours < 1) throw new Error("user.session.expiry_hours must be a positive number");
195
+ const ttlSeconds = Math.floor(expiryHours * 60 * 60);
196
+ const clientToken = await issueJwtSession(c, userId, ttlSeconds);
197
+
198
+ setCookie(c, "session_token", clientToken, {
199
+ httpOnly: true,
200
+ secure: !env.IS_DEVELOPMENT,
201
+ sameSite: "Lax",
202
+ maxAge: ttlSeconds,
203
+ path: "/",
204
+ });
205
+ return clientToken;
206
+ },
207
+
208
+ revoke: revokeToken,
209
+
210
+ delete: async (c: Context): Promise<void> => {
211
+ const token = session.getToken(c);
212
+ if (token) await revokeToken(token, c.req.header("x-request-id"));
213
+ deleteCookie(c, "session_token", { path: "/" });
214
+ },
215
+
216
+ revokeAllForUser: async (userId: string): Promise<void> => {
217
+ const [updated] = await sql<Array<{ id: string }>>`
218
+ UPDATE auth.users SET auth_epoch = auth_epoch + 1
219
+ WHERE id = ${userId}::uuid RETURNING id
220
+ `;
221
+ if (!updated) throw new Error("Cannot revoke sessions for an unknown user");
222
+ },
223
+
224
+ authenticate: authenticateToken,
225
+
226
+ authenticateRequest: (c: Context, token: string): Promise<AuthenticatedSession | null> =>
227
+ requestCached(authenticationByRequest, c, token, async () => {
228
+ const credential = await verifyForRequest(c, token);
229
+ return credential ? authenticateVerified(credential) : null;
230
+ }),
231
+
232
+ getRequestSubject: async (c: Context, token: string): Promise<string | null> => {
233
+ const credential = await verifyForRequest(c, token);
234
+ return credential?.sub ?? null;
235
+ },
236
+ };
@@ -0,0 +1,18 @@
1
+ import { Hono } from "hono";
2
+ import { sql } from "bun";
3
+ import { session } from "./index";
4
+
5
+ /** Integration fixture: run with an isolated Core authority and its public JWKS endpoint. */
6
+ export const createTestSession = async (userId: string): Promise<string> => {
7
+ // Most session fixtures represent established users, not first-use onboarding.
8
+ await sql`INSERT INTO auth.legal_acceptances (user_id, session_id, document_version, documents)
9
+ VALUES (${userId}::uuid, ${crypto.randomUUID()}::uuid, 'test-fixture', '[]'::jsonb) ON CONFLICT DO NOTHING`;
10
+ const app = new Hono().post("/login", async (c) => c.json({ token: await session.create(c, userId) }));
11
+ const response = await app.request("/login", { method: "POST" });
12
+ if (response.status !== 200) throw new Error(`Session fixture login failed: ${response.status}`);
13
+ const body: unknown = await response.json();
14
+ if (!body || typeof body !== "object" || !("token" in body) || typeof body.token !== "string") {
15
+ throw new Error("Session fixture did not return a token");
16
+ }
17
+ return body.token;
18
+ };
@@ -0,0 +1,144 @@
1
+ import { sql } from "bun";
2
+ import type { User } from "../../contracts/shared";
3
+ import { buildRoles } from "../accounts/authz";
4
+ import { resolveProviderProfile } from "../accounts/base-user";
5
+ import { managedGroupIdsSubquery, managedGroupsNamesSubquery } from "../accounts/group-sql";
6
+ import { buildIpaUserData, emptyIpaUserData, userIpaDataColumns, userIpaDataJoin } from "../accounts/ipa-data";
7
+ import { resolveAccountExpires } from "../accounts/model";
8
+ import { toPgTextArray } from "../postgres";
9
+
10
+ type DbRow = Record<string, unknown>;
11
+
12
+ const pgArrayLiteralToStrings = (value: string): string[] => {
13
+ if (value === "{}") return [];
14
+ if (!value.startsWith("{") || !value.endsWith("}")) return [];
15
+ const items: string[] = [];
16
+ let item = "";
17
+ let quoted = false;
18
+ let escaped = false;
19
+ let wasQuoted = false;
20
+ for (const char of value.slice(1, -1)) {
21
+ if (escaped) {
22
+ item += char;
23
+ escaped = false;
24
+ } else if (char === "\\") {
25
+ escaped = true;
26
+ } else if (char === '"') {
27
+ quoted = !quoted;
28
+ wasQuoted = true;
29
+ } else if (char === "," && !quoted) {
30
+ if (wasQuoted || (item !== "" && item !== "NULL")) items.push(item);
31
+ item = "";
32
+ wasQuoted = false;
33
+ } else {
34
+ item += char;
35
+ }
36
+ }
37
+ if (wasQuoted || (item !== "" && item !== "NULL")) items.push(item);
38
+ return items;
39
+ };
40
+
41
+ const stringArray = (value: unknown): string[] => {
42
+ if (Array.isArray(value)) return value.filter((item): item is string => typeof item === "string");
43
+ if (typeof value === "string") return pgArrayLiteralToStrings(value);
44
+ return [];
45
+ };
46
+
47
+ export const buildProjectedUser = (row: DbRow): User => {
48
+ const { provider, profile } = resolveProviderProfile(row);
49
+ const mail = (row.mail as string | null | undefined) ?? null;
50
+ const displayName = (row.display_name as string | null | undefined) ?? "";
51
+ const memberofGroup = stringArray(row.member_groups);
52
+ const memberofGroupIds = stringArray(row.member_group_ids);
53
+ const manages = stringArray(row.manages);
54
+ const managesGroupIds = stringArray(row.manages_group_ids);
55
+ const common = {
56
+ id: row.id as string,
57
+ uid: row.uid as string,
58
+ roles: buildRoles({ provider, profile, memberofGroup, manages, admin: Boolean(row.effective_admin) }),
59
+ profile,
60
+ givenname: (row.given_name as string | null | undefined) ?? "",
61
+ sn: (row.sn as string | null | undefined) ?? "",
62
+ displayName: displayName || (profile === "guest" && mail ? mail : ""),
63
+ mail,
64
+ avatarHash: (row.avatar_hash as string | null | undefined) ?? null,
65
+ accountExpires: resolveAccountExpires(row)?.toISOString() ?? null,
66
+ lastLoginLocal: row.last_login_local ? new Date(row.last_login_local as Date | string).toISOString() : null,
67
+ memberofGroup,
68
+ memberofGroupIds,
69
+ manages,
70
+ managesGroupIds,
71
+ };
72
+ return provider === "ipa"
73
+ ? { ...common, provider: "ipa", ipa: buildIpaUserData(row) ?? emptyIpaUserData() }
74
+ : { ...common, provider: "local", ipa: null };
75
+ };
76
+
77
+ export const userProjectionSql = (groupsAdmin: string[]) => sql`
78
+ u.*,
79
+ ${userIpaDataColumns},
80
+ CASE
81
+ WHEN u.provider = 'local' THEN u.admin
82
+ ELSE EXISTS(
83
+ SELECT 1 FROM auth.ipa_user_effective_groups eg
84
+ WHERE eg.user_id = u.id
85
+ AND eg.group_name = ANY(${toPgTextArray(groupsAdmin)}::text[])
86
+ )
87
+ END AS effective_admin,
88
+ COALESCE(ARRAY(
89
+ SELECT g.name
90
+ FROM auth.user_groups_v2 ug
91
+ JOIN auth.groups g ON g.id = ug.group_id
92
+ WHERE ug.user_id = u.id
93
+ ORDER BY g.name
94
+ ), '{}') AS member_groups,
95
+ COALESCE(ARRAY(
96
+ SELECT ug.group_id
97
+ FROM auth.user_groups_v2 ug
98
+ JOIN auth.groups g ON g.id = ug.group_id
99
+ WHERE ug.user_id = u.id
100
+ ORDER BY g.name
101
+ ), '{}') AS member_group_ids,
102
+ COALESCE(ARRAY(${managedGroupsNamesSubquery(sql`u.id`)}), '{}') AS manages,
103
+ COALESCE(ARRAY(${managedGroupIdsSubquery(sql`u.id`)}), '{}') AS manages_group_ids
104
+ `;
105
+
106
+ export const loadJwtSessionUser = async (
107
+ params: {
108
+ userId: string;
109
+ sid: string;
110
+ authEpoch: number;
111
+ groupsAdmin: string[];
112
+ /** Core's consent screen only: still validates family, epoch and expiry. */
113
+ allowPendingLegalConsent?: boolean;
114
+ },
115
+ query: typeof sql = sql,
116
+ ): Promise<User | null> => {
117
+ const rows = await query<DbRow[]>`
118
+ SELECT ${userProjectionSql(params.groupsAdmin)}
119
+ FROM auth.session_families sf
120
+ JOIN auth.users u ON u.id = sf.user_id
121
+ JOIN auth.signing_keys sk ON sk.kid = sf.signing_kid AND sk.state <> 'revoked'
122
+ ${userIpaDataJoin}
123
+ WHERE sf.sid = ${params.sid}::uuid
124
+ AND sf.user_id = ${params.userId}::uuid
125
+ AND sf.auth_epoch = ${params.authEpoch}
126
+ AND u.auth_epoch = ${params.authEpoch}
127
+ AND sf.revoked_at IS NULL
128
+ AND sf.expires_at > now()
129
+ AND (${params.allowPendingLegalConsent === true} OR NOT sf.legal_pending OR EXISTS (
130
+ SELECT 1 FROM auth.legal_acceptances la WHERE la.user_id = u.id
131
+ ))
132
+ `;
133
+ return rows[0] ? buildProjectedUser(rows[0]) : null;
134
+ };
135
+
136
+ export const loadCurrentUser = async (params: { userId: string; groupsAdmin: string[] }, query: typeof sql = sql): Promise<User | null> => {
137
+ const rows = await query<DbRow[]>`
138
+ SELECT ${userProjectionSql(params.groupsAdmin)}
139
+ FROM auth.users u
140
+ ${userIpaDataJoin}
141
+ WHERE u.id = ${params.userId}::uuid
142
+ `;
143
+ return rows[0] ? buildProjectedUser(rows[0]) : null;
144
+ };
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Typed async settings API.
3
+ *
4
+ * Two surfaces are exposed:
5
+ *
6
+ * 1. `app.settings` — created by `defineApp`, typed against that app's own
7
+ * declared settings (plus any core settings since core's snapshot is
8
+ * loaded into every container). Available on the AppDefinition return value.
9
+ *
10
+ * 2. `coreSettings` — for cloud-lib internal services that don't have an
11
+ * `app` reference (e.g. `services/notifications/email.ts`,
12
+ * `services/weather/forecast.ts`, OpenAPI middleware, cron callbacks).
13
+ * Loosely typed (caller provides the value type via the `T` generic).
14
+ *
15
+ * Backend: cache-aside via `store.ts` (Redis 5min TTL + DB fallback). All
16
+ * functions are async — sync render-time access uses the per-request snapshot
17
+ * exposed via `c.get("settings")`.
18
+ */
19
+
20
+ import type { AppSettingsMap, KindToType } from "../../contracts/settings-types";
21
+ import { deleteKey, readKey, writeKey } from "./store";
22
+
23
+ /**
24
+ * Per-app typed settings API. Keys constrained to those declared in the
25
+ * app's `defineApp({ settings: ... })` block.
26
+ */
27
+ export interface SettingsAPI<F extends Record<string, unknown>> {
28
+ get<K extends keyof F & string>(key: K): Promise<F[K]>;
29
+ set<K extends keyof F & string>(key: K, value: F[K]): Promise<void>;
30
+ remove<K extends keyof F & string>(key: K): Promise<void>;
31
+ }
32
+
33
+ /**
34
+ * Build a typed SettingsAPI for a given AppSettingsMap.
35
+ *
36
+ * The runtime is identical for every app (delegates to store.ts); the typing
37
+ * is purely a TS construct that constrains keys/values to what was declared.
38
+ */
39
+ export const createSettingsAPI = <S extends AppSettingsMap>(): SettingsAPI<{
40
+ [K in keyof S]: KindToType<S[K]["kind"]>;
41
+ }> => ({
42
+ get: async (key) => readKey(key) as never,
43
+ set: async (key, value) => writeKey(key, value),
44
+ remove: async (key) => deleteKey(key),
45
+ });
46
+
47
+ /**
48
+ * cloud-lib internal services and other apps use `coreSettings` for any
49
+ * setting access outside of a per-request context. Loose-typed (the caller
50
+ * specifies the expected value type via the `T` generic) — apps that need
51
+ * tight typing for their OWN settings use `app.settings.get/set` instead.
52
+ *
53
+ * Usage:
54
+ * await coreSettings.get<string>("app.name"); // string
55
+ * await coreSettings.set("freeipa.enable", true); // value type free
56
+ */
57
+ export const coreSettings = {
58
+ get: <T = unknown>(key: string): Promise<T> => readKey(key) as Promise<T>,
59
+ set: (key: string, value: unknown): Promise<void> => writeKey(key, value),
60
+ remove: (key: string): Promise<void> => deleteKey(key),
61
+ };