@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,58 @@
1
+ /**
2
+ * Output helpers for app CLI commands.
3
+ *
4
+ * Every command has to honour `ctx.options.output`, and hand-writing that
5
+ * branch is where it goes wrong: several apps checked only for "json" and then
6
+ * printed a text table under `--jsonl`, which silently breaks the machine
7
+ * -readable contract agents rely on.
8
+ *
9
+ * Both helpers emit the *full* value in structured modes, not the table
10
+ * projection — a table drops fields on purpose, a JSON consumer wants them.
11
+ */
12
+
13
+ import type { CloudCliContext, CloudCliTableColumn } from "./index";
14
+
15
+ /**
16
+ * Render a list: full payload as JSON, one object per line as JSONL, or a
17
+ * table in text mode.
18
+ *
19
+ * `value` is what structured consumers get. `rows`/`columns` are the text
20
+ * projection, and may be a narrower shape than `value`.
21
+ */
22
+ export const printRows = <TRow extends Record<string, unknown>>(
23
+ ctx: CloudCliContext,
24
+ value: unknown,
25
+ rows: TRow[],
26
+ columns: CloudCliTableColumn<TRow>[],
27
+ ): void => {
28
+ if (ctx.options.output === "json") {
29
+ ctx.json(value);
30
+ return;
31
+ }
32
+ if (ctx.options.output === "jsonl") {
33
+ for (const item of Array.isArray(value) ? value : [value]) ctx.jsonLine(item);
34
+ return;
35
+ }
36
+ ctx.table(rows, columns);
37
+ };
38
+
39
+ /**
40
+ * Handle the structured modes for a single value and report whether anything
41
+ * was printed. Use it to guard bespoke text rendering:
42
+ *
43
+ * ```ts
44
+ * if (printStructured(ctx, mailbox)) return;
45
+ * ctx.print(`${mailbox.name} — ${mailbox.address}`);
46
+ * ```
47
+ */
48
+ export const printStructured = (ctx: CloudCliContext, value: unknown): boolean => {
49
+ if (ctx.options.output === "json") {
50
+ ctx.json(value);
51
+ return true;
52
+ }
53
+ if (ctx.options.output === "jsonl") {
54
+ ctx.jsonLine(value);
55
+ return true;
56
+ }
57
+ return false;
58
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Typed client for the core platform API.
3
+ *
4
+ * Apps that need to call core endpoints (`/api/auth/*`, `/api/me/*`,
5
+ * `/api/admin/lifecycle/*`, `/api/accounts/entities`) import this client
6
+ * instead of constructing their own — the type stays in lockstep with the
7
+ * actual route definitions in `@k2b/cloud/api`.
8
+ */
9
+
10
+ import type { CoreApiType } from "../api";
11
+ import { api } from "../server/api-client";
12
+
13
+ export const coreClient = api.create<CoreApiType>({ baseUrl: "/api" });
14
+
15
+ /**
16
+ * Alias for islands that already use the conventional name `apiClient`.
17
+ * Both names refer to the same client; use whichever fits the call site.
18
+ */
19
+ export const apiClient = coreClient;
20
+ export type { CoreApiType };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Environment variable parsing.
3
+ * All env vars are parsed and validated here, then exported as typed `env` object.
4
+ */
5
+
6
+ const str = (key: string, fallback: string = ""): string => process.env[key] ?? fallback;
7
+
8
+ const optional = (key: string): string | undefined => process.env[key]?.trim() || undefined;
9
+
10
+ const int = (key: string, fallback: number): number => {
11
+ const value = process.env[key];
12
+ const parsed = parseInt(value ?? "", 10);
13
+ return isNaN(parsed) ? fallback : parsed;
14
+ };
15
+
16
+ const bool = (key: string, fallback: boolean): boolean => {
17
+ const value = process.env[key]?.trim().toLowerCase();
18
+ if (!value) return fallback;
19
+ return value === "true" || value === "1";
20
+ };
21
+
22
+ const list = (key: string): string[] =>
23
+ (process.env[key] ?? "")
24
+ .split(",")
25
+ .map((entry) => entry.trim())
26
+ .filter(Boolean);
27
+
28
+ export const env = {
29
+ // Application (infrastructure — not changeable at runtime)
30
+ APP_SECRET: str("APP_SECRET"),
31
+ PORT: int("PORT", 3000),
32
+ IS_DEVELOPMENT: process.env.NODE_ENV === "development",
33
+
34
+ // Admin login (local emergency access — token-based, no IPA required)
35
+ ADMIN_LOGIN_TOKEN: str("ADMIN_LOGIN_TOKEN"),
36
+
37
+ // NATS / @k2b/sync (Postgres and Redis are read by Bun from DATABASE_URL / REDIS_URL)
38
+ /** Bootstrap servers, comma-separated `nats://host:port` URLs. Required by `connectNats()`. */
39
+ NATS_SERVERS: list("NATS_SERVERS"),
40
+ /** Sync namespace shared by every app of one Cloud installation (e.g. `dev`, `prod`). Required. */
41
+ SYNC_NAMESPACE: str("SYNC_NAMESPACE"),
42
+ /** Path to a mounted NATS `.creds` file (JWT + NKey). Unset = server-side no-auth. */
43
+ NATS_CREDS_FILE: optional("NATS_CREDS_FILE"),
44
+ /** Path to a mounted CA certificate; enables TLS with that CA. Unset = plain TCP. */
45
+ NATS_TLS_CA_FILE: optional("NATS_TLS_CA_FILE"),
46
+ /**
47
+ * Default true: only dial the servers listed in NATS_SERVERS. Clustered
48
+ * nodes gossip their peers' advertised addresses, which are Docker-internal
49
+ * hostnames (`ipa_nats_2`) that host-side clients (tests, `dev:cld` tooling)
50
+ * cannot resolve. Set to `false` when clients can reach every advertised
51
+ * address and should learn new nodes at runtime.
52
+ */
53
+ NATS_IGNORE_CLUSTER_UPDATES: bool("NATS_IGNORE_CLUSTER_UPDATES", true),
54
+ } as const;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Main config export.
3
+ * Re-exports env vars. SSR config is per-app via defineApp().
4
+ */
5
+
6
+ export { env } from "./env";
@@ -0,0 +1,62 @@
1
+ import { z } from "zod";
2
+
3
+ /** Presentation categories are derived, never stored on an account. */
4
+ export type AccountCategory = "guest" | "login" | "freeipa";
5
+ const categoryPolicy = z.object({ enabled: z.boolean(), visible: z.boolean() }).strict();
6
+ export const AccountCategoryPolicySchema = z
7
+ .object({
8
+ guest: categoryPolicy,
9
+ login: categoryPolicy
10
+ .extend({
11
+ label: z
12
+ .string()
13
+ .trim()
14
+ .transform((label) => label || "Login"),
15
+ })
16
+ .strict(),
17
+ freeipa: categoryPolicy,
18
+ })
19
+ .strict();
20
+ export type AccountCategoryPolicy = z.infer<typeof AccountCategoryPolicySchema>;
21
+
22
+ export const DEFAULT_ACCOUNT_CATEGORY_POLICY: AccountCategoryPolicy = {
23
+ guest: { enabled: true, visible: true },
24
+ login: { enabled: true, visible: true, label: "Login" },
25
+ freeipa: { enabled: true, visible: true },
26
+ };
27
+
28
+ export const accountCategory = (user: { provider: "local" | "ipa"; profile: "guest" | "user" }): AccountCategory =>
29
+ user.provider === "ipa" ? "freeipa" : user.profile === "guest" ? "guest" : "login";
30
+
31
+ export const accountCategoryLabel = (user: { provider: "local" | "ipa"; profile: "guest" | "user" }, loginLabel = "Login"): string => {
32
+ const category = accountCategory(user);
33
+ return category === "freeipa" ? "FreeIPA" : category === "guest" ? "Guest" : loginLabel.trim() || "Login";
34
+ };
35
+
36
+ export const visibleAccountCategories = (policy: AccountCategoryPolicy, freeIpaEnabled: boolean): AccountCategory[] =>
37
+ (["guest", "login", "freeipa"] as const).filter(
38
+ (category) => policy[category].enabled && policy[category].visible && (category !== "freeipa" || freeIpaEnabled),
39
+ );
40
+
41
+ /** Explicit links can open hidden, allowed categories; cookies cannot. Legacy
42
+ * email links remain unbound to Guest so existing full-account links work. */
43
+ export const resolveAccountCategoryLogin = (input: {
44
+ policy: AccountCategoryPolicy;
45
+ freeIpaEnabled: boolean;
46
+ method?: string;
47
+ remembered?: string | null;
48
+ hasToken?: boolean;
49
+ hideGuest?: boolean;
50
+ }): { visible: AccountCategory[]; active: AccountCategory | "email" | null } => {
51
+ const visible = visibleAccountCategories(input.policy, input.freeIpaEnabled).filter(
52
+ (category) => !input.hideGuest || category === "freeipa",
53
+ );
54
+ if (input.hasToken) return { visible, active: "email" };
55
+ if (input.method === "email") return { visible, active: input.policy.guest.enabled || input.policy.login.enabled ? "email" : null };
56
+ const method = input.method === "ipa" ? "freeipa" : input.method;
57
+ if (method === "guest" || method === "login" || method === "freeipa") {
58
+ return { visible, active: input.policy[method].enabled && (method !== "freeipa" || input.freeIpaEnabled) ? method : null };
59
+ }
60
+ const remembered = input.remembered === "ipa" ? "freeipa" : input.remembered;
61
+ return { visible, active: visible.find((category) => category === remembered) ?? visible[0] ?? null };
62
+ };
@@ -0,0 +1,121 @@
1
+ import { z } from "zod";
2
+
3
+ export const ANNOUNCEMENTS_COOKIE = "cloud_announcements";
4
+ export const ANNOUNCEMENTS_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 365;
5
+ export const MAX_DISMISSED_BANNER_VERSIONS = 50;
6
+
7
+ export const AnnouncementKindSchema = z.enum(["announcement", "banner"]);
8
+ export type AnnouncementKind = z.infer<typeof AnnouncementKindSchema>;
9
+
10
+ export const AnnouncementToneSchema = z.enum(["info", "success", "warning", "danger"]);
11
+ export type AnnouncementTone = z.infer<typeof AnnouncementToneSchema>;
12
+
13
+ export const AnnouncementCookieStateSchema = z.object({
14
+ seenAnnouncementVersion: z.number().int().nonnegative().default(0),
15
+ dismissedBannerVersions: z.array(z.number().int().positive()).default([]),
16
+ });
17
+ export type AnnouncementCookieState = z.infer<typeof AnnouncementCookieStateSchema>;
18
+
19
+ export const DEFAULT_ANNOUNCEMENT_COOKIE_STATE: AnnouncementCookieState = {
20
+ seenAnnouncementVersion: 0,
21
+ dismissedBannerVersions: [],
22
+ };
23
+
24
+ export const AnnouncementEntrySchema = z.object({
25
+ id: z.uuid(),
26
+ version: z.number().int().positive(),
27
+ kind: AnnouncementKindSchema,
28
+ title: z.string(),
29
+ body: z.string(),
30
+ tone: AnnouncementToneSchema,
31
+ publishedAt: z.string().datetime(),
32
+ expiresAt: z.string().datetime().nullable(),
33
+ createdAt: z.string().datetime(),
34
+ updatedAt: z.string().datetime(),
35
+ createdBy: z.uuid().nullable(),
36
+ updatedBy: z.uuid().nullable(),
37
+ });
38
+ export type AnnouncementEntry = z.infer<typeof AnnouncementEntrySchema>;
39
+
40
+ export const AnnouncementDisplayEntrySchema = AnnouncementEntrySchema.omit({ body: true }).extend({
41
+ bodyHtml: z.string(),
42
+ });
43
+ export type AnnouncementDisplayEntry = z.infer<typeof AnnouncementDisplayEntrySchema>;
44
+
45
+ const DatetimeInputSchema = z.string().datetime();
46
+ const NullableDatetimeInputSchema = z.string().datetime().nullable();
47
+
48
+ export const CreateAnnouncementSchema = z.object({
49
+ kind: AnnouncementKindSchema,
50
+ title: z.string().trim().min(1).max(180),
51
+ body: z.string().trim().min(1).max(20_000),
52
+ tone: AnnouncementToneSchema.default("info"),
53
+ publishedAt: DatetimeInputSchema.optional(),
54
+ expiresAt: NullableDatetimeInputSchema.optional(),
55
+ });
56
+ export type CreateAnnouncement = z.infer<typeof CreateAnnouncementSchema>;
57
+
58
+ export const UpdateAnnouncementSchema = z
59
+ .object({
60
+ kind: AnnouncementKindSchema.optional(),
61
+ title: z.string().trim().min(1).max(180).optional(),
62
+ body: z.string().trim().min(1).max(20_000).optional(),
63
+ tone: AnnouncementToneSchema.optional(),
64
+ publishedAt: DatetimeInputSchema.optional(),
65
+ expiresAt: NullableDatetimeInputSchema.optional(),
66
+ })
67
+ .refine((value) => Object.keys(value).length > 0, "Provide at least one field to update.");
68
+ export type UpdateAnnouncement = z.infer<typeof UpdateAnnouncementSchema>;
69
+
70
+ export const AnnouncementListResponseSchema = z.object({
71
+ items: z.array(AnnouncementEntrySchema),
72
+ });
73
+ export type AnnouncementListResponse = z.infer<typeof AnnouncementListResponseSchema>;
74
+
75
+ export const ActiveAnnouncementsResponseSchema = z.object({
76
+ banners: z.array(AnnouncementDisplayEntrySchema),
77
+ announcements: z.array(AnnouncementDisplayEntrySchema),
78
+ latestAnnouncementVersion: z.number().int().nonnegative(),
79
+ });
80
+ export type ActiveAnnouncementsResponse = z.infer<typeof ActiveAnnouncementsResponseSchema>;
81
+
82
+ const normalizeCookieState = (value: unknown): AnnouncementCookieState => {
83
+ const parsed = AnnouncementCookieStateSchema.safeParse(value);
84
+ if (!parsed.success) return DEFAULT_ANNOUNCEMENT_COOKIE_STATE;
85
+ const dismissed = [...new Set(parsed.data.dismissedBannerVersions)]
86
+ .filter((version) => Number.isInteger(version) && version > 0)
87
+ .sort((a, b) => b - a)
88
+ .slice(0, MAX_DISMISSED_BANNER_VERSIONS);
89
+
90
+ return {
91
+ seenAnnouncementVersion: Math.max(0, parsed.data.seenAnnouncementVersion),
92
+ dismissedBannerVersions: dismissed,
93
+ };
94
+ };
95
+
96
+ export const parseAnnouncementCookieValue = (value: string | null | undefined): AnnouncementCookieState => {
97
+ if (!value) return DEFAULT_ANNOUNCEMENT_COOKIE_STATE;
98
+ try {
99
+ return normalizeCookieState(JSON.parse(decodeURIComponent(value)));
100
+ } catch {
101
+ return DEFAULT_ANNOUNCEMENT_COOKIE_STATE;
102
+ }
103
+ };
104
+
105
+ export const parseAnnouncementCookieHeader = (cookieHeader: string | null | undefined): AnnouncementCookieState => {
106
+ if (!cookieHeader) return DEFAULT_ANNOUNCEMENT_COOKIE_STATE;
107
+ const match = cookieHeader.match(new RegExp(`(?:^|;\\s*)${ANNOUNCEMENTS_COOKIE}=([^;]+)`));
108
+ return parseAnnouncementCookieValue(match?.[1]);
109
+ };
110
+
111
+ export const serializeAnnouncementCookieState = (state: AnnouncementCookieState): string =>
112
+ encodeURIComponent(JSON.stringify(normalizeCookieState(state)));
113
+
114
+ export const mergeAnnouncementCookieState = (
115
+ current: AnnouncementCookieState,
116
+ patch: Partial<AnnouncementCookieState>,
117
+ ): AnnouncementCookieState =>
118
+ normalizeCookieState({
119
+ seenAnnouncementVersion: Math.max(current.seenAnnouncementVersion, patch.seenAnnouncementVersion ?? 0),
120
+ dismissedBannerVersions: [...current.dismissedBannerVersions, ...(patch.dismissedBannerVersions ?? [])],
121
+ });
@@ -0,0 +1,197 @@
1
+ import { z } from "zod";
2
+
3
+ /** Version 1 has a single algorithm and fixed signing encodings. No negotiation. */
4
+ export const APP_APPROVAL_PROTOCOL = "cloud-app-approval-v1";
5
+ export const APP_APPROVAL_PATH = "/api/auth/app-approval/v1";
6
+ export const APP_APPROVAL_LIMITS = {
7
+ bodyBytes: 8192,
8
+ pairingSeconds: 300,
9
+ loginSeconds: 300,
10
+ proofSeconds: 60,
11
+ recentSessionSeconds: 600,
12
+ devicesPerAccount: 20,
13
+ pendingPerAccount: 5,
14
+ pageSize: 20,
15
+ pollSeconds: 5,
16
+ } as const;
17
+
18
+ const Id = z.string().uuid();
19
+ const Secret = z.string().regex(/^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/);
20
+ const Signature = z.string().regex(/^[A-Za-z0-9_-]{85}[AQgw]$/);
21
+ export const AppDevicePublicKeySchema = z
22
+ .object({
23
+ kty: z.literal("EC"),
24
+ crv: z.literal("P-256"),
25
+ x: Secret,
26
+ y: Secret,
27
+ })
28
+ .strict();
29
+ export type AppDevicePublicKey = z.infer<typeof AppDevicePublicKeySchema>;
30
+ export const AppPairingStartSchema = z.object({ userId: Id.optional() }).strict();
31
+ export const AppPairingClaimSchema = z
32
+ .object({
33
+ pairingId: Id,
34
+ secret: Secret,
35
+ publicKey: AppDevicePublicKeySchema,
36
+ name: z.string().trim().min(1).max(80),
37
+ signature: Signature,
38
+ })
39
+ .strict();
40
+ export const AppPairingReferenceSchema = z.object({ pairingId: Id, secret: Secret, publicKey: AppDevicePublicKeySchema }).strict();
41
+ export const AppPairingConfirmSchema = z.object({ pairingId: Id, comparison: z.string().regex(/^\d{6}$/) }).strict();
42
+ export const AppLoginStartSchema = z
43
+ .object({
44
+ identifier: z.string().trim().min(1).max(254),
45
+ category: z.enum(["guest", "login", "freeipa"]),
46
+ })
47
+ .strict();
48
+ export const AppLoginReferenceSchema = z.object({ requestId: Id, browserSecret: Secret }).strict();
49
+ export const AppDeviceMutationSchema = z.discriminatedUnion("operation", [
50
+ z.object({ operation: z.literal("rename"), deviceId: Id, name: z.string().trim().min(1).max(80) }).strict(),
51
+ z.object({ operation: z.literal("revoke"), deviceId: Id }).strict(),
52
+ ]);
53
+ export const AppDeviceCommandSchema = z.discriminatedUnion("operation", [
54
+ z.object({ operation: z.literal("pending") }).strict(),
55
+ z.object({ operation: z.literal("revoke") }).strict(),
56
+ z
57
+ .object({
58
+ operation: z.literal("decide"),
59
+ requestId: Id,
60
+ challenge: Secret,
61
+ comparison: z.string().regex(/^\d{6}$/),
62
+ decision: z.enum(["approve", "deny"]),
63
+ })
64
+ .strict(),
65
+ ]);
66
+ export const AppDeviceProofSchema = z
67
+ .object({
68
+ issuer: z.string().url(),
69
+ deviceId: Id,
70
+ jti: Id,
71
+ issuedAt: z.number().int().nonnegative(),
72
+ expiresAt: z.number().int().positive(),
73
+ command: AppDeviceCommandSchema,
74
+ })
75
+ .strict();
76
+ export const AppDeviceRequestSchema = z.object({ proof: AppDeviceProofSchema, signature: Signature }).strict();
77
+ export type AppDeviceProof = z.infer<typeof AppDeviceProofSchema>;
78
+ export type AppDeviceRequest = z.infer<typeof AppDeviceRequestSchema>;
79
+
80
+ /** UTF-8 of this exact JSON array; signatures use WebCrypto ECDSA P-256/SHA-256,
81
+ * 64-byte IEEE-P1363 r||s, encoded as unpadded base64url. */
82
+ export const appDeviceProofMessage = (proof: AppDeviceProof): string => {
83
+ const c = proof.command;
84
+ return JSON.stringify([
85
+ APP_APPROVAL_PROTOCOL,
86
+ "device",
87
+ proof.issuer,
88
+ proof.deviceId,
89
+ proof.jti,
90
+ proof.issuedAt,
91
+ proof.expiresAt,
92
+ c.operation,
93
+ ...(c.operation === "decide" ? [c.requestId, c.challenge, c.comparison, c.decision] : []),
94
+ ]);
95
+ };
96
+ export const appPairingProofMessage = (issuer: string, claim: Omit<z.infer<typeof AppPairingClaimSchema>, "signature">): string =>
97
+ JSON.stringify([
98
+ APP_APPROVAL_PROTOCOL,
99
+ "pair",
100
+ issuer,
101
+ claim.pairingId,
102
+ claim.secret,
103
+ claim.publicKey.kty,
104
+ claim.publicKey.crv,
105
+ claim.publicKey.x,
106
+ claim.publicKey.y,
107
+ claim.name,
108
+ ]);
109
+
110
+ const DateTime = z.string().datetime();
111
+ const Comparison = z.string().regex(/^\d{6}$/);
112
+ export const AppDeviceViewSchema = z
113
+ .object({
114
+ id: Id,
115
+ name: z.string(),
116
+ createdAt: DateTime,
117
+ lastUsedAt: DateTime.nullable(),
118
+ revokedAt: DateTime.nullable(),
119
+ assisted: z.boolean(),
120
+ })
121
+ .strict();
122
+ export const AppPendingLoginSchema = z
123
+ .object({ requestId: Id, challenge: Secret, comparison: Comparison, createdAt: DateTime, expiresAt: DateTime })
124
+ .strict();
125
+ export const AppPairingPayloadSchema = z
126
+ .object({ protocol: z.literal(APP_APPROVAL_PROTOCOL), issuer: z.string().url(), pairingId: Id, secret: Secret, expiresAt: DateTime })
127
+ .strict();
128
+ export type AppPairingPayload = z.infer<typeof AppPairingPayloadSchema>;
129
+
130
+ const pairingOrigin = (value: string): string => {
131
+ const url = new URL(value);
132
+ const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
133
+ if (
134
+ (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) ||
135
+ url.username ||
136
+ url.password ||
137
+ url.pathname !== "/" ||
138
+ url.search ||
139
+ url.hash
140
+ )
141
+ throw new Error("Invalid pairing origin");
142
+ return url.origin;
143
+ };
144
+
145
+ /** The same fragment-only link is used for QR, copy/paste and same-device opening. */
146
+ export const appPairingLink = (authenticatorOrigin: string, payload: AppPairingPayload): string => {
147
+ const parsed = AppPairingPayloadSchema.parse(payload);
148
+ if (pairingOrigin(parsed.issuer) !== parsed.issuer) throw new Error("Invalid pairing issuer");
149
+ const link = `${pairingOrigin(authenticatorOrigin)}/#pairing=${encodeURIComponent(JSON.stringify(parsed))}`;
150
+ if (link.length > APP_APPROVAL_LIMITS.bodyBytes) throw new Error("Pairing link too long");
151
+ return link;
152
+ };
153
+
154
+ /** Parses untrusted pasted/scanned text without fetching or trusting the issuer.
155
+ * The caller must obtain consent, validate discovery and clear the fragment. */
156
+ export const parseAppPairingLink = (input: string, authenticatorOrigin: string): AppPairingPayload => {
157
+ if (input.length > APP_APPROVAL_LIMITS.bodyBytes) throw new Error("Pairing link too long");
158
+ const url = new URL(input.trim());
159
+ if (
160
+ url.origin !== pairingOrigin(authenticatorOrigin) ||
161
+ url.username ||
162
+ url.password ||
163
+ url.pathname !== "/" ||
164
+ url.search ||
165
+ !url.hash.startsWith("#pairing=")
166
+ )
167
+ throw new Error("Invalid pairing link");
168
+ const payload = AppPairingPayloadSchema.parse(JSON.parse(decodeURIComponent(url.hash.slice("#pairing=".length))));
169
+ if (pairingOrigin(payload.issuer) !== payload.issuer) throw new Error("Invalid pairing issuer");
170
+ return payload;
171
+ };
172
+ export const AppPairingClaimResultSchema = z.object({ deviceId: Id, comparison: Comparison, expiresAt: DateTime }).strict();
173
+ export const AppPairingResultSchema = z
174
+ .object({ state: z.enum(["pending", "claimed", "confirmed", "cancelled"]), deviceId: Id.nullable(), comparison: Comparison.nullable() })
175
+ .strict();
176
+ /** Initiating Cloud session only; never returned to an unpaired device. */
177
+ export const AppPairingInspectionSchema = AppPairingResultSchema.extend({ name: z.string().nullable(), userId: Id });
178
+ export const AppDeviceResponseSchema = z.union([
179
+ z
180
+ .object({
181
+ requests: z.array(AppPendingLoginSchema).max(APP_APPROVAL_LIMITS.pendingPerAccount),
182
+ pollAfterSeconds: z.number().positive(),
183
+ })
184
+ .strict(),
185
+ z.object({ state: z.enum(["approved", "denied", "revoked"]) }).strict(),
186
+ ]);
187
+ export const AppLoginStartResultSchema = z
188
+ .object({ requestId: Id, browserSecret: Secret, comparison: Comparison, expiresAt: DateTime, pollAfterSeconds: z.number().positive() })
189
+ .strict();
190
+ export const AppLoginStatusSchema = z
191
+ .object({ state: z.enum(["pending", "approved", "denied", "consumed", "expired"]), pollAfterSeconds: z.number().positive() })
192
+ .strict();
193
+ export const AppDevicesPageSchema = z
194
+ .object({ items: z.array(AppDeviceViewSchema).max(APP_APPROVAL_LIMITS.pageSize), nextCursor: Id.nullable() })
195
+ .strict();
196
+ export type AppDeviceView = z.infer<typeof AppDeviceViewSchema>;
197
+ export type AppPendingLogin = z.infer<typeof AppPendingLoginSchema>;
@@ -0,0 +1,162 @@
1
+ import type { Sync } from "@k2b/sync";
2
+ import type { HelpManifest } from "../shared/help";
3
+ import type { Role } from "./shared";
4
+ import type { DashboardWidgetPresentation } from "./widgets";
5
+
6
+ /**
7
+ * One link entry contributed by an app to the global legal/info footer
8
+ * (login page, app footer, rail dropdown). Aggregated across all running
9
+ * apps via `listLegalLinks()`.
10
+ */
11
+ export type LegalLink = {
12
+ label: string;
13
+ href: string;
14
+ icon?: string;
15
+ };
16
+
17
+ export type AppAppearanceColor = `#${string}`;
18
+
19
+ export type AppAppearance = {
20
+ accent: AppAppearanceColor;
21
+ background?: {
22
+ from: AppAppearanceColor;
23
+ via?: AppAppearanceColor;
24
+ to?: AppAppearanceColor;
25
+ angle?: number;
26
+ /** Edge color intensity from 0 to 100. Defaults to 20. */
27
+ strength?: number;
28
+ };
29
+ };
30
+
31
+ export type AppAdminNavigationLink = {
32
+ label: string;
33
+ href: string;
34
+ icon: string;
35
+ };
36
+
37
+ export type AppAdminNavigationGroup = {
38
+ /** Stable key used only to localize this presentation group. */
39
+ id?: string;
40
+ label: string;
41
+ links: AppAdminNavigationLink[];
42
+ };
43
+
44
+ export type AppPresentationTranslation = {
45
+ name?: string;
46
+ description?: string;
47
+ /** Group labels keyed by `adminNav[].id`. */
48
+ adminGroups?: Readonly<Record<string, string>>;
49
+ /** Link labels keyed by their stable same-origin href. */
50
+ adminLinks?: Readonly<Record<string, string>>;
51
+ /** Legal-link labels keyed by their stable same-origin href. */
52
+ legalLinks?: Readonly<Record<string, string>>;
53
+ };
54
+
55
+ export type AppPresentationCatalog = {
56
+ /** Locale of the complete `name`, `description`, `adminNav`, and `legalLinks` declaration. */
57
+ baseLocale: string;
58
+ /** Partial presentation overlays with exact -> ancestor -> base fallback. */
59
+ translations: Readonly<Record<string, AppPresentationTranslation>>;
60
+ };
61
+
62
+ export type AppMeta = {
63
+ id: string;
64
+ name: string;
65
+ icon: string;
66
+ description: string;
67
+ presentation?: AppPresentationCatalog;
68
+ appearance?: AppAppearance;
69
+ adminHref?: string;
70
+ /** Optional multi-link admin navigation owned by this app. */
71
+ adminNav?: AppAdminNavigationGroup[];
72
+ /**
73
+ * Top-level URL prefixes the gateway routes to this app. The gateway
74
+ * is dumb — it just builds a prefix-trie from these strings.
75
+ */
76
+ routes: readonly string[];
77
+ nav?: {
78
+ href: string;
79
+ match?: string;
80
+ section: "primary" | "more" | "hidden";
81
+ requiresAuth?: boolean;
82
+ requiresRoles?: Role[];
83
+ };
84
+ /**
85
+ * Legal/info pages this app owns. Aggregated app-wide and rendered in
86
+ * login footer, app Footer, and the rail "more" dropdown. Each app
87
+ * contributes its own (e.g. settings → terms/privacy/imprint, faq → FAQ).
88
+ */
89
+ legalLinks?: LegalLink[];
90
+ /**
91
+ * Dashboard widget endpoints this app exposes. Each entry references an
92
+ * HTTP endpoint that returns a `WidgetResponse` (see `contracts/widgets.ts`).
93
+ * Core resolves the initiating user before dispatching these; the endpoint
94
+ * is responsible for permission / role gating and returns 204 to silently
95
+ * skip rendering for the current user.
96
+ */
97
+ widgets?: WidgetEndpoint[];
98
+ /** Setting keys declared by this app. Used by admin tooling to protect active app-owned settings. */
99
+ settingKeys?: readonly string[];
100
+ /** Gateway-relative URL where this app's OpenAPI JSON is served, or undefined. */
101
+ openapi?: string;
102
+ };
103
+
104
+ export type WidgetEndpoint = {
105
+ /** Unique-within-the-app id, e.g. "open-requests". */
106
+ id: string;
107
+ /** Absolute path on the app's HTTP service, e.g. "/api/accounts/widget/open-requests". */
108
+ path: string;
109
+ /** Optional initial layout recommendation. Explicit user choices win. */
110
+ presentation?: DashboardWidgetPresentation;
111
+ };
112
+
113
+ export type RuntimeAppMeta = AppMeta & {
114
+ help?: HelpManifest;
115
+ searchTags?: string[];
116
+ searchHelp?: string;
117
+ searchTagHelp?: AppSearchTagHelpEntry[];
118
+ };
119
+
120
+ export type CloudLogger = {
121
+ debug: (message: string, metadata?: Record<string, unknown>) => void;
122
+ info: (message: string, metadata?: Record<string, unknown>) => void;
123
+ warn: (message: string, metadata?: Record<string, unknown>) => void;
124
+ error: (message: string, metadata?: Record<string, unknown>) => void;
125
+ };
126
+
127
+ export type CloudRuntime = {
128
+ apps: readonly RuntimeAppMeta[];
129
+ };
130
+
131
+ export type CloudContext = {
132
+ logger: (source: string) => CloudLogger;
133
+ settings: {
134
+ get: <T = unknown>(key: string) => Promise<T>;
135
+ set: (key: string, value: unknown) => Promise<void>;
136
+ };
137
+ runtime: CloudRuntime;
138
+ };
139
+
140
+ /** Lifecycle hooks additionally receive the ready process-wide @k2b/sync instance. */
141
+ export type CloudLifecycleContext = CloudContext & { sync: Sync };
142
+
143
+ export type AppLifecycle = {
144
+ setup?: (ctx: CloudLifecycleContext) => Promise<void>;
145
+ start?: (ctx: CloudLifecycleContext) => Promise<void>;
146
+ stop?: (ctx: CloudLifecycleContext) => Promise<void>;
147
+ };
148
+
149
+ export type AppSearchTagHelpEntry = {
150
+ tag: string;
151
+ help: string;
152
+ };
153
+
154
+ /**
155
+ * Removes query parameters from a navigation href so path matching stays stable.
156
+ */
157
+ export const stripQuery = (href: string): string => href.split("?")[0] ?? href;
158
+
159
+ /**
160
+ * Resolves the active-path matcher for a nav entry.
161
+ */
162
+ export const resolveNavMatch = (meta: AppMeta): string | undefined => meta.nav?.match ?? (meta.nav ? stripQuery(meta.nav.href) : undefined);