@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,801 @@
1
+ /**
2
+ * defineApp() — The single entry point for every cloud app.
3
+ *
4
+ * Merges SSR config, app meta, and server bootstrap into one call.
5
+ * Returns `{ ssr, plugin, config, meta, start }`.
6
+ */
7
+
8
+ import type { SsrConfig } from "@k2b/ssr";
9
+ import { createConfig as createSsrConfig } from "@k2b/ssr";
10
+ import { routes } from "@k2b/ssr/hono";
11
+ import type { Sync } from "@k2b/sync";
12
+ import { type Context, type Handler, Hono } from "hono";
13
+ import { generateSpecs } from "hono-openapi";
14
+ import { env } from "../config/env";
15
+ import type {
16
+ AppAdminNavigationGroup,
17
+ AppAppearance,
18
+ AppLifecycle,
19
+ AppMeta,
20
+ AppPresentationCatalog,
21
+ CloudLifecycleContext,
22
+ WidgetEndpoint,
23
+ } from "../contracts/app";
24
+ import { CAPABILITY_FRAMEWORK_ERROR_CODES, CAPABILITY_MAX_REQUEST_BYTES, type CapabilityDefinitions } from "../contracts/capabilities";
25
+ import { type BoundNotificationMap, bindNotificationDefinitions, type NotificationDefinitionMap } from "../contracts/notification-types";
26
+ import type { AppRegistryEntry } from "../contracts/registry";
27
+ import type { AppSettingsMap, KindToType } from "../contracts/settings-types";
28
+ import type { Role } from "../contracts/shared";
29
+ import type { HelpDefinition } from "../server/help";
30
+ import { getLocale, resolveLocale } from "../server/locale";
31
+ import { type AuthContext, auth } from "../server/middleware/auth";
32
+ import { requireInvocation } from "../server/middleware/invocation";
33
+ import { routeTemplate } from "../server/middleware/route-template";
34
+ import { runtime as runtimeMiddleware } from "../server/middleware/runtime";
35
+ import { settings as settingsMiddleware } from "../server/middleware/settings";
36
+ import {
37
+ capabilityInvocationOperation,
38
+ searchInvocationOperation,
39
+ syncInvocationOperation,
40
+ widgetInvocationOperation,
41
+ } from "../services/identity/invocation-operations";
42
+ import { logger } from "../services/logging";
43
+ import { startNotificationDefinitionRegistration } from "../services/notifications/catalog";
44
+ import { railPreferences } from "../services/rail-preferences";
45
+ import { get, loadCache as loadSettingsCache, set } from "../services/settings";
46
+ import { createSettingsAPI, type SettingsAPI } from "../services/settings/api";
47
+ import { registerSettings, toLegacySettingDefs } from "../services/settings/defaults";
48
+ import { createSyncOpsRoutes } from "../services/sync-ops";
49
+ import { capabilityMessages } from "../shared/capability-messages";
50
+ import { normalizeLocale } from "../shared/locale";
51
+ import { escapeHtml } from "../shared/markdown/shared";
52
+ import { themeBootstrapScript } from "../shared/theme";
53
+ import { appFaviconHref } from "./app-favicon";
54
+ import { compileAppPresentation } from "./app-presentation";
55
+ import { readBoundedJson } from "./bounded-json";
56
+ import { appRuntimeMetadata } from "./build-metadata";
57
+ import { compileCapabilities, invokeCompiledCapability, reviewCompiledCapability, serializeCapabilityProviderResult } from "./capabilities";
58
+ import { createHeartbeat } from "./heartbeat";
59
+ import { compileHelp } from "./help";
60
+ import { createPageResponses } from "./page-responses";
61
+ import { getProcessSync, startProcessSync } from "./process-sync";
62
+ import { APP_READINESS_PATH, appReadinessResponse } from "./readiness";
63
+ import { appRegistry, type CapabilityRegistryRecord, capabilityRegistry, helpRegistry } from "./registry";
64
+ import { ensureRuntimeWatcher, getCurrentRuntime, stopRuntimeWatcher } from "./runtime-watcher";
65
+ import { servePublicAsset } from "./static-assets";
66
+ import { createStatusPreservingSsrHandler } from "./status-preserving-ssr";
67
+
68
+ /** Cache-busting version stamp — changes on every server start / rebuild. */
69
+ const v = Date.now();
70
+
71
+ export type PageOptions = {
72
+ title?: string;
73
+ description?: string;
74
+ theme?: "light" | "dark";
75
+ /** Framework-resolved BCP 47 document language — always the canonical request locale, never a page override. */
76
+ lang?: string;
77
+ };
78
+
79
+ // ── Public types ────────────────────────────────────────────────────────────
80
+
81
+ /**
82
+ * App definition options.
83
+ *
84
+ * `S` is the inferred per-app settings map (see `AppSettingsMap`). Apps that
85
+ * declare `settings: { ... } as const` get S inferred to the literal shape;
86
+ * `AppContext<typeof app>` then exposes the typed snapshot on `c.get("settings")`.
87
+ *
88
+ * Apps that omit `settings` get S = {} (no own settings — only core's are
89
+ * available in their snapshot, populated by core's own defineApp.settings).
90
+ */
91
+ export type AppOptions<S extends AppSettingsMap = {}, N extends NotificationDefinitionMap = {}, AppId extends string = string> = {
92
+ id: AppId;
93
+ name: string;
94
+ icon: string;
95
+ description: string;
96
+ /** Optional localized overlays for app-owned presentation metadata. */
97
+ presentation?: AppPresentationCatalog;
98
+ appearance?: AppAppearance;
99
+ /** URL prefix for SSR asset isolation. Omit for the global `/_ssr/` path (core). */
100
+ basePath?: string;
101
+ /** Base URL as seen by other containers (e.g. "http://app-notebooks:3000"). */
102
+ baseUrl: string;
103
+ adminHref?: string;
104
+ /** Multi-link admin navigation contributed by this app. */
105
+ adminNav?: ReadonlyArray<{
106
+ id?: string;
107
+ label: string;
108
+ links: ReadonlyArray<{ label: string; href: string; icon: string }>;
109
+ }>;
110
+ nav?: {
111
+ href: string;
112
+ match?: string;
113
+ section: "primary" | "more" | "hidden";
114
+ requiresAuth?: boolean;
115
+ requiresRoles?: Role[];
116
+ };
117
+ /**
118
+ * Settings owned by this app, declared as a map of dotted-key → definition.
119
+ *
120
+ * Example: `{ "files.filegate_url": { kind: "url", default: "" } }`.
121
+ *
122
+ * These keys are exposed as a typed nested snapshot on `c.get("settings")`
123
+ * for any Hono route using `Hono<AppContext<typeof app>>`. Writes go through
124
+ * `app.settings.set(key, value)` (also typed). The runtime registry
125
+ * (`SETTINGS_MAP` in `services/settings/defaults.ts`) is populated from
126
+ * this map automatically on `defineApp()` call.
127
+ */
128
+ settings?: S;
129
+ /** Notification kinds owned by this app, conventionally imported from `src/notifications.ts`. */
130
+ notifications?: N;
131
+ /**
132
+ * Legal/info links contributed by this app — aggregated app-wide via
133
+ * `listLegalLinks()` and rendered in login footer, app Footer, rail more
134
+ * dropdown. Each app contributes its own (e.g. core owns
135
+ * Imprint/Privacy/Terms; faq owns FAQ). KISS: no `external` flag, links
136
+ * always open in a new tab from the login footer.
137
+ */
138
+ legalLinks?: ReadonlyArray<{ label: string; href: string; icon?: string }>;
139
+ /**
140
+ * Dashboard widget endpoints this app exposes. Each entry references an
141
+ * HTTP path on this app that returns a `WidgetResponse`. The dashboard
142
+ * reaches the declared handler through Core; the endpoint remains
143
+ * responsible for permission gating (200 = render, 403 = unavailable at
144
+ * the user's access level, 204 = no content).
145
+ */
146
+ widgets?: ReadonlyArray<WidgetEndpoint>;
147
+ /**
148
+ * Top-level URL prefixes the gateway should route to this app.
149
+ *
150
+ * Standard apps follow a four-prefix convention:
151
+ * `/api/<id>` — widget, admin, ws, crud — everything HTTP API
152
+ * `/app/<id>` — user-facing SSR pages
153
+ * `/admin/<id>` — admin SSR pages
154
+ * `/public/<id>` — built CSS and other static assets
155
+ *
156
+ * Apps with non-standard URLs (core's `/auth`, `/me`, `/legal/*`, `/impressum`;
157
+ * oauth's `/oauth`, `/.well-known/...`) list whatever
158
+ * top-level paths they own. The gateway is dumb — it just builds a
159
+ * prefix-trie from these strings.
160
+ */
161
+ routes: readonly string[];
162
+ /**
163
+ * Gateway-relative URL at which this app's OpenAPI 3.x JSON spec is
164
+ * served, e.g. `"/api/notebooks/openapi.json"`. Opt-in: only set this
165
+ * for apps whose api router is documented with `middleware.openapi()`
166
+ * (i.e. `describeRoute()`) and worth surfacing in the api-docs aggregator.
167
+ *
168
+ * Pair this with `app.start({ openapi: <api router> })` — `defineApp`
169
+ * generates the spec from that router at boot, mounts it on the
170
+ * framework server (before the user's fetch, so it bypasses any
171
+ * auth/rate-limit middleware), and advertises the URL via the registry
172
+ * so `app-api-docs` picks it up automatically.
173
+ *
174
+ * The path must be reachable through the gateway — usually that means
175
+ * the standard form `"/api/<id>/openapi.json"` (covered by the
176
+ * `/api/<id>` entry in `routes`).
177
+ */
178
+ openapi?: string;
179
+ /**
180
+ * Project root used by the SSR plugin to discover island/client files.
181
+ * Defaults to `process.cwd()`. Override only if you run the entrypoint
182
+ * from a directory other than the project root.
183
+ */
184
+ appRoot?: string;
185
+ };
186
+
187
+ export type StartOptions = {
188
+ /**
189
+ * Web-standard fetch handler. Mounted at `/` of the app's container.
190
+ * Typically you pass a Hono instance's `.fetch`:
191
+ *
192
+ * const router = new Hono<AuthContext>()
193
+ * .use("*", middleware.runtime())
194
+ * .use("*", middleware.settings())
195
+ * .route("/api/<id>", apiRoutes)
196
+ * .route("/app/<id>", pageRoutes);
197
+ *
198
+ * app.start({ fetch: router.fetch });
199
+ *
200
+ * The framework owns `/_ssr/*`, `/public/*`, and the versioned internal
201
+ * capability and widget endpoints when their handlers are declared, and
202
+ * registers them before this fetch — they take precedence over any
203
+ * catch-all the app might register.
204
+ */
205
+ fetch: (req: Request, env?: unknown) => Response | Promise<Response>;
206
+ /**
207
+ * Hono router to scan for OpenAPI route metadata. When set together with
208
+ * `defineApp({ openapi: "..." })`, the framework generates an OpenAPI
209
+ * spec from this router and mounts it at the configured URL on the
210
+ * framework server (before the user's fetch, so the spec stays public).
211
+ *
212
+ * Pass the BARE api router — the one with `describeRoute()` annotations.
213
+ * `generateSpecs` walks the route tree without executing middleware, so
214
+ * the inner auth/rate-limit `.use(...)` calls don't matter for spec
215
+ * generation; they only run if the router is hit by an actual request.
216
+ */
217
+ openapi?: Hono<any>;
218
+ lifecycle?: AppLifecycle;
219
+ capabilities?: CapabilityDefinitions;
220
+ /**
221
+ * Handlers for declared dashboard widgets, keyed by widget id. The
222
+ * framework exposes these only on its invocation-authenticated internal
223
+ * route; applications may keep mounting the same handlers on their public
224
+ * compatibility paths during the rolling migration.
225
+ */
226
+ widgets?: Readonly<Record<string, Handler<AuthContext>>>;
227
+ help?: HelpDefinition;
228
+ port?: number;
229
+ skipSetup?: boolean;
230
+ };
231
+
232
+ export type StartResult = {
233
+ hostname: "0.0.0.0";
234
+ port: number;
235
+ development: boolean;
236
+ fetch: Hono["fetch"];
237
+ };
238
+
239
+ export type AppDefinition<S extends AppSettingsMap = {}, N extends NotificationDefinitionMap = {}, AppId extends string = string> = {
240
+ // Bind the generic explicitly — without it, ssr collapses to the constraint
241
+ // `object` and apps lose the typed `c.get("page")` (title/description/theme).
242
+ ssr: ReturnType<typeof createStatusPreservingSsrHandler<PageOptions>> & ReturnType<typeof createPageResponses>;
243
+ plugin: () => import("bun").BunPlugin;
244
+ config: SsrConfig;
245
+ meta: AppMeta;
246
+ baseUrl: string;
247
+ start: (opts: StartOptions) => Promise<StartResult>;
248
+ /**
249
+ * The process-wide @k2b/sync instance, connected and ready once `start()`
250
+ * resolved. Reading it earlier throws: declare primitives lazily with
251
+ * `lazySync((sync) => sync.job(...))` and use them from `lifecycle.start`
252
+ * or request handlers.
253
+ */
254
+ readonly sync: Sync;
255
+ /**
256
+ * Phantom field — type-only carrier for the per-app settings shape. Always
257
+ * `undefined` at runtime; do not read or assign. Used by `AppContext<App>`
258
+ * to extract the inferred settings map via `App["_settings"]`.
259
+ */
260
+ readonly _settings: S;
261
+ /**
262
+ * Typed async settings API for this app. Keys constrained to those declared
263
+ * in `defineApp({ settings: ... })`. Backed by Redis cache-aside (see store.ts).
264
+ *
265
+ * Use for read/write outside of request-scoped sync access. Inside HTTP
266
+ * handlers, prefer `c.get("settings").x.y` (the per-request snapshot, sync,
267
+ * frozen for the duration of the request).
268
+ */
269
+ readonly settings: SettingsAPI<{ [K in keyof S]: KindToType<S[K]["kind"]> }>;
270
+ /** Typed notification descriptors declared by this app. */
271
+ readonly notifications: BoundNotificationMap<AppId, N>;
272
+ };
273
+
274
+ // ── Implementation ──────────────────────────────────────────────────────────
275
+
276
+ export const defineApp = <
277
+ const S extends AppSettingsMap = {},
278
+ const N extends NotificationDefinitionMap = {},
279
+ const AppId extends string = string,
280
+ >(
281
+ opts: AppOptions<S, N, AppId>,
282
+ ): AppDefinition<S, N, AppId> => {
283
+ const isDevelopment = process.env.NODE_ENV === "development";
284
+ const notifications = bindNotificationDefinitions(opts.id, opts.notifications);
285
+
286
+ // ── 0. Register declared settings into the runtime registry ──────────
287
+ // SETTINGS_MAP is the single source of truth for validation in store.ts
288
+ // (writeKey checks SETTINGS_MAP.get(key)) and for snapshot.ts (allKnownKeys
289
+ // returns SETTINGS.map(d => d.key)). Without this registration, app-declared
290
+ // settings would be type-known but runtime-unknown.
291
+ if (opts.settings) {
292
+ registerSettings(toLegacySettingDefs(opts.settings as Record<string, unknown>, opts.presentation?.baseLocale ?? "en"));
293
+ }
294
+
295
+ // ── 1. SSR config ─────────────────────────────────────────────────────
296
+ const { config, plugin, html } = createSsrConfig<PageOptions>({
297
+ dev: isDevelopment,
298
+ verbose: true,
299
+ rootDir: opts.appRoot ?? process.cwd(),
300
+ basePath: opts.basePath,
301
+ template: ({ body, scripts, title, description, theme, lang }) => {
302
+ const themeFixed = theme !== undefined;
303
+ return `<!DOCTYPE html>
304
+ <html lang="${normalizeLocale(lang)}" class="${theme ?? "light"}"${themeFixed ? " data-theme-fixed" : ""}>
305
+ <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
306
+ <meta name="view-transition" content="same-origin">
307
+ <title>${escapeHtml(title ?? "Cloud")}</title>
308
+ <meta name="description" content="${escapeHtml(description ?? "Cloud workspace")}">
309
+ <meta name="theme-color" content="#09090b">
310
+ <meta name="mobile-web-app-capable" content="yes">
311
+ <link rel="icon" href="${appFaviconHref(opts.id, v)}">
312
+ <style data-cloud-css-layers>@layer theme, base, components, utilities;</style>
313
+ <link rel="preload" href="/public/tabler-icons.woff2" as="font" type="font/woff2" crossorigin>
314
+ <link rel="stylesheet" href="/public/fonts.css?v=${v}">
315
+ <link rel="stylesheet" href="/public/tabler-icons.css?v=${v}">
316
+ <link rel="stylesheet" href="/public/${opts.id}/app.css?v=${v}">
317
+ <link rel="stylesheet" href="/public/global.css?v=${v}">
318
+ <script>${themeBootstrapScript}</script>
319
+ </head>
320
+ <body class="k2b-ui" data-k2b-app-workspace-controller="global">
321
+ ${body}
322
+ </body>
323
+ ${scripts}
324
+ </html>`;
325
+ },
326
+ });
327
+
328
+ // Pass PageOptions explicitly so c.get("page") in apps' SSR handlers is
329
+ // typed as Partial<PageOptions> (with title/description/theme), not the
330
+ // bare `object` fallback the constraint would otherwise produce.
331
+ //
332
+ // The finalize hook resolves the request locale for `<html lang>` after all
333
+ // route middlewares ran (so the settings snapshot is present), keeping the
334
+ // document language request-scoped without per-page plumbing. It assigns
335
+ // unconditionally: `<html lang>`, the Layout LocaleProvider, and
336
+ // `getDateConfig` share one canonical `getLocale(c)` that page handlers
337
+ // cannot override.
338
+ const ssr = Object.assign(
339
+ createStatusPreservingSsrHandler<PageOptions>(html, async (c) => {
340
+ c.get("page").lang = getLocale(c);
341
+ const user = c.get("user");
342
+ if (user) c.set("railPreferences", await railPreferences.get(user.id));
343
+ }),
344
+ createPageResponses(html),
345
+ );
346
+
347
+ // ── 2. Meta ───────────────────────────────────────────────────────────
348
+ const baseMeta: AppMeta = {
349
+ id: opts.id,
350
+ name: opts.name,
351
+ icon: opts.icon,
352
+ description: opts.description,
353
+ appearance: opts.appearance,
354
+ adminHref: opts.adminHref,
355
+ adminNav: opts.adminNav?.map(
356
+ (group): AppAdminNavigationGroup => ({
357
+ id: group.id,
358
+ label: group.label,
359
+ links: group.links.map((link) => ({ ...link })),
360
+ }),
361
+ ),
362
+ routes: [...opts.routes],
363
+ nav: opts.nav,
364
+ legalLinks: opts.legalLinks ? [...opts.legalLinks] : undefined,
365
+ widgets: opts.widgets ? opts.widgets.map((w) => ({ ...w })) : undefined,
366
+ settingKeys: opts.settings ? Object.keys(opts.settings) : undefined,
367
+ openapi: opts.openapi,
368
+ };
369
+ const meta: AppMeta = { ...baseMeta, presentation: compileAppPresentation(baseMeta, opts.presentation) };
370
+
371
+ // ── 3. start() — builds and boots the Hono server ────────────────────
372
+ const start = async (startOpts: StartOptions): Promise<StartResult> => {
373
+ const port = startOpts.port ?? 3000;
374
+ const baseUrl = opts.baseUrl;
375
+ const log = logger("app");
376
+
377
+ // Settings are encrypted at rest, so every container needs the secret. It
378
+ // is only read on the first decrypt, which meant a container without it
379
+ // booted cleanly and failed much later with an unrelated-looking error.
380
+ if (!env.APP_SECRET.trim()) {
381
+ throw new Error(
382
+ `APP_SECRET is not set (app "${meta.id}"). Settings are encrypted at rest and every app in the ` +
383
+ "deployment must share the same value; without it the first settings read fails.",
384
+ );
385
+ }
386
+
387
+ // One NATS connection and one Sync instance per process. Ready before
388
+ // anything registers, so a discoverable app can always serve sync work.
389
+ const processSync = await startProcessSync({ application: meta.id });
390
+ const cleanup: Array<() => void | Promise<void>> = [() => processSync.stop()];
391
+ let cleanupPromise: Promise<void> | undefined;
392
+ const stopStartedResources = (): Promise<void> =>
393
+ (cleanupPromise ??= (async () => {
394
+ for (const stop of cleanup.toReversed()) {
395
+ try {
396
+ await stop();
397
+ } catch (error) {
398
+ log.error("Application cleanup failed", { appId: meta.id, error: error instanceof Error ? error.message : String(error) });
399
+ }
400
+ }
401
+ })());
402
+ try {
403
+ const startedAt = Date.now();
404
+
405
+ // OpenAPI advertised in the registry only when there's a router to
406
+ // derive the spec from. The mount block lower down uses the same
407
+ // flag so the registry never points at a URL that 404s.
408
+ const advertiseOpenapi = !!(opts.openapi && startOpts.openapi);
409
+ const compiledCapabilities = startOpts.capabilities ? compileCapabilities(meta.id, startOpts.capabilities) : undefined;
410
+ const compiledHelp = startOpts.help
411
+ ? compileHelp({
412
+ appId: meta.id,
413
+ appName: meta.name,
414
+ appIcon: meta.icon,
415
+ basePath: opts.basePath,
416
+ definition: startOpts.help,
417
+ })
418
+ : undefined;
419
+
420
+ // Registry entry
421
+ const entry: AppRegistryEntry = {
422
+ id: meta.id,
423
+ name: meta.name,
424
+ icon: meta.icon,
425
+ description: meta.description,
426
+ presentation: meta.presentation,
427
+ appearance: meta.appearance,
428
+ baseUrl,
429
+ runtime: appRuntimeMetadata,
430
+ startedAt,
431
+ routes: [...meta.routes],
432
+ nav:
433
+ meta.nav || meta.adminHref
434
+ ? {
435
+ href: meta.nav?.href ?? "",
436
+ match: meta.nav?.match,
437
+ section: meta.nav?.section ?? "hidden",
438
+ requiresAuth: meta.nav?.requiresAuth,
439
+ requiresRoles: meta.nav?.requiresRoles,
440
+ adminHref: meta.adminHref,
441
+ }
442
+ : undefined,
443
+ adminNav: meta.adminNav?.map((group) => ({
444
+ id: group.id,
445
+ label: group.label,
446
+ links: group.links.map((link) => ({ ...link })),
447
+ })),
448
+ capabilities: compiledCapabilities
449
+ ? {
450
+ protocolVersion: compiledCapabilities.manifest.protocolVersion,
451
+ manifestHash: compiledCapabilities.manifest.manifestHash,
452
+ }
453
+ : undefined,
454
+ help: compiledHelp?.summary,
455
+ legalLinks: meta.legalLinks ? meta.legalLinks.map((l) => ({ ...l })) : undefined,
456
+ widgets: meta.widgets ? meta.widgets.map((w) => ({ ...w })) : undefined,
457
+ settingKeys: meta.settingKeys ? [...meta.settingKeys] : undefined,
458
+ openapi: advertiseOpenapi ? opts.openapi : undefined,
459
+ };
460
+
461
+ // Heartbeat
462
+ const heartbeat = createHeartbeat(meta.id, entry, {
463
+ registry: appRegistry(),
464
+ onError: (error) =>
465
+ log.error("Registry heartbeat failed", {
466
+ appId: meta.id,
467
+ error: error instanceof Error ? error.message : String(error),
468
+ }),
469
+ onStale: (error) => {
470
+ log.error("Registry heartbeat could not renew the app lease; restarting", {
471
+ appId: meta.id,
472
+ error: error instanceof Error ? error.message : String(error),
473
+ });
474
+ process.exit(1);
475
+ },
476
+ });
477
+ const capabilityEntry: CapabilityRegistryRecord | undefined = compiledCapabilities
478
+ ? {
479
+ appId: meta.id,
480
+ manifest: compiledCapabilities.manifest,
481
+ presentation: compiledCapabilities.presentation,
482
+ }
483
+ : undefined;
484
+ const capabilityHeartbeat = capabilityEntry
485
+ ? createHeartbeat(meta.id, capabilityEntry, {
486
+ key: `capabilities/${meta.id}`,
487
+ registry: capabilityRegistry(),
488
+ onError: (error) =>
489
+ log.error("Capability registry heartbeat failed", {
490
+ appId: meta.id,
491
+ error: error instanceof Error ? error.message : String(error),
492
+ }),
493
+ onStale: (error) => {
494
+ log.error("Capability registry lease expired; restarting", {
495
+ appId: meta.id,
496
+ error: error instanceof Error ? error.message : String(error),
497
+ });
498
+ process.exit(1);
499
+ },
500
+ })
501
+ : undefined;
502
+ const helpHeartbeat = compiledHelp
503
+ ? createHeartbeat(meta.id, compiledHelp.registryEntry, {
504
+ key: `help/${meta.id}`,
505
+ registry: helpRegistry(),
506
+ onError: (error) =>
507
+ log.error("Help registry heartbeat failed", {
508
+ appId: meta.id,
509
+ error: error instanceof Error ? error.message : String(error),
510
+ }),
511
+ onStale: (error) => {
512
+ log.error("Help registry lease expired; restarting", {
513
+ appId: meta.id,
514
+ error: error instanceof Error ? error.message : String(error),
515
+ });
516
+ process.exit(1);
517
+ },
518
+ })
519
+ : undefined;
520
+ cleanup.push(async () => {
521
+ await helpHeartbeat?.stop();
522
+ });
523
+ cleanup.push(async () => {
524
+ await capabilityHeartbeat?.stop();
525
+ });
526
+ cleanup.push(() => heartbeat.stop());
527
+
528
+ // Runtime context — start the registry watcher so middleware.runtime() and
529
+ // the lifecycle context below see populated cluster state. Idempotent: the
530
+ // watcher is a module-level singleton, only one runs per process.
531
+ cleanup.push(() => stopRuntimeWatcher());
532
+ await ensureRuntimeWatcher();
533
+
534
+ // Build Hono server. Framework owns these mounts (registered first so
535
+ // they take precedence over any catch-all in the user's fetch):
536
+ // /_ssr/* island chunks (SSR adapter)
537
+ // /public/* serveStatic + terminal 404
538
+ // /api/_internal/capabilities/v1/* when capabilities are declared
539
+ // /api/_internal/widgets/v1/* when widget handlers are bound
540
+ // /_internal/sync/* process-local sync operations (admin only)
541
+ // <opts.openapi> OpenAPI JSON spec, when both opts.openapi
542
+ // and startOpts.openapi are set
543
+ const ssrMountPath = config.basePath ? `${config.basePath}/_ssr` : "/_ssr";
544
+
545
+ // Framework-owned mounts answer before the app's router ever runs, so the
546
+ // app middleware cannot report their template. Without this, every hashed
547
+ // island chunk would land in telemetry as its own route.
548
+ const server = new Hono<AuthContext>()
549
+ .use("*", routeTemplate)
550
+ .get(APP_READINESS_PATH, () => appReadinessResponse(meta.id))
551
+ .route(ssrMountPath, routes(config))
552
+ .all(`${ssrMountPath}/*`, (c) => c.notFound())
553
+ .all("/public/*", servePublicAsset(isDevelopment))
554
+ .use(
555
+ "/_internal/sync/*",
556
+ requireInvocation((c) => ({
557
+ targetAppId: meta.id,
558
+ operation: syncInvocationOperation(c.req.method, new URL(c.req.url).pathname.slice("/_internal/sync".length)),
559
+ schemaHash: null,
560
+ })),
561
+ )
562
+ .use("/_internal/sync/*", auth.requireOAuthScope("admin"))
563
+ .use("/_internal/sync/*", async (c, next) => {
564
+ if (!c.get("user")?.roles.includes("admin")) return c.json({ message: "Insufficient permissions" }, 403);
565
+ return next();
566
+ })
567
+ .route("/_internal/sync", createSyncOpsRoutes(getProcessSync));
568
+
569
+ if (compiledHelp) {
570
+ const pageBase = compiledHelp.summary.pageBase;
571
+ const centralPageBase = `/help/apps/${encodeURIComponent(meta.id)}`;
572
+ server.get(pageBase, (c) => c.redirect(centralPageBase, 302));
573
+ server.get(`${pageBase}/:topic`, (c) => c.redirect(`${centralPageBase}/${encodeURIComponent(c.req.param("topic"))}`, 302));
574
+ }
575
+
576
+ if (compiledCapabilities) {
577
+ const invoke = async (c: Context<AuthContext>, kind: "query" | "action" | "review") => {
578
+ // Framework-owned endpoints run before the app's settings middleware,
579
+ // so the operator default is read directly instead of via a snapshot.
580
+ const requestLocale = resolveLocale(c.req.raw.headers, await get<string>("app.locale"));
581
+ const messages = capabilityMessages(requestLocale);
582
+ const parsedBody = await readBoundedJson(c.req.raw, CAPABILITY_MAX_REQUEST_BYTES);
583
+ if (!parsedBody.ok) {
584
+ const message = parsedBody.reason === "too_large" ? messages.requestTooLarge : messages.requestBodyJson;
585
+ return c.json({ code: CAPABILITY_FRAMEWORK_ERROR_CODES.validationFailed, message }, 400);
586
+ }
587
+ if (
588
+ typeof parsedBody.data !== "object" ||
589
+ parsedBody.data === null ||
590
+ Array.isArray(parsedBody.data) ||
591
+ !Object.hasOwn(parsedBody.data, "input") ||
592
+ Object.keys(parsedBody.data).length !== 1
593
+ ) {
594
+ return c.json(
595
+ {
596
+ code: CAPABILITY_FRAMEWORK_ERROR_CODES.validationFailed,
597
+ message: messages.requestInputOnly,
598
+ },
599
+ 400,
600
+ );
601
+ }
602
+ const body = parsedBody.data as { input: unknown };
603
+ const actor = c.get("actor");
604
+ const user = actor.kind === "user" ? actor.user : actor.delegatedUser;
605
+ const idempotencyKey = c.req.header("idempotency-key") || undefined;
606
+ const invocation = {
607
+ compiled: compiledCapabilities,
608
+ localId: c.req.param("capabilityId") ?? "",
609
+ input: body.input,
610
+ expectedSchemaHash: c.req.header("x-cloud-capability-schema-hash") ?? null,
611
+ context: {
612
+ actor,
613
+ accessSubject: c.get("accessSubject"),
614
+ user,
615
+ idempotencyKey,
616
+ locale: requestLocale,
617
+ signal: c.req.raw.signal,
618
+ },
619
+ onUnexpectedError: (error: unknown) =>
620
+ log.error(kind === "review" ? "Capability review failed" : "Capability execution failed", {
621
+ appId: meta.id,
622
+ kind,
623
+ capabilityId: c.req.param("capabilityId") ?? "",
624
+ error: error instanceof Error ? error.message : String(error),
625
+ }),
626
+ };
627
+ const result =
628
+ kind === "review" ? await reviewCompiledCapability(invocation) : await invokeCompiledCapability({ ...invocation, kind });
629
+ const operation = kind === "action" ? compiledCapabilities.actions.get(invocation.localId) : undefined;
630
+ const serialized = serializeCapabilityProviderResult(result, {
631
+ nonIdempotentAction: kind === "action" && operation?.manifest.idempotency === "none",
632
+ });
633
+ return new Response(serialized.body, {
634
+ status: serialized.status,
635
+ headers: { "content-type": "application/json" },
636
+ });
637
+ };
638
+ const capabilityAuth = (kind: "queries" | "actions", review = false) =>
639
+ requireInvocation((c) => {
640
+ const localId = c.req.param("capabilityId") ?? "";
641
+ const operation = kind === "queries" ? compiledCapabilities.queries.get(localId) : compiledCapabilities.actions.get(localId);
642
+ if (!operation) return null;
643
+ const requestedOperation = c.req.header("x-cloud-invocation-operation");
644
+ const invocationOperation =
645
+ kind === "queries" &&
646
+ requestedOperation === searchInvocationOperation &&
647
+ "universalSearch" in operation.manifest &&
648
+ operation.manifest.universalSearch
649
+ ? searchInvocationOperation
650
+ : capabilityInvocationOperation(kind, localId, review);
651
+ return {
652
+ targetAppId: meta.id,
653
+ operation: invocationOperation,
654
+ schemaHash: operation.manifest.schemaHash,
655
+ };
656
+ });
657
+ const capabilityReadScope = auth.requireOAuthScope("read", "admin");
658
+ const capabilityWriteScope = auth.requireOAuthScope("write", "admin");
659
+ server.post("/api/_internal/capabilities/v1/queries/:capabilityId", capabilityAuth("queries"), capabilityReadScope, (c) =>
660
+ invoke(c, "query"),
661
+ );
662
+ server.post("/api/_internal/capabilities/v1/actions/:capabilityId", capabilityAuth("actions"), capabilityWriteScope, (c) =>
663
+ invoke(c, "action"),
664
+ );
665
+ server.post(
666
+ "/api/_internal/capabilities/v1/actions/:capabilityId/review",
667
+ capabilityAuth("actions", true),
668
+ capabilityReadScope,
669
+ (c) => invoke(c, "review"),
670
+ );
671
+ }
672
+
673
+ if (startOpts.widgets) {
674
+ const declaredWidgetIds = new Set(meta.widgets?.map((widget) => widget.id) ?? []);
675
+ for (const widgetId of Object.keys(startOpts.widgets)) {
676
+ if (!declaredWidgetIds.has(widgetId)) throw new Error(`Widget handler "${widgetId}" is not declared by app "${meta.id}"`);
677
+ }
678
+ server.get(
679
+ "/api/_internal/widgets/v1/:widgetId",
680
+ requireInvocation((c) => {
681
+ const widgetId = c.req.param("widgetId") ?? "";
682
+ if (!declaredWidgetIds.has(widgetId) || !startOpts.widgets?.[widgetId]) return null;
683
+ return { targetAppId: meta.id, operation: widgetInvocationOperation(widgetId), schemaHash: null };
684
+ }),
685
+ auth.requireOAuthScope("read", "admin"),
686
+ runtimeMiddleware(),
687
+ settingsMiddleware(),
688
+ async (c) => {
689
+ const handler = startOpts.widgets?.[c.req.param("widgetId") ?? ""];
690
+ return handler ? handler(c, async () => {}) : c.json({ message: "Widget not found" }, 404);
691
+ },
692
+ );
693
+ }
694
+
695
+ // OpenAPI spec mount. Registered on the framework server (before the
696
+ // user-fetch catch-all below) so it bypasses any auth / rate-limit
697
+ // middleware on the api router — the spec must stay reachable without
698
+ // a session for `app-api-docs` to render it.
699
+ //
700
+ // The `servers` override is load-bearing: hono-openapi walks the
701
+ // BARE api router and emits paths relative to its own root (e.g.
702
+ // `/{id}`, `/{id}/notes`), without the `/api/<id>` prefix it ends
703
+ // up under in the user's outer router. We derive that prefix from
704
+ // `opts.openapi` (everything before the trailing `/openapi.json`)
705
+ // so combined Scalar URLs resolve to the real public paths.
706
+ if (advertiseOpenapi) {
707
+ const apiPrefix = opts.openapi!.replace(/\/openapi\.json$/, "") || "/";
708
+ const spec = await generateSpecs(startOpts.openapi!, {
709
+ documentation: {
710
+ info: {
711
+ title: meta.name,
712
+ version: "0.0.1",
713
+ description: meta.description,
714
+ },
715
+ servers: [{ url: apiPrefix }],
716
+ },
717
+ });
718
+ server.get(opts.openapi!, (c) => c.json(spec));
719
+ }
720
+
721
+ // User's fetch handles everything else. The framework doesn't inject any
722
+ // context vars here — the user's router is expected to register the
723
+ // middlewares it needs (middleware.runtime, middleware.settings, …).
724
+ //
725
+ // env is threaded through so Bun-specific helpers inside the user's
726
+ // router still work — most importantly `upgradeWebSocket` from hono/bun,
727
+ // which reads the Bun server off `c.env`.
728
+ server.all("*", (c) => Promise.resolve(startOpts.fetch(c.req.raw, c.env)));
729
+
730
+ // Lifecycle
731
+ const cloudCtx: CloudLifecycleContext = {
732
+ logger,
733
+ settings: { get, set },
734
+ runtime: getCurrentRuntime(),
735
+ sync: processSync.sync,
736
+ };
737
+
738
+ let stopNotificationRegistration: (() => void) | undefined;
739
+ cleanup.push(() => stopNotificationRegistration?.());
740
+ const stopLifecycle = startOpts.lifecycle?.stop;
741
+ if (stopLifecycle) cleanup.push(() => stopLifecycle(cloudCtx));
742
+
743
+ if (!startOpts.skipSetup && startOpts.lifecycle?.setup) {
744
+ log.info(`Setup: ${meta.id}`);
745
+ await startOpts.lifecycle.setup(cloudCtx);
746
+ }
747
+
748
+ stopNotificationRegistration = await startNotificationDefinitionRegistration(meta.id, notifications);
749
+
750
+ await loadSettingsCache();
751
+
752
+ if (startOpts.lifecycle?.start) {
753
+ log.info(`Start: ${meta.id}`);
754
+ await startOpts.lifecycle.start(cloudCtx);
755
+ }
756
+
757
+ // Provision resources declared during application setup before exposing readiness.
758
+ await processSync.sync.ready();
759
+
760
+ // Only advertise after setup, workers, and their declared resources are ready.
761
+ // Publish supporting entries first; the app entry makes them discoverable.
762
+ await helpHeartbeat?.start();
763
+ await capabilityHeartbeat?.start();
764
+ await heartbeat.start();
765
+ log.info(`Registered "${meta.id}"`, { baseUrl });
766
+
767
+ // Graceful shutdown
768
+ let stopping = false;
769
+ const shutdown = async () => {
770
+ if (stopping) return;
771
+ stopping = true;
772
+ log.info(`Stopping: ${meta.id}`);
773
+ await stopStartedResources();
774
+ };
775
+
776
+ process.on("SIGTERM", () => void shutdown().then(() => process.exit(0)));
777
+ process.on("SIGINT", () => void shutdown().then(() => process.exit(0)));
778
+
779
+ return { hostname: "0.0.0.0", port, development: isDevelopment, fetch: server.fetch };
780
+ } catch (error) {
781
+ await stopStartedResources();
782
+ throw error;
783
+ }
784
+ };
785
+
786
+ return {
787
+ ssr,
788
+ plugin,
789
+ config,
790
+ meta,
791
+ baseUrl: opts.baseUrl,
792
+ start,
793
+ get sync() {
794
+ return getProcessSync();
795
+ },
796
+ // Phantom — see AppDefinition._settings doc. Do not read at runtime.
797
+ _settings: undefined as unknown as S,
798
+ settings: createSettingsAPI<S>(),
799
+ notifications,
800
+ };
801
+ };