@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,644 @@
1
+ import { err, fail, ok, type Result } from "@k2b/stdlib";
2
+ import { type SQLQuery, sql } from "bun";
3
+ import { recursiveGroupIdsSubquery } from "../../services/accounts/group-sql";
4
+ import { toPgUuidArray } from "../../services/postgres";
5
+
6
+ export type AccessDb = typeof sql;
7
+
8
+ // ==========================
9
+ // Permission Levels
10
+ // ==========================
11
+
12
+ export const PERMISSION_LEVELS = ["none", "read", "write", "admin"] as const;
13
+ export type PermissionLevel = (typeof PERMISSION_LEVELS)[number];
14
+
15
+ /** Compare permission levels (returns true if a >= b) */
16
+ export const hasPermission = (userLevel: PermissionLevel, requiredLevel: PermissionLevel): boolean => {
17
+ const levels = PERMISSION_LEVELS;
18
+ return levels.indexOf(userLevel) >= levels.indexOf(requiredLevel);
19
+ };
20
+
21
+ // ==========================
22
+ // Principal Types
23
+ // ==========================
24
+
25
+ export type PrincipalType = "user" | "group" | "service_account" | "authenticated" | "public";
26
+
27
+ export type Principal =
28
+ | { type: "user"; userId: string }
29
+ | { type: "group"; groupId: string }
30
+ | { type: "service_account"; serviceAccountId: string }
31
+ | { type: "authenticated" }
32
+ | { type: "public" };
33
+
34
+ export type AccessSubject =
35
+ | { type: "user"; userId: string; delegatedByServiceAccountId?: string | null }
36
+ | { type: "service_account"; serviceAccountId: string };
37
+
38
+ export type EffectiveGroup = {
39
+ id: string;
40
+ name: string;
41
+ };
42
+
43
+ export type AccessPrincipalColumns = {
44
+ userId: SQLQuery;
45
+ groupId: SQLQuery;
46
+ serviceAccountId: SQLQuery;
47
+ authenticatedOnly: SQLQuery;
48
+ };
49
+
50
+ export type AccessPrincipalTierConditions = {
51
+ serviceAccount: SQLQuery;
52
+ user: SQLQuery;
53
+ group: SQLQuery;
54
+ authenticated: SQLQuery;
55
+ public: SQLQuery;
56
+ };
57
+
58
+ // ==========================
59
+ // Access Entry Types
60
+ // ==========================
61
+
62
+ export type AccessEntry = {
63
+ id: string;
64
+ principal: Principal;
65
+ permission: PermissionLevel;
66
+ createdAt: string;
67
+ // Resolved display info (populated by service)
68
+ displayName?: string;
69
+ avatarHash?: string | null;
70
+ };
71
+
72
+ export type AccessUserSource =
73
+ | { type: "direct" }
74
+ | {
75
+ type: "group";
76
+ /** Top-level group from the access grant, not the nested membership group. */
77
+ groupId: string;
78
+ groupName: string;
79
+ };
80
+
81
+ export type AccessUser = {
82
+ id: string;
83
+ uid: string;
84
+ displayName: string;
85
+ avatarHash: string | null;
86
+ permission: Exclude<PermissionLevel, "none">;
87
+ source: AccessUserSource;
88
+ };
89
+
90
+ type DbAccess = {
91
+ id: string;
92
+ user_id: string | null;
93
+ group_id: string | null;
94
+ service_account_id: string | null;
95
+ authenticated_only: boolean;
96
+ permission: PermissionLevel;
97
+ created_at: Date;
98
+ };
99
+
100
+ type DbAccessUser = {
101
+ id: string;
102
+ uid: string;
103
+ display_name: string;
104
+ avatar_hash: string | null;
105
+ permission: Exclude<PermissionLevel, "none">;
106
+ direct: boolean;
107
+ source_group_id: string | null;
108
+ source_group_name: string | null;
109
+ };
110
+
111
+ // ==========================
112
+ // Helper Functions
113
+ // ==========================
114
+
115
+ const uniqueIds = (values: string[] | null | undefined): string[] => [...new Set((values ?? []).filter(Boolean))];
116
+
117
+ const escapeLikePattern = (value: string): string => value.replace(/[\\%_]/g, (match) => `\\${match}`);
118
+
119
+ const PERMISSION_RANK: Record<PermissionLevel, number> = {
120
+ none: 1,
121
+ read: 2,
122
+ write: 3,
123
+ admin: 4,
124
+ };
125
+
126
+ /**
127
+ * Builds a typed access principal from one database access row.
128
+ */
129
+ const principalFromDb = (row: DbAccess): Principal => {
130
+ if (row.user_id) return { type: "user", userId: row.user_id };
131
+ if (row.group_id) return { type: "group", groupId: row.group_id };
132
+ if (row.service_account_id) return { type: "service_account", serviceAccountId: row.service_account_id };
133
+ if (row.authenticated_only) return { type: "authenticated" };
134
+ return { type: "public" };
135
+ };
136
+
137
+ /**
138
+ * Maps raw access rows into the normalized AccessEntry shape used by app services.
139
+ */
140
+ const mapToAccessEntry = (row: DbAccess): AccessEntry => ({
141
+ id: row.id,
142
+ principal: principalFromDb(row),
143
+ permission: row.permission,
144
+ createdAt: row.created_at.toISOString(),
145
+ });
146
+
147
+ // ==========================
148
+ // Core Access Service
149
+ // ==========================
150
+
151
+ /**
152
+ * Create a new access entry.
153
+ * Returns the created entry ID.
154
+ */
155
+ export const createAccess = async (
156
+ params: { principal: Principal; permission: PermissionLevel },
157
+ db: AccessDb = sql,
158
+ ): Promise<Result<{ id: string }>> => {
159
+ const { principal, permission } = params;
160
+
161
+ let userId: string | null = null;
162
+ let groupId: string | null = null;
163
+ let serviceAccountId: string | null = null;
164
+ let authenticatedOnly = false;
165
+
166
+ if (principal.type === "user") {
167
+ userId = principal.userId;
168
+ // Verify user exists
169
+ const [user] = await db<{ id: string }[]>`
170
+ SELECT id FROM auth.users WHERE id = ${userId}::uuid
171
+ `;
172
+ if (!user) {
173
+ return fail(err.notFound("User"));
174
+ }
175
+ } else if (principal.type === "group") {
176
+ groupId = principal.groupId;
177
+ // Verify group exists
178
+ const [group] = await db<{ id: string }[]>`
179
+ SELECT id FROM auth.groups WHERE id = ${groupId}::uuid
180
+ `;
181
+ if (!group) {
182
+ return fail(err.notFound("Group"));
183
+ }
184
+ } else if (principal.type === "service_account") {
185
+ serviceAccountId = principal.serviceAccountId;
186
+ const [serviceAccount] = await db<{ id: string }[]>`
187
+ SELECT id FROM auth.service_accounts WHERE id = ${serviceAccountId}::uuid AND status = 'active'
188
+ `;
189
+ if (!serviceAccount) {
190
+ return fail(err.notFound("Service account"));
191
+ }
192
+ } else if (principal.type === "authenticated") {
193
+ authenticatedOnly = true;
194
+ }
195
+ // public: user/group null, authenticated_only false
196
+
197
+ const [row] = await db<{ id: string }[]>`
198
+ INSERT INTO auth.access (user_id, group_id, service_account_id, authenticated_only, permission)
199
+ VALUES (${userId}::uuid, ${groupId}::uuid, ${serviceAccountId}::uuid, ${authenticatedOnly}, ${permission}::auth.permission_level)
200
+ RETURNING id
201
+ `;
202
+
203
+ if (!row) {
204
+ return fail(err.internal("Failed to create access entry"));
205
+ }
206
+
207
+ return ok({ id: row.id });
208
+ };
209
+
210
+ /**
211
+ * Get an access entry by ID.
212
+ */
213
+ export const getAccess = async (params: { id: string }, db: AccessDb = sql): Promise<AccessEntry | null> => {
214
+ const [row] = await db<DbAccess[]>`
215
+ SELECT id, user_id, group_id, service_account_id, authenticated_only, permission, created_at
216
+ FROM auth.access
217
+ WHERE id = ${params.id}::uuid
218
+ `;
219
+ return row ? mapToAccessEntry(row) : null;
220
+ };
221
+
222
+ /**
223
+ * Update an access entry's permission level.
224
+ */
225
+ export const updateAccess = async (params: { id: string; permission: PermissionLevel }, db: AccessDb = sql): Promise<Result<void>> => {
226
+ const result = await db`
227
+ UPDATE auth.access
228
+ SET permission = ${params.permission}::auth.permission_level
229
+ WHERE id = ${params.id}::uuid
230
+ `;
231
+
232
+ if (result.count === 0) {
233
+ return fail(err.notFound("Access entry"));
234
+ }
235
+
236
+ return ok();
237
+ };
238
+
239
+ /** Resolve direct and nested group memberships from the authoritative database mirror. */
240
+ export const getEffectiveGroups = async (params: { userId: string | null }, db: AccessDb = sql): Promise<EffectiveGroup[]> => {
241
+ if (!params.userId) return [];
242
+ const rows = await db<{ id: string; name: string }[]>`
243
+ SELECT g.id, g.name
244
+ FROM auth.groups g
245
+ WHERE g.id IN (${recursiveGroupIdsSubquery(params.userId)})
246
+ ORDER BY g.name
247
+ `;
248
+ return rows;
249
+ };
250
+
251
+ export const getEffectiveGroupIds = async (params: { userId: string | null }, db: AccessDb = sql): Promise<string[]> =>
252
+ (await getEffectiveGroups(params, db)).map((group) => group.id);
253
+
254
+ /**
255
+ * Delete an access entry.
256
+ */
257
+ export const deleteAccess = async (params: { id: string }, db: AccessDb = sql): Promise<Result<void>> => {
258
+ const result = await db`
259
+ DELETE FROM auth.access
260
+ WHERE id = ${params.id}::uuid
261
+ `;
262
+
263
+ if (result.count === 0) {
264
+ return fail(err.notFound("Access entry"));
265
+ }
266
+
267
+ return ok();
268
+ };
269
+
270
+ // ==========================
271
+ // Resource Access Helpers
272
+ // ==========================
273
+
274
+ /**
275
+ * Generic interface for resource access junction tables.
276
+ * Each app implements this to connect their resources to auth.access.
277
+ */
278
+ export type ResourceAccessAdapter<TResourceId = string> = {
279
+ /** Get all access entries for a resource */
280
+ list: (resourceId: TResourceId) => Promise<AccessEntry[]>;
281
+ /** Add an access entry to a resource */
282
+ add: (resourceId: TResourceId, accessId: string) => Promise<Result<void>>;
283
+ /** Remove an access entry from a resource */
284
+ remove: (resourceId: TResourceId, accessId: string) => Promise<Result<void>>;
285
+ /** Count access entries for a resource */
286
+ count: (resourceId: TResourceId) => Promise<number>;
287
+ };
288
+
289
+ /**
290
+ * Build canonical principal predicates for one auth.access row, separated by
291
+ * precedence tier.
292
+ *
293
+ * Apps provide column fragments because access rows are commonly joined under
294
+ * different aliases. The subject is authoritative: group membership is always
295
+ * resolved recursively from its user id and never accepted from a caller.
296
+ * Resource binding, credential scopes, and permission precedence remain the
297
+ * responsibility of the app using the predicate.
298
+ */
299
+ export const buildAccessPrincipalTierConditions = (params: {
300
+ subject: AccessSubject | null;
301
+ columns: AccessPrincipalColumns;
302
+ }): AccessPrincipalTierConditions => {
303
+ const userId = params.subject?.type === "user" ? params.subject.userId : null;
304
+ const serviceAccountId = params.subject?.type === "service_account" ? params.subject.serviceAccountId : null;
305
+ const authenticated = params.subject !== null;
306
+ const columns = params.columns;
307
+
308
+ return {
309
+ serviceAccount: sql`${columns.serviceAccountId} = ${serviceAccountId}::uuid`,
310
+ user: sql`${columns.userId} = ${userId}::uuid`,
311
+ group: sql`(
312
+ ${userId}::uuid IS NOT NULL
313
+ AND ${columns.groupId} IN (${userId ? recursiveGroupIdsSubquery(userId) : sql`SELECT NULL::uuid WHERE FALSE`})
314
+ )`,
315
+ authenticated: sql`${authenticated} AND ${columns.authenticatedOnly} = true`,
316
+ public: sql`(
317
+ ${columns.userId} IS NULL
318
+ AND ${columns.groupId} IS NULL
319
+ AND ${columns.serviceAccountId} IS NULL
320
+ AND ${columns.authenticatedOnly} = false
321
+ )`,
322
+ };
323
+ };
324
+
325
+ /** Build one predicate that matches every principal tier for the subject. */
326
+ export const buildAccessPrincipalCondition = (params: { subject: AccessSubject | null; columns: AccessPrincipalColumns }): SQLQuery => {
327
+ const tiers = buildAccessPrincipalTierConditions(params);
328
+ return sql`(${tiers.serviceAccount} OR ${tiers.user} OR ${tiers.group} OR ${tiers.authenticated} OR ${tiers.public})`;
329
+ };
330
+
331
+ /**
332
+ * Get the effective permission level for an actor on a resource.
333
+ * Returns the highest permission from:
334
+ * - Direct user or service-account access
335
+ * - Direct and nested user group memberships resolved from the database
336
+ * - Authenticated access for every authenticated subject
337
+ * - Public access for every subject, including anonymous callers
338
+ */
339
+ export const getEffectivePermission = async (params: {
340
+ accessIds: string[];
341
+ subject?: AccessSubject | null;
342
+ /** @deprecated Pass subject instead. */
343
+ userId?: string | null;
344
+ /** @deprecated Membership is resolved from userId and this value is intentionally ignored. */
345
+ userGroups?: string[];
346
+ /** @deprecated Pass subject instead. */
347
+ serviceAccountId?: string | null;
348
+ }): Promise<PermissionLevel> => {
349
+ const accessIds = params.accessIds ?? [];
350
+ const subject =
351
+ "subject" in params
352
+ ? (params.subject ?? null)
353
+ : params.userId
354
+ ? { type: "user" as const, userId: params.userId }
355
+ : params.serviceAccountId
356
+ ? { type: "service_account" as const, serviceAccountId: params.serviceAccountId }
357
+ : null;
358
+ const legacyServiceAccountId = !("subject" in params) && !params.userId ? (params.serviceAccountId ?? null) : null;
359
+
360
+ if (accessIds.length === 0) return "none";
361
+
362
+ // Legacy callers did not provide resource binding or scope context. Keep
363
+ // their service-account lookup direct-only so the new authenticated/public
364
+ // semantics require an explicit AccessSubject migration.
365
+ const principalMatch = legacyServiceAccountId
366
+ ? sql`a.service_account_id = ${legacyServiceAccountId}::uuid`
367
+ : buildAccessPrincipalCondition({
368
+ subject,
369
+ columns: {
370
+ userId: sql`a.user_id`,
371
+ groupId: sql`a.group_id`,
372
+ serviceAccountId: sql`a.service_account_id`,
373
+ authenticatedOnly: sql`a.authenticated_only`,
374
+ },
375
+ });
376
+
377
+ const rows = await sql<{ permission: PermissionLevel }[]>`
378
+ SELECT a.permission
379
+ FROM auth.access a
380
+ WHERE a.id = ANY(${toPgUuidArray(accessIds)}::uuid[])
381
+ AND ${principalMatch}
382
+ ORDER BY
383
+ CASE permission
384
+ WHEN 'admin' THEN 4
385
+ WHEN 'write' THEN 3
386
+ WHEN 'read' THEN 2
387
+ WHEN 'none' THEN 1
388
+ END DESC
389
+ LIMIT 1
390
+ `;
391
+
392
+ return rows[0]?.permission ?? "none";
393
+ };
394
+
395
+ /**
396
+ * Lists concrete users reachable from auth.access entries.
397
+ *
398
+ * Apps stay responsible for collecting the relevant access entry IDs from their
399
+ * own junction tables. This helper expands direct user grants and recursive
400
+ * group grants only. It intentionally does not expand public or
401
+ * authenticated-only grants into "all users", because those scopes are not
402
+ * bounded, predictable assignee/member lists.
403
+ */
404
+ export const listUsersWithAccess = async (params: {
405
+ accessIds: string[];
406
+ search?: string;
407
+ userIds?: string[];
408
+ excludeUserIds?: string[];
409
+ minimumPermission?: Exclude<PermissionLevel, "none">;
410
+ limit?: number;
411
+ db?: AccessDb;
412
+ }): Promise<AccessUser[]> => {
413
+ const accessIds = uniqueIds(params.accessIds);
414
+ if (accessIds.length === 0) return [];
415
+
416
+ const requestedUserIds = uniqueIds(params.userIds);
417
+ const excludeUserIds = uniqueIds(params.excludeUserIds);
418
+ const query = params.search?.trim().toLowerCase();
419
+ const pattern = query ? `%${escapeLikePattern(query)}%` : null;
420
+ const minimumRank = PERMISSION_RANK[params.minimumPermission ?? "read"];
421
+ const defaultLimit = requestedUserIds.length > 0 ? requestedUserIds.length : 20;
422
+ const limit = Math.min(Math.max(params.limit ?? defaultLimit, 1), 500);
423
+ const db = params.db ?? sql;
424
+ const userFilter = requestedUserIds.length > 0 ? db`AND id = ANY(${toPgUuidArray(requestedUserIds)}::uuid[])` : db``;
425
+
426
+ const rows = await db<DbAccessUser[]>`
427
+ WITH RECURSIVE
428
+ root_groups(root_group_id, root_group_name, group_id, group_ids, permission, permission_rank) AS (
429
+ SELECT
430
+ a.group_id,
431
+ COALESCE(NULLIF(g.name, ''), g.cn),
432
+ a.group_id,
433
+ ARRAY[a.group_id]::uuid[],
434
+ a.permission,
435
+ CASE a.permission
436
+ WHEN 'admin' THEN 4
437
+ WHEN 'write' THEN 3
438
+ WHEN 'read' THEN 2
439
+ ELSE 1
440
+ END
441
+ FROM auth.access a
442
+ JOIN auth.groups g ON g.id = a.group_id
443
+ WHERE a.id = ANY(${toPgUuidArray(accessIds)}::uuid[])
444
+ AND a.group_id IS NOT NULL
445
+ AND CASE a.permission
446
+ WHEN 'admin' THEN 4
447
+ WHEN 'write' THEN 3
448
+ WHEN 'read' THEN 2
449
+ ELSE 1
450
+ END >= ${minimumRank}
451
+
452
+ UNION ALL
453
+
454
+ SELECT
455
+ rg.root_group_id,
456
+ rg.root_group_name,
457
+ gg.child_group_id,
458
+ rg.group_ids || gg.child_group_id,
459
+ rg.permission,
460
+ rg.permission_rank
461
+ FROM auth.group_groups_v2 gg
462
+ JOIN root_groups rg ON rg.group_id = gg.parent_group_id
463
+ WHERE NOT gg.child_group_id = ANY(rg.group_ids)
464
+ ),
465
+ candidate_users AS (
466
+ SELECT
467
+ u.id,
468
+ u.uid,
469
+ COALESCE(NULLIF(u.display_name, ''), u.uid, u.id::text) AS display_name,
470
+ u.avatar_hash,
471
+ TRUE AS direct,
472
+ NULL::uuid AS source_group_id,
473
+ NULL::text AS source_group_name,
474
+ a.permission,
475
+ CASE a.permission
476
+ WHEN 'admin' THEN 4
477
+ WHEN 'write' THEN 3
478
+ WHEN 'read' THEN 2
479
+ ELSE 1
480
+ END AS permission_rank
481
+ FROM auth.access a
482
+ JOIN auth.users u ON u.id = a.user_id
483
+ WHERE a.id = ANY(${toPgUuidArray(accessIds)}::uuid[])
484
+ AND a.user_id IS NOT NULL
485
+ AND CASE a.permission
486
+ WHEN 'admin' THEN 4
487
+ WHEN 'write' THEN 3
488
+ WHEN 'read' THEN 2
489
+ ELSE 1
490
+ END >= ${minimumRank}
491
+
492
+ UNION ALL
493
+
494
+ SELECT
495
+ u.id,
496
+ u.uid,
497
+ COALESCE(NULLIF(u.display_name, ''), u.uid, u.id::text) AS display_name,
498
+ u.avatar_hash,
499
+ FALSE AS direct,
500
+ rg.root_group_id AS source_group_id,
501
+ rg.root_group_name AS source_group_name,
502
+ rg.permission,
503
+ rg.permission_rank
504
+ FROM root_groups rg
505
+ JOIN auth.user_groups_v2 ug ON ug.group_id = rg.group_id
506
+ JOIN auth.users u ON u.id = ug.user_id
507
+ ),
508
+ access_users AS (
509
+ SELECT
510
+ id,
511
+ uid,
512
+ display_name,
513
+ avatar_hash,
514
+ CASE MAX(permission_rank)
515
+ WHEN 4 THEN 'admin'
516
+ WHEN 3 THEN 'write'
517
+ ELSE 'read'
518
+ END AS permission,
519
+ BOOL_OR(direct) AS direct,
520
+ (
521
+ ARRAY_AGG(source_group_id ORDER BY permission_rank DESC, source_group_name)
522
+ FILTER (WHERE NOT direct AND source_group_id IS NOT NULL)
523
+ )[1] AS source_group_id,
524
+ (
525
+ ARRAY_AGG(source_group_name ORDER BY permission_rank DESC, source_group_name)
526
+ FILTER (WHERE NOT direct AND source_group_name IS NOT NULL)
527
+ )[1] AS source_group_name,
528
+ COALESCE(
529
+ STRING_AGG(source_group_name, ' ')
530
+ FILTER (WHERE NOT direct AND source_group_name IS NOT NULL),
531
+ ''
532
+ ) AS group_names
533
+ FROM candidate_users
534
+ GROUP BY id, uid, display_name, avatar_hash
535
+ )
536
+ SELECT id, uid, display_name, avatar_hash, permission, direct, source_group_id, source_group_name
537
+ FROM access_users
538
+ WHERE id <> ALL(${toPgUuidArray(excludeUserIds)}::uuid[])
539
+ AND (direct OR (source_group_id IS NOT NULL AND source_group_name IS NOT NULL))
540
+ ${userFilter}
541
+ AND (
542
+ ${pattern}::text IS NULL
543
+ OR LOWER(display_name) LIKE ${pattern} ESCAPE '\\'
544
+ OR LOWER(uid) LIKE ${pattern} ESCAPE '\\'
545
+ OR LOWER(group_names) LIKE ${pattern} ESCAPE '\\'
546
+ )
547
+ ORDER BY LOWER(display_name), id
548
+ LIMIT ${limit}
549
+ `;
550
+
551
+ return rows.map((row) => ({
552
+ id: row.id,
553
+ uid: row.uid,
554
+ displayName: row.display_name,
555
+ avatarHash: row.avatar_hash,
556
+ permission: row.permission,
557
+ source: row.direct
558
+ ? { type: "direct" }
559
+ : {
560
+ type: "group",
561
+ groupId: row.source_group_id as string,
562
+ groupName: row.source_group_name as string,
563
+ },
564
+ }));
565
+ };
566
+
567
+ /**
568
+ * Resolve display names for access entries.
569
+ * Populates the displayName field based on principal type.
570
+ */
571
+ export const resolveDisplayNames = async (entries: AccessEntry[]): Promise<AccessEntry[]> => {
572
+ const userIds = entries.filter((e) => e.principal.type === "user").map((e) => (e.principal as { type: "user"; userId: string }).userId);
573
+
574
+ const groupIds = entries
575
+ .filter((e) => e.principal.type === "group")
576
+ .map((e) => (e.principal as { type: "group"; groupId: string }).groupId);
577
+
578
+ const serviceAccountIds = entries
579
+ .filter((e) => e.principal.type === "service_account")
580
+ .map((e) => (e.principal as { type: "service_account"; serviceAccountId: string }).serviceAccountId);
581
+
582
+ // Fetch user display names
583
+ const userNames = new Map<string, { displayName: string; avatarHash: string | null }>();
584
+ if (userIds.length > 0) {
585
+ const users = await sql<{ id: string; display_name: string; uid: string; avatar_hash: string | null }[]>`
586
+ SELECT id, display_name, uid, avatar_hash
587
+ FROM auth.users
588
+ WHERE id = ANY(${toPgUuidArray(userIds)}::uuid[])
589
+ `;
590
+ for (const u of users) {
591
+ userNames.set(u.id, { displayName: u.display_name || u.uid, avatarHash: u.avatar_hash });
592
+ }
593
+ }
594
+
595
+ const groupNames = new Map<string, string>();
596
+ if (groupIds.length > 0) {
597
+ const groups = await sql<{ id: string; name: string }[]>`
598
+ SELECT id, name
599
+ FROM auth.groups
600
+ WHERE id = ANY(${toPgUuidArray(groupIds)}::uuid[])
601
+ `;
602
+ for (const group of groups) {
603
+ groupNames.set(group.id, group.name);
604
+ }
605
+ }
606
+
607
+ const serviceAccountNames = new Map<string, string>();
608
+ if (serviceAccountIds.length > 0) {
609
+ const serviceAccounts = await sql<{ id: string; name: string }[]>`
610
+ SELECT id, name
611
+ FROM auth.service_accounts
612
+ WHERE id = ANY(${toPgUuidArray(serviceAccountIds)}::uuid[])
613
+ `;
614
+ for (const serviceAccount of serviceAccounts) {
615
+ serviceAccountNames.set(serviceAccount.id, serviceAccount.name);
616
+ }
617
+ }
618
+
619
+ return entries.map((entry) => {
620
+ let displayName: string;
621
+ let avatarHash: string | null | undefined;
622
+ switch (entry.principal.type) {
623
+ case "user": {
624
+ const user = userNames.get(entry.principal.userId);
625
+ displayName = user?.displayName ?? "Unknown User";
626
+ avatarHash = user?.avatarHash ?? null;
627
+ break;
628
+ }
629
+ case "group":
630
+ displayName = groupNames.get(entry.principal.groupId) ?? "Unknown Group";
631
+ break;
632
+ case "service_account":
633
+ displayName = serviceAccountNames.get(entry.principal.serviceAccountId) ?? "Unknown Service Account";
634
+ break;
635
+ case "authenticated":
636
+ displayName = "All users (incl. guests)";
637
+ break;
638
+ case "public":
639
+ displayName = "Public";
640
+ break;
641
+ }
642
+ return { ...entry, displayName, avatarHash };
643
+ });
644
+ };