@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,179 @@
1
+ import { X509Certificate } from "node:crypto";
2
+ import { setFreeIpaTlsResolver } from "../server/services/freeipa/tls";
3
+ import { coreSettings } from "./settings/api";
4
+
5
+ const MAX_CA_CERT_BYTES = 256 * 1024;
6
+ const PEM_CERTIFICATE_PATTERN = /-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g;
7
+
8
+ export type FreeIpaSyncGuard = {
9
+ maxUserChanges: number;
10
+ maxUserChangePercent: number;
11
+ maxGroupDeletions: number;
12
+ maxGroupDeletionPercent: number;
13
+ };
14
+
15
+ export type FreeIpaConfig = {
16
+ enabled: boolean;
17
+ configured: boolean;
18
+ /** Setting keys that are still empty. Empty when `configured` is true. */
19
+ missingSettings: string[];
20
+ url: string;
21
+ serviceUser: string;
22
+ servicePassword: string;
23
+ groupsAdmin: string[];
24
+ groupsBaseSync: string[];
25
+ groupsBaseIpaRealm: string[];
26
+ groupsExcluded: string[];
27
+ caCert: string;
28
+ allowInsecure: boolean;
29
+ syncGuard: FreeIpaSyncGuard;
30
+ };
31
+
32
+ const normalizeString = (value: unknown): string => (typeof value === "string" ? value.trim() : "");
33
+ const normalizeNonNegativeNumber = (value: unknown, fallback: number): number => {
34
+ const parsed = typeof value === "number" ? value : Number(value);
35
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
36
+ };
37
+ const normalizeNonNegativeInteger = (value: unknown, fallback: number): number => Math.floor(normalizeNonNegativeNumber(value, fallback));
38
+ const normalizePercentage = (value: unknown, fallback: number): number => Math.min(100, normalizeNonNegativeNumber(value, fallback));
39
+
40
+ export const validateFreeIpaCaCert = (value: unknown): { ok: true; value: string } | { ok: false; error: string } => {
41
+ if (typeof value !== "string") return { ok: false, error: "CA certificate must be PEM text" };
42
+ const normalized = value.trim();
43
+ if (!normalized) return { ok: true, value: "" };
44
+ if (new TextEncoder().encode(normalized).byteLength > MAX_CA_CERT_BYTES) {
45
+ return { ok: false, error: "CA certificate bundle must not exceed 256 KiB" };
46
+ }
47
+
48
+ const certificates = normalized.match(PEM_CERTIFICATE_PATTERN) ?? [];
49
+ const remainder = normalized.replace(PEM_CERTIFICATE_PATTERN, "").trim();
50
+ if (certificates.length === 0 || remainder.length > 0) {
51
+ return { ok: false, error: "Enter one or more complete PEM certificates" };
52
+ }
53
+
54
+ try {
55
+ for (const certificate of certificates) new X509Certificate(certificate);
56
+ } catch {
57
+ return { ok: false, error: "CA certificate contains invalid PEM data" };
58
+ }
59
+ return { ok: true, value: normalized };
60
+ };
61
+
62
+ export const resolveFreeIpaTlsOptions = (caCert: string, allowInsecure: boolean): Bun.TLSOptions => {
63
+ const certificate = validateFreeIpaCaCert(caCert);
64
+ if (!certificate.ok) throw new Error(`Invalid freeipa.ca_cert: ${certificate.error}`);
65
+ if (certificate.value) return { ca: certificate.value, rejectUnauthorized: true };
66
+ if (allowInsecure) return { rejectUnauthorized: false };
67
+ return { rejectUnauthorized: true };
68
+ };
69
+
70
+ /**
71
+ * `fallback` is only for groups that have a genuine FreeIPA-wide default.
72
+ *
73
+ * The scope groups deliberately have none: `base_sync` decides who gets a Cloud
74
+ * account at all and `base_ipa_realm` decides who is a full user rather than a
75
+ * guest, and both depend entirely on how the directory is organised. A guess
76
+ * either over-provisions every FreeIPA user or silently demotes everyone to
77
+ * guest, so an empty value has to make the config incomplete instead.
78
+ */
79
+ const normalizeStringList = (value: unknown, fallback: string[] = []): string[] => {
80
+ if (!Array.isArray(value)) return fallback;
81
+ const normalized = value.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
82
+ return normalized.length > 0 ? normalized : fallback;
83
+ };
84
+
85
+ /**
86
+ * Read the full FreeIPA config snapshot from settings (Redis cache-aside +
87
+ * Postgres fallback). Always returns within Redis-TTL fresh data — no hidden
88
+ * dependency on request-lifecycle middleware.
89
+ */
90
+ export const getFreeIpaConfig = async (): Promise<FreeIpaConfig> => {
91
+ const [
92
+ rawUrl,
93
+ rawServiceUser,
94
+ rawServicePassword,
95
+ rawEnabled,
96
+ rawAdmin,
97
+ rawBaseSync,
98
+ rawBaseIpaRealm,
99
+ rawExcluded,
100
+ rawCaCert,
101
+ rawAllowInsecure,
102
+ rawMaxUserChanges,
103
+ rawMaxUserChangePercent,
104
+ rawMaxGroupDeletions,
105
+ rawMaxGroupDeletionPercent,
106
+ ] = await Promise.all([
107
+ coreSettings.get<string>("freeipa.url"),
108
+ coreSettings.get<string>("freeipa.service_user"),
109
+ coreSettings.get<string>("freeipa.service_password"),
110
+ coreSettings.get<boolean>("freeipa.enable"),
111
+ coreSettings.get<string[]>("freeipa.groups.admin"),
112
+ coreSettings.get<string[]>("freeipa.groups.base_sync"),
113
+ coreSettings.get<string[]>("freeipa.groups.base_ipa_realm"),
114
+ coreSettings.get<string[]>("freeipa.groups.excluded"),
115
+ coreSettings.get<string>("freeipa.ca_cert"),
116
+ coreSettings.get<boolean>("freeipa.allow_insecure"),
117
+ coreSettings.get<number>("freeipa.sync_guard.max_user_changes"),
118
+ coreSettings.get<number>("freeipa.sync_guard.max_user_change_percent"),
119
+ coreSettings.get<number>("freeipa.sync_guard.max_group_deletions"),
120
+ coreSettings.get<number>("freeipa.sync_guard.max_group_deletion_percent"),
121
+ ]);
122
+
123
+ const url = normalizeString(rawUrl);
124
+ const serviceUser = normalizeString(rawServiceUser);
125
+ const servicePassword = normalizeString(rawServicePassword);
126
+ const enabled = Boolean(rawEnabled);
127
+ const caCert = normalizeString(rawCaCert);
128
+ const caValidation = validateFreeIpaCaCert(caCert);
129
+
130
+ // "admins" and the excluded list are real FreeIPA defaults; the scope groups
131
+ // are not, so they stay empty and make the config incomplete.
132
+ const groupsAdmin = normalizeStringList(rawAdmin, ["admins"]);
133
+ const groupsBaseSync = normalizeStringList(rawBaseSync);
134
+ const groupsBaseIpaRealm = normalizeStringList(rawBaseIpaRealm);
135
+ const groupsExcluded = normalizeStringList(rawExcluded, ["editors", "trust admins", "admins"]);
136
+
137
+ const missingSettings = [
138
+ ...(url ? [] : ["freeipa.url"]),
139
+ ...(serviceUser ? [] : ["freeipa.service_user"]),
140
+ ...(servicePassword ? [] : ["freeipa.service_password"]),
141
+ ...(groupsBaseSync.length > 0 ? [] : ["freeipa.groups.base_sync"]),
142
+ ...(groupsBaseIpaRealm.length > 0 ? [] : ["freeipa.groups.base_ipa_realm"]),
143
+ ...(caValidation.ok ? [] : ["freeipa.ca_cert"]),
144
+ ];
145
+
146
+ return {
147
+ enabled,
148
+ configured: missingSettings.length === 0,
149
+ missingSettings,
150
+ url,
151
+ serviceUser,
152
+ servicePassword,
153
+ groupsAdmin,
154
+ groupsBaseSync,
155
+ groupsBaseIpaRealm,
156
+ groupsExcluded,
157
+ caCert,
158
+ allowInsecure: Boolean(rawAllowInsecure),
159
+ syncGuard: {
160
+ maxUserChanges: normalizeNonNegativeInteger(rawMaxUserChanges, 10),
161
+ maxUserChangePercent: normalizePercentage(rawMaxUserChangePercent, 20),
162
+ maxGroupDeletions: normalizeNonNegativeInteger(rawMaxGroupDeletions, 5),
163
+ maxGroupDeletionPercent: normalizePercentage(rawMaxGroupDeletionPercent, 20),
164
+ },
165
+ };
166
+ };
167
+
168
+ // ── TLS resolver wiring ──────────────────────────────────────────────────────
169
+ // Register an async resolver at module load so the freeipa transport
170
+ // (`server/services/freeipa/client.ts` + `session.ts`) can read TLS opts
171
+ // without taking a hard dependency on settings (would create a layering cycle).
172
+ //
173
+ // Resolution order: ca_cert (proper, signed by your private CA) wins over
174
+ // allow_insecure (lab/dev kill switch). Verification is explicit in every
175
+ // secure mode so NODE_TLS_REJECT_UNAUTHORIZED cannot weaken this policy.
176
+ setFreeIpaTlsResolver(async () => {
177
+ const config = await getFreeIpaConfig();
178
+ return resolveFreeIpaTlsOptions(config.caCert, config.allowInsecure);
179
+ });
@@ -0,0 +1,186 @@
1
+ import { lazySync } from "../_internal/process-sync";
2
+ import { logger } from "./logging";
3
+
4
+ /** Written by the gateway router, read by gateway-ops: one owner, one declaration. */
5
+ const GATEWAY_RESOURCE_OWNER = "cloud";
6
+ const SNAPSHOT_TTL_MS = 30_000;
7
+ const TOPIC_ID = "cloud-gateway-telemetry";
8
+ const TOPIC_RETENTION_MS = 24 * 60 * 60 * 1000;
9
+ /** Hard loss boundary for the 24h request-telemetry buffer (discard-old). */
10
+ const TOPIC_RETENTION_BYTES = 1024 * 1024 * 1024;
11
+ /** Whole envelope; events are a few hundred bytes with 200-char text bounds. */
12
+ const TOPIC_PAYLOAD_BYTES = 16 * 1024;
13
+ const TOPIC_TENANT = "default";
14
+ const DROP_LOG_INTERVAL_MS = 30_000;
15
+
16
+ const log = logger("gateway:telemetry");
17
+
18
+ export type GatewayRouteWarning = {
19
+ appId: string;
20
+ prefix: string;
21
+ reason: string;
22
+ detail?: string;
23
+ };
24
+
25
+ export type GatewayRouteSnapshotInput = {
26
+ instanceId: string;
27
+ baseUrl: string;
28
+ startedAt: number;
29
+ routeHash: string;
30
+ routeWarnings: GatewayRouteWarning[];
31
+ table: {
32
+ version: number;
33
+ builtAt: number;
34
+ routeCount: number;
35
+ routes: Array<{ prefix: string; appId: string }>;
36
+ };
37
+ stats: {
38
+ totalRequests: number;
39
+ noRouteCount: number;
40
+ byApp: Map<string, { count: number; totalMs: number; errors: number }>;
41
+ byRoute: Map<string, { count: number; errors: number; lastSeen: number }>;
42
+ };
43
+ };
44
+
45
+ export type GatewayRouteSnapshot = {
46
+ instanceId: string;
47
+ baseUrl: string;
48
+ startedAt: number;
49
+ updatedAt: number;
50
+ tableVersion: number;
51
+ tableBuiltAt: number;
52
+ routeCount: number;
53
+ routeHash: string;
54
+ routeWarnings: GatewayRouteWarning[];
55
+ routes: Array<{ prefix: string; appId: string }>;
56
+ stats: {
57
+ totalRequests: number;
58
+ noRouteCount: number;
59
+ byApp: Array<{ appId: string; count: number; totalMs: number; errors: number }>;
60
+ byRoute: Array<{ prefix: string; count: number; errors: number; lastSeen: number }>;
61
+ };
62
+ };
63
+
64
+ /**
65
+ * Response header an app uses to tell the gateway which route template it
66
+ * matched (`/api/mail/mailboxes/:mailboxId`). Set by `middleware.runtime()`
67
+ * / `middleware.observability()`, consumed and stripped by the gateway proxy.
68
+ */
69
+ export const ROUTE_TEMPLATE_HEADER = "X-Route-Template";
70
+
71
+ export type GatewayTelemetryEvent = {
72
+ v: 1;
73
+ kind: "request";
74
+ appId: string;
75
+ routePrefix: string;
76
+ /**
77
+ * Matched route template, id segments collapsed and query string dropped.
78
+ * `null` when neither the app nor the gateway fallback could derive one —
79
+ * older rows predating this field read the same way.
80
+ */
81
+ pathTemplate: string | null;
82
+ method: string;
83
+ status: number;
84
+ durationMs: number;
85
+ errorKind: "upstream_unavailable" | "unmatched_route" | null;
86
+ occurredAt: string;
87
+ };
88
+
89
+ const snapshots = lazySync((sync) =>
90
+ sync.ephemeral<GatewayRouteSnapshot>({
91
+ id: "gateway-route-snapshots",
92
+ owner: GATEWAY_RESOURCE_OWNER,
93
+ ttlMs: SNAPSHOT_TTL_MS,
94
+ maxValueBytes: 128_000,
95
+ }),
96
+ );
97
+
98
+ export const buildGatewayRouteSnapshot = (input: GatewayRouteSnapshotInput): GatewayRouteSnapshot => ({
99
+ instanceId: input.instanceId,
100
+ baseUrl: input.baseUrl,
101
+ startedAt: input.startedAt,
102
+ updatedAt: Date.now(),
103
+ tableVersion: input.table.version,
104
+ tableBuiltAt: input.table.builtAt,
105
+ routeCount: input.table.routeCount,
106
+ routeHash: input.routeHash,
107
+ routeWarnings: input.routeWarnings,
108
+ routes: input.table.routes,
109
+ stats: {
110
+ totalRequests: input.stats.totalRequests,
111
+ noRouteCount: input.stats.noRouteCount,
112
+ byApp: [...input.stats.byApp.entries()].map(([appId, value]) => ({ appId, ...value })),
113
+ byRoute: [...input.stats.byRoute.entries()].map(([prefix, value]) => ({ prefix, ...value })),
114
+ },
115
+ });
116
+
117
+ export const publishGatewayRouteSnapshot = async (snapshot: GatewayRouteSnapshot): Promise<void> => {
118
+ await snapshots().upsert({ key: `instances/${snapshot.instanceId}`, value: snapshot });
119
+ };
120
+
121
+ export const removeGatewayRouteSnapshot = async (instanceId: string): Promise<void> => {
122
+ await snapshots().delete({ key: `instances/${instanceId}` });
123
+ };
124
+
125
+ export const listGatewayRouteSnapshots = async (): Promise<GatewayRouteSnapshot[]> => {
126
+ const snap = await snapshots().snapshot({ prefix: "instances/" });
127
+ return snap.entries.map((entry) => entry.value).sort((a, b) => a.instanceId.localeCompare(b.instanceId));
128
+ };
129
+
130
+ export const latestGatewayRouteSnapshot = async (): Promise<GatewayRouteSnapshot | null> => {
131
+ const all = await listGatewayRouteSnapshots();
132
+ return all.sort((a, b) => b.updatedAt - a.updatedAt)[0] ?? null;
133
+ };
134
+
135
+ export const gatewayTelemetryTopic = lazySync((sync) =>
136
+ sync.topic<GatewayTelemetryEvent>({
137
+ id: TOPIC_ID,
138
+ owner: GATEWAY_RESOURCE_OWNER,
139
+ retention: { maxAgeMs: TOPIC_RETENTION_MS, maxBytes: TOPIC_RETENTION_BYTES },
140
+ maxPayloadBytes: TOPIC_PAYLOAD_BYTES,
141
+ }),
142
+ );
143
+
144
+ export const GATEWAY_TELEMETRY_TENANT = TOPIC_TENANT;
145
+
146
+ const normalizeMethod = (method: string): string => method.toUpperCase().slice(0, 16);
147
+ const normalizeStatus = (status: number): number => (Number.isFinite(status) ? Math.max(0, Math.min(999, Math.round(status))) : 0);
148
+ const normalizeDuration = (durationMs: number): number => (Number.isFinite(durationMs) ? Math.max(0, Math.round(durationMs)) : 0);
149
+ const normalizeText = (value: string, fallback: string): string => {
150
+ const trimmed = value.trim();
151
+ return (trimmed.length ? trimmed : fallback).slice(0, 200);
152
+ };
153
+
154
+ export const publishRequestTelemetry = (event: Omit<GatewayTelemetryEvent, "v" | "kind" | "occurredAt">): void => {
155
+ const payload: GatewayTelemetryEvent = {
156
+ v: 1,
157
+ kind: "request",
158
+ appId: normalizeText(event.appId, "unknown"),
159
+ routePrefix: normalizeText(event.routePrefix, "(unknown)"),
160
+ pathTemplate: event.pathTemplate ? normalizeText(event.pathTemplate, "(unknown)") : null,
161
+ method: normalizeMethod(event.method),
162
+ status: normalizeStatus(event.status),
163
+ durationMs: normalizeDuration(event.durationMs),
164
+ errorKind: event.errorKind,
165
+ occurredAt: new Date().toISOString(),
166
+ };
167
+
168
+ void gatewayTelemetryTopic()
169
+ .publish({
170
+ tenantId: GATEWAY_TELEMETRY_TENANT,
171
+ orderingKey: payload.appId,
172
+ data: payload,
173
+ })
174
+ .catch((error) => {
175
+ const now = Date.now();
176
+ if (now - lastPublishErrorAt < DROP_LOG_INTERVAL_MS) return;
177
+ lastPublishErrorAt = now;
178
+ log.warn("Dropped gateway telemetry event", {
179
+ appId: payload.appId,
180
+ routePrefix: payload.routePrefix,
181
+ error: error instanceof Error ? error.message : String(error),
182
+ });
183
+ });
184
+ };
185
+
186
+ let lastPublishErrorAt = 0;
@@ -0,0 +1,18 @@
1
+ export const CLOUD_SESSION_JWKS_PATH = "/.well-known/cloud-session-jwks.json";
2
+ export const CLOUD_INVOCATION_JWKS_PATH = "/.well-known/cloud-invocation-jwks.json";
3
+ export const CLOUD_OAUTH_JWKS_PATH = "/.well-known/jwks.json";
4
+ export const CLOUD_SESSION_TOKEN_TYPE = "cloud-session+jwt";
5
+ export const CLOUD_SESSION_AUDIENCE = "cloud";
6
+ export const CLOUD_INVOCATION_TOKEN_TYPE = "cloud-invocation+jwt";
7
+ export const CLOUD_INVOCATION_PROTOCOL_VERSION = 1 as const;
8
+ export const CLOUD_INVOCATION_TOKEN_TTL_SECONDS = 30;
9
+ export const CLOUD_IDENTITY_ALGORITHM = "RS256";
10
+
11
+ export const IDENTITY_CLOCK_TOLERANCE_SECONDS = 30;
12
+ export const INVOCATION_CLOCK_TOLERANCE_SECONDS = 2;
13
+ export const IDENTITY_JWKS_MAX_AGE_SECONDS = 5 * 60;
14
+ export const IDENTITY_ACTIVATION_LEAD_MS = 10 * 60_000;
15
+ export const IDENTITY_SIGNING_CACHE_MS = 60_000;
16
+ export const IDENTITY_ROTATION_AGE_MS = 30 * 24 * 60 * 60_000;
17
+ export const IDENTITY_ROLLOUT_MARGIN_MS = 10 * 60_000;
18
+ export const IDENTITY_MAX_COMPACT_TOKEN_BYTES = 4 * 1024;
@@ -0,0 +1,52 @@
1
+ export {
2
+ CLOUD_INVOCATION_JWKS_PATH,
3
+ CLOUD_INVOCATION_PROTOCOL_VERSION,
4
+ CLOUD_INVOCATION_TOKEN_TTL_SECONDS,
5
+ CLOUD_INVOCATION_TOKEN_TYPE,
6
+ CLOUD_SESSION_AUDIENCE,
7
+ CLOUD_SESSION_JWKS_PATH,
8
+ CLOUD_SESSION_TOKEN_TYPE,
9
+ } from "./constants";
10
+ export { type ResolvedInvocationAuthority, resolveInvocationAuthority } from "./invocation-actor";
11
+ export { invocationAuthorityFromRequest } from "./invocation-authority";
12
+ export { capabilityInvocationOperation, searchInvocationOperation, widgetInvocationOperation } from "./invocation-operations";
13
+ export {
14
+ type CloudInvocationClaims,
15
+ clearInvocationVerifierCachesForTest,
16
+ type InvocationAuthority,
17
+ isInvocationJwtCandidate,
18
+ normalizeInvocationRequestId,
19
+ signInvocationToken,
20
+ verifyInvocationToken,
21
+ } from "./invocation-token";
22
+ export {
23
+ clearIdentityKeyCachesForTest,
24
+ getIdentitySigningKeyStatus,
25
+ type IdentitySigningKeyStatus,
26
+ initializeIdentityAuthority,
27
+ invalidateIdentitySignerCache,
28
+ listIdentityJwks,
29
+ type PreparedIdentitySigner,
30
+ prepareIdentitySigner,
31
+ revokeIdentitySigningKey,
32
+ rewrapIdentitySigningKeys,
33
+ runIdentityKeyMaintenance,
34
+ type SigningKeyPurpose,
35
+ startIdentityKeyMaintenance,
36
+ withActiveIdentitySigner,
37
+ } from "./key-ring";
38
+ export { identityMetrics } from "./metrics";
39
+ export { createIdentityPublicRoutes } from "./routes";
40
+ export {
41
+ type CloudSessionClaims,
42
+ clearSessionVerifierCachesForTest,
43
+ isSessionJwtCandidate,
44
+ signSessionToken,
45
+ verifySessionToken,
46
+ } from "./session-token";
47
+ export {
48
+ type AuthenticatedWorkload,
49
+ authenticateWorkloadCredential,
50
+ WORKLOAD_SCOPES,
51
+ type WorkloadScope,
52
+ } from "./workload-auth";
@@ -0,0 +1,120 @@
1
+ import { sql } from "bun";
2
+ import type { RequestActor } from "../../server/middleware/auth";
3
+ import type { AccessSubject } from "../../server/services/access";
4
+ import { isAccountCategoryAllowed } from "../account-category-policy";
5
+ import { isAccountExpired } from "../account-model";
6
+ import type { ServiceAccount } from "../service-accounts";
7
+ import { buildProjectedUser, loadCurrentUser, userProjectionSql } from "../session/user";
8
+ import type { CloudInvocationClaims } from "./invocation-token";
9
+ import { getIdentityRuntimeConfig } from "./runtime-config";
10
+
11
+ type ServiceAccountRow = Record<string, unknown> & {
12
+ service_account_id: string;
13
+ service_account_name: string;
14
+ service_account_kind: ServiceAccount["kind"];
15
+ service_account_status: ServiceAccount["status"];
16
+ delegated_user_id: string | null;
17
+ app_id: string | null;
18
+ resource_type: string | null;
19
+ resource_id: string | null;
20
+ service_account_created_by: string | null;
21
+ service_account_created_at: Date | string;
22
+ };
23
+
24
+ export type ResolvedInvocationAuthority = {
25
+ actor: RequestActor;
26
+ accessSubject: AccessSubject;
27
+ credentialKind: "invocation";
28
+ scopes: string[];
29
+ };
30
+
31
+ const provenance = (claims: CloudInvocationClaims): NonNullable<RequestActor["delegation"]> => ({
32
+ kind: "invocation",
33
+ callingAppId: claims.act.sub.slice("app:".length),
34
+ credentialKind: claims.credential_kind,
35
+ invocationId: claims.jti,
36
+ requestId: claims.request_id ?? null,
37
+ ...(claims.mandate_id
38
+ ? {
39
+ mandateId: claims.mandate_id,
40
+ mandateRevision: claims.mandate_revision,
41
+ workloadType: claims.workload_type,
42
+ workloadId: claims.workload_id,
43
+ }
44
+ : {}),
45
+ });
46
+
47
+ const mapServiceAccount = (row: ServiceAccountRow): ServiceAccount => ({
48
+ id: row.service_account_id,
49
+ name: row.service_account_name,
50
+ kind: row.service_account_kind,
51
+ status: row.service_account_status,
52
+ delegatedUserId: row.delegated_user_id,
53
+ appId: row.app_id,
54
+ resourceType: row.resource_type,
55
+ resourceId: row.resource_id,
56
+ createdBy: row.service_account_created_by,
57
+ createdAt: new Date(row.service_account_created_at).toISOString(),
58
+ });
59
+
60
+ export const resolveInvocationAuthority = async (
61
+ claims: CloudInvocationClaims,
62
+ query: typeof sql = sql,
63
+ groupsAdminOverride?: string[],
64
+ ): Promise<ResolvedInvocationAuthority | null> => {
65
+ const groupsAdmin = groupsAdminOverride ?? (await getIdentityRuntimeConfig()).groupsAdmin;
66
+ const delegation = provenance(claims);
67
+
68
+ if (claims.principal_type === "user") {
69
+ const user = await loadCurrentUser({ userId: claims.sub, groupsAdmin }, query);
70
+ if (!user || isAccountExpired(user.accountExpires)) return null;
71
+ if (!(await isAccountCategoryAllowed(user, query))) return null;
72
+ return {
73
+ actor: { kind: "user", user, delegation },
74
+ accessSubject: { type: "user", userId: user.id },
75
+ credentialKind: "invocation",
76
+ scopes: [...claims.scopes],
77
+ };
78
+ }
79
+
80
+ const rows = await query<ServiceAccountRow[]>`
81
+ SELECT
82
+ sa.id AS service_account_id,
83
+ sa.name AS service_account_name,
84
+ sa.kind AS service_account_kind,
85
+ sa.status AS service_account_status,
86
+ sa.delegated_user_id,
87
+ sa.app_id,
88
+ sa.resource_type,
89
+ sa.resource_id,
90
+ sa.created_by AS service_account_created_by,
91
+ sa.created_at AS service_account_created_at,
92
+ ${userProjectionSql(groupsAdmin)}
93
+ FROM auth.service_accounts sa
94
+ LEFT JOIN auth.users u ON u.id = sa.delegated_user_id
95
+ LEFT JOIN auth.user_ipa_data ui ON ui.user_id = u.id
96
+ WHERE sa.id = ${claims.sub}::uuid
97
+ AND sa.status = 'active'
98
+ `;
99
+ const row = rows[0];
100
+ if (!row) return null;
101
+
102
+ const serviceAccount = mapServiceAccount(row);
103
+ const delegatedUser = serviceAccount.delegatedUserId ? buildProjectedUser(row) : null;
104
+ if (serviceAccount.kind === "user_delegated" && (!delegatedUser || isAccountExpired(delegatedUser.accountExpires))) return null;
105
+ if (delegatedUser && !(await isAccountCategoryAllowed(delegatedUser, query))) return null;
106
+
107
+ const actor: RequestActor = {
108
+ kind: "service_account",
109
+ serviceAccount,
110
+ delegatedUser,
111
+ scopes: [...claims.scopes],
112
+ credentialId: claims.credential_id ?? null,
113
+ credentialExpiresAt: new Date(claims.exp * 1_000).toISOString(),
114
+ delegation,
115
+ };
116
+ const accessSubject: AccessSubject = delegatedUser
117
+ ? { type: "user", userId: delegatedUser.id, delegatedByServiceAccountId: serviceAccount.id }
118
+ : { type: "service_account", serviceAccountId: serviceAccount.id };
119
+ return { actor, accessSubject, credentialKind: "invocation", scopes: [...claims.scopes] };
120
+ };
@@ -0,0 +1,53 @@
1
+ import type { RequestAuthority } from "../../server/middleware/auth";
2
+ import type { InvocationAuthority } from "./invocation-token";
3
+
4
+ export const invocationAuthorityFromRequest = (authority: RequestAuthority): InvocationAuthority => {
5
+ if (authority.credentialKind === "invocation") {
6
+ throw new Error("A Cloud invocation cannot be exchanged for another invocation");
7
+ }
8
+
9
+ if (authority.actor.kind === "user") {
10
+ if (authority.accessSubject.type !== "user" || authority.accessSubject.userId !== authority.actor.user.id) {
11
+ throw new Error("User request authority is inconsistent");
12
+ }
13
+ if (authority.credentialKind === "api_key") throw new Error("An API key must act through a service account");
14
+ return {
15
+ sub: authority.actor.user.id,
16
+ principal_type: "user",
17
+ access_subject_type: "user",
18
+ access_subject_id: authority.actor.user.id,
19
+ credential_kind: authority.credentialKind,
20
+ scopes: [...new Set(authority.scopes)].sort(),
21
+ };
22
+ }
23
+
24
+ const actor = authority.actor;
25
+ if (actor.delegatedUser) {
26
+ if (authority.accessSubject.type !== "user" || authority.accessSubject.userId !== actor.delegatedUser.id) {
27
+ throw new Error("Delegated request authority is inconsistent");
28
+ }
29
+ return {
30
+ sub: actor.serviceAccount.id,
31
+ principal_type: "service_account",
32
+ access_subject_type: "user",
33
+ access_subject_id: actor.delegatedUser.id,
34
+ delegated_user_id: actor.delegatedUser.id,
35
+ credential_kind: authority.credentialKind,
36
+ ...(actor.credentialId ? { credential_id: actor.credentialId } : {}),
37
+ scopes: [...new Set(authority.scopes)].sort(),
38
+ };
39
+ }
40
+
41
+ if (authority.accessSubject.type !== "service_account" || authority.accessSubject.serviceAccountId !== actor.serviceAccount.id) {
42
+ throw new Error("Service-account request authority is inconsistent");
43
+ }
44
+ return {
45
+ sub: actor.serviceAccount.id,
46
+ principal_type: "service_account",
47
+ access_subject_type: "service_account",
48
+ access_subject_id: actor.serviceAccount.id,
49
+ credential_kind: authority.credentialKind,
50
+ ...(actor.credentialId ? { credential_id: actor.credentialId } : {}),
51
+ scopes: [...new Set(authority.scopes)].sort(),
52
+ };
53
+ };
@@ -0,0 +1,15 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ export const capabilityInvocationOperation = (kind: "queries" | "actions", capabilityId: string, review = false): string =>
4
+ kind === "queries"
5
+ ? `capability.query:${capabilityId}`
6
+ : review
7
+ ? `capability.action.review:${capabilityId}`
8
+ : `capability.action.run:${capabilityId}`;
9
+
10
+ export const searchInvocationOperation = "search.query" as const;
11
+ export const widgetInvocationOperation = (widgetId: string): string => `widget.read:${widgetId}`;
12
+
13
+ /** Bind an administrative invocation to its exact target route and HTTP method. */
14
+ export const syncInvocationOperation = (method: string, path: string): string =>
15
+ `sync.operation:${createHash("sha256").update(`${method.toUpperCase()}:${path}`).digest("hex")}`;