@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,3 @@
1
+ export const APP_READINESS_PATH = "/_cloud/ready";
2
+
3
+ export const appReadinessResponse = (appId: string): Response => Response.json({ status: "ready", appId });
@@ -0,0 +1,136 @@
1
+ import type { AppRegistryEntry } from "../contracts/registry";
2
+
3
+ const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null && !Array.isArray(value);
4
+ const isString = (value: unknown): value is string => typeof value === "string";
5
+ const isStringArray = (value: unknown): value is string[] => Array.isArray(value) && value.every(isString);
6
+ const isStringRecord = (value: unknown): value is Record<string, string> => isRecord(value) && Object.values(value).every(isString);
7
+
8
+ const invalid = (path: string, expected: string): string => `${path} must be ${expected}`;
9
+
10
+ export const validateAppRegistryEntry = (value: unknown): string | null => {
11
+ if (!isRecord(value)) return "entry must be an object";
12
+ for (const field of ["id", "name", "icon", "description", "baseUrl"] as const) {
13
+ if (!isString(value[field]) || value[field].length === 0) return invalid(field, "a non-empty string");
14
+ }
15
+ try {
16
+ const baseUrl = new URL(value.baseUrl as string);
17
+ if (!(["http:", "https:"] as const).includes(baseUrl.protocol as "http:" | "https:") || baseUrl.username || baseUrl.password) {
18
+ return invalid("baseUrl", "an HTTP(S) URL without embedded credentials");
19
+ }
20
+ } catch {
21
+ return invalid("baseUrl", "an HTTP(S) URL without embedded credentials");
22
+ }
23
+ if (!isStringArray(value.routes) || value.routes.some((route) => !route.startsWith("/"))) {
24
+ return invalid("routes", "an array of absolute paths");
25
+ }
26
+ if (value.startedAt !== undefined && !(typeof value.startedAt === "number" && Number.isFinite(value.startedAt))) {
27
+ return invalid("startedAt", "a finite number");
28
+ }
29
+ if (value.runtime !== undefined) {
30
+ if (!isRecord(value.runtime)) return invalid("runtime", "an object");
31
+ if (!isString(value.runtime.release) || value.runtime.release.length === 0) return invalid("runtime.release", "a non-empty string");
32
+ if (!isString(value.runtime.syncVersion) || value.runtime.syncVersion.length === 0) {
33
+ return invalid("runtime.syncVersion", "a non-empty string");
34
+ }
35
+ }
36
+ if (value.presentation !== undefined) {
37
+ if (!isRecord(value.presentation) || !isString(value.presentation.baseLocale) || !isRecord(value.presentation.translations)) {
38
+ return invalid("presentation", "a localized app-presentation catalog");
39
+ }
40
+ for (const [locale, translation] of Object.entries(value.presentation.translations)) {
41
+ if (!isRecord(translation)) return invalid(`presentation.translations.${locale}`, "an object");
42
+ if (translation.name !== undefined && !isString(translation.name)) return invalid(`presentation.translations.${locale}.name`, "a string");
43
+ if (translation.description !== undefined && !isString(translation.description)) {
44
+ return invalid(`presentation.translations.${locale}.description`, "a string");
45
+ }
46
+ for (const field of ["adminGroups", "adminLinks", "legalLinks"] as const) {
47
+ if (translation[field] !== undefined && !isStringRecord(translation[field])) {
48
+ return invalid(`presentation.translations.${locale}.${field}`, "a string map");
49
+ }
50
+ }
51
+ }
52
+ }
53
+ if (value.nav !== undefined) {
54
+ if (!isRecord(value.nav)) return invalid("nav", "an object");
55
+ if (!isString(value.nav.href) || !["primary", "more", "hidden"].includes(String(value.nav.section))) {
56
+ return invalid("nav", "a valid navigation object");
57
+ }
58
+ if (value.nav.requiresRoles !== undefined && !isStringArray(value.nav.requiresRoles)) {
59
+ return invalid("nav.requiresRoles", "an array of strings");
60
+ }
61
+ }
62
+ if (value.adminNav !== undefined) {
63
+ if (
64
+ !Array.isArray(value.adminNav) ||
65
+ value.adminNav.some(
66
+ (group) =>
67
+ !isRecord(group) ||
68
+ (group.id !== undefined && !isString(group.id)) ||
69
+ !isString(group.label) ||
70
+ !Array.isArray(group.links) ||
71
+ group.links.some((link) => !isRecord(link) || !isString(link.label) || !isString(link.href) || !isString(link.icon)),
72
+ )
73
+ ) {
74
+ return invalid("adminNav", "an array of valid navigation groups");
75
+ }
76
+ }
77
+ if (value.legalLinks !== undefined) {
78
+ if (
79
+ !Array.isArray(value.legalLinks) ||
80
+ value.legalLinks.some(
81
+ (link) => !isRecord(link) || !isString(link.label) || !isString(link.href) || (link.icon !== undefined && !isString(link.icon)),
82
+ )
83
+ ) {
84
+ return invalid("legalLinks", "an array of valid links");
85
+ }
86
+ }
87
+ if (value.widgets !== undefined) {
88
+ if (
89
+ !Array.isArray(value.widgets) ||
90
+ value.widgets.some((widget) => !isRecord(widget) || !isString(widget.id) || !isString(widget.path))
91
+ ) {
92
+ return invalid("widgets", "an array of valid widgets");
93
+ }
94
+ }
95
+ if (value.settingKeys !== undefined && !isStringArray(value.settingKeys)) return invalid("settingKeys", "an array of strings");
96
+ if (value.capabilities !== undefined) {
97
+ if (
98
+ !isRecord(value.capabilities) ||
99
+ typeof value.capabilities.protocolVersion !== "number" ||
100
+ !isString(value.capabilities.manifestHash)
101
+ ) {
102
+ return invalid("capabilities", "a valid capability summary");
103
+ }
104
+ }
105
+ if (value.help !== undefined) {
106
+ if (
107
+ !isRecord(value.help) ||
108
+ !isString(value.help.manifestHash) ||
109
+ !isString(value.help.pageBase) ||
110
+ !Array.isArray(value.help.documents)
111
+ ) {
112
+ return invalid("help", "a valid Help summary");
113
+ }
114
+ if (
115
+ !value.help.pageBase.startsWith("/") ||
116
+ value.help.documents.some(
117
+ (document) =>
118
+ !isRecord(document) ||
119
+ !isString(document.id) ||
120
+ !isString(document.title) ||
121
+ typeof document.order !== "number" ||
122
+ !isString(document.searchUrl) ||
123
+ !document.searchUrl.startsWith("/") ||
124
+ !isString(document.url) ||
125
+ !document.url.startsWith("/"),
126
+ )
127
+ ) {
128
+ return invalid("help", "a valid Help summary");
129
+ }
130
+ }
131
+ if (value.openapi !== undefined && !isString(value.openapi)) return invalid("openapi", "a string");
132
+ return null;
133
+ };
134
+
135
+ export const asAppRegistryEntry = (value: unknown): AppRegistryEntry | null =>
136
+ validateAppRegistryEntry(value) === null ? (value as AppRegistryEntry) : null;
@@ -0,0 +1,52 @@
1
+ /** Consume registry invalidations without queuing one snapshot read per event. */
2
+ export const watchRegistryChanges = async ({
3
+ signal,
4
+ watch,
5
+ onChange,
6
+ }: {
7
+ signal: AbortSignal;
8
+ watch: (signal: AbortSignal) => AsyncIterable<{ type: string }>;
9
+ onChange: () => Promise<void>;
10
+ }): Promise<void> => {
11
+ const controller = new AbortController();
12
+ const abort = () => controller.abort();
13
+ signal.addEventListener("abort", abort, { once: true });
14
+ if (signal.aborted) abort();
15
+ let dirty = false;
16
+ let refresh: Promise<void> | undefined;
17
+ let failure: { error: unknown } | undefined;
18
+ const invalidate = () => {
19
+ dirty = true;
20
+ if (refresh) return;
21
+ refresh = (async () => {
22
+ while (dirty) {
23
+ dirty = false;
24
+ await onChange();
25
+ }
26
+ })()
27
+ .catch((error: unknown) => {
28
+ failure = { error };
29
+ controller.abort();
30
+ })
31
+ .finally(() => {
32
+ refresh = undefined;
33
+ if (dirty && !failure) invalidate();
34
+ });
35
+ };
36
+ try {
37
+ while (!controller.signal.aborted) {
38
+ for await (const event of watch(controller.signal)) {
39
+ if (controller.signal.aborted) break;
40
+ invalidate();
41
+ // A resync invalidates even when the next watch replays no entries.
42
+ if (event.type === "resync_required") break;
43
+ }
44
+ }
45
+ } finally {
46
+ controller.abort();
47
+ signal.removeEventListener("abort", abort);
48
+ // Finish the current read and any invalidation already received during it.
49
+ while (refresh) await refresh;
50
+ if (failure) throw failure.error;
51
+ }
52
+ };
@@ -0,0 +1,338 @@
1
+ import type { EphemeralConfig } from "@k2b/sync";
2
+ import type { AppAppearanceColor } from "../contracts/app";
3
+ import type { CapabilityManifest, CapabilityPresentationCatalog } from "../contracts/capabilities";
4
+ import type { AppRegistryEntry, CapabilityRegistryEntry, HelpRegistryEntry } from "../contracts/registry";
5
+ import type { DashboardWidgetPresentation } from "../contracts/widgets";
6
+ import { resolveAppPresentations } from "../shared/app-presentation";
7
+ import { compileCapabilityPresentation, parseCapabilityManifest } from "./capabilities";
8
+ import { lazySync } from "./process-sync";
9
+ import { validateAppRegistryEntry } from "./registry-validation";
10
+ import { watchRegistryChanges } from "./registry-watch";
11
+
12
+ /**
13
+ * Shared app registry: three @k2b/sync ephemerals on the process Sync
14
+ * instance. Apps, the gateway, and gateway-ops all declare them through the
15
+ * functions below, so id, owner, TTL, and value bound stay identical across
16
+ * the fleet (a differing declaration fails with ResourceDriftError).
17
+ *
18
+ * TTL is 3× the heartbeat interval (see `./heartbeat.ts`).
19
+ */
20
+ export const APP_REGISTRY_TTL_MS = 180_000;
21
+ const REGISTRY_OWNER = "cloud";
22
+
23
+ export const APP_REGISTRY_CONFIG = {
24
+ id: "cloud-apps",
25
+ owner: REGISTRY_OWNER,
26
+ ttlMs: APP_REGISTRY_TTL_MS,
27
+ maxValueBytes: 64 * 1024,
28
+ } as const satisfies EphemeralConfig;
29
+
30
+ export const CAPABILITY_REGISTRY_CONFIG = {
31
+ id: "cloud-capabilities",
32
+ owner: REGISTRY_OWNER,
33
+ ttlMs: APP_REGISTRY_TTL_MS,
34
+ maxValueBytes: 512 * 1024,
35
+ } as const satisfies EphemeralConfig;
36
+
37
+ export const HELP_REGISTRY_CONFIG = {
38
+ id: "cloud-help",
39
+ owner: REGISTRY_OWNER,
40
+ ttlMs: APP_REGISTRY_TTL_MS,
41
+ maxValueBytes: 512 * 1024,
42
+ } as const satisfies EphemeralConfig;
43
+
44
+ export const APP_REGISTRY_PREFIX = "apps/";
45
+
46
+ export type CapabilityRegistryRecord = { appId: string; manifest: CapabilityManifest; presentation?: CapabilityPresentationCatalog };
47
+
48
+ export const appRegistry = lazySync((sync) => sync.ephemeral<AppRegistryEntry>(APP_REGISTRY_CONFIG));
49
+ export const capabilityRegistry = lazySync((sync) => sync.ephemeral<CapabilityRegistryRecord>(CAPABILITY_REGISTRY_CONFIG));
50
+ export const helpRegistry = lazySync((sync) => sync.ephemeral<HelpRegistryEntry>(HELP_REGISTRY_CONFIG));
51
+
52
+ /**
53
+ * Follow the app registry until `signal` aborts. The watch first replays the
54
+ * current entries, then streams changes. While a refresh runs, further events
55
+ * coalesce into one follow-up refresh. Resync also refreshes an empty registry.
56
+ * Shutdown waits for received invalidations; refresh errors stop the watch.
57
+ */
58
+ export const watchAppRegistry = async ({ signal, onChange }: { signal: AbortSignal; onChange: () => Promise<void> }): Promise<void> => {
59
+ const registry = appRegistry();
60
+ await watchRegistryChanges({ signal, onChange, watch: (signal) => registry.watch({ prefix: APP_REGISTRY_PREFIX, signal }) });
61
+ };
62
+
63
+ /**
64
+ * App entry enriched with registry metadata.
65
+ * `createdAt` = process start reported by the app (uptime anchor).
66
+ * `updatedAt` = most recent heartbeat touch; `expiresAt` = `updatedAt` + TTL.
67
+ * `version` = the entry's registry revision.
68
+ */
69
+ export type AppRegistryDetail = AppRegistryEntry & {
70
+ createdAt: number;
71
+ updatedAt: number;
72
+ expiresAt: number;
73
+ version: string;
74
+ };
75
+
76
+ export type AppRegistryIssue = {
77
+ key: string;
78
+ version: string;
79
+ reason: string;
80
+ };
81
+
82
+ export type AppRegistrySnapshot = {
83
+ apps: AppRegistryDetail[];
84
+ issues: AppRegistryIssue[];
85
+ };
86
+
87
+ const loggedInvalidReasons = new Map<string, string>();
88
+
89
+ const reportInvalidEntry = (issue: AppRegistryIssue): void => {
90
+ if (loggedInvalidReasons.get(issue.key) === issue.reason) return;
91
+ loggedInvalidReasons.set(issue.key, issue.reason);
92
+ console.error(
93
+ JSON.stringify({
94
+ level: "error",
95
+ source: "app-registry",
96
+ message: "Rejected invalid app registry entry",
97
+ registryKey: issue.key,
98
+ registryVersion: issue.version,
99
+ reason: issue.reason,
100
+ }),
101
+ );
102
+ };
103
+
104
+ export const readAppRegistrySnapshot = async (): Promise<AppRegistrySnapshot> => {
105
+ const snapshot = await appRegistry().snapshot({ prefix: APP_REGISTRY_PREFIX });
106
+ const apps: AppRegistryDetail[] = [];
107
+ const issues: AppRegistryIssue[] = [];
108
+ const presentKeys = new Set<string>();
109
+
110
+ for (const entry of snapshot.entries) {
111
+ presentKeys.add(entry.key);
112
+ const expectedKey =
113
+ entry.value && typeof entry.value === "object" && "id" in entry.value ? `${APP_REGISTRY_PREFIX}${String(entry.value.id)}` : undefined;
114
+ const reason = entry.key !== expectedKey ? "registry key must match entry id" : validateAppRegistryEntry(entry.value);
115
+ if (reason) {
116
+ const issue = { key: entry.key, version: entry.revision, reason };
117
+ issues.push(issue);
118
+ reportInvalidEntry(issue);
119
+ continue;
120
+ }
121
+ loggedInvalidReasons.delete(entry.key);
122
+ const value = entry.value as AppRegistryEntry;
123
+ const updatedAt = entry.updatedAt.getTime();
124
+ apps.push({
125
+ ...value,
126
+ createdAt: value.startedAt ?? updatedAt,
127
+ updatedAt,
128
+ expiresAt: updatedAt + APP_REGISTRY_TTL_MS,
129
+ version: entry.revision,
130
+ });
131
+ }
132
+
133
+ for (const key of loggedInvalidReasons.keys()) {
134
+ if (!presentKeys.has(key)) loggedInvalidReasons.delete(key);
135
+ }
136
+ return { apps, issues };
137
+ };
138
+
139
+ export const requireUsableAppRegistry = (snapshot: AppRegistrySnapshot): AppRegistryDetail[] => {
140
+ if (snapshot.apps.length === 0 && snapshot.issues.length > 0) {
141
+ throw new Error(`App registry contains no valid entries (${snapshot.issues.length} rejected)`);
142
+ }
143
+ return snapshot.apps;
144
+ };
145
+
146
+ /**
147
+ * List all currently live (TTL-valid) app registry entries.
148
+ */
149
+ export const listApps = async (): Promise<AppRegistryEntry[]> => {
150
+ const snapshot = await readAppRegistrySnapshot();
151
+ return requireUsableAppRegistry(snapshot);
152
+ };
153
+
154
+ /** Reads one live app without materializing the full registry. */
155
+ export const getApp = async (appId: string): Promise<AppRegistryEntry | null> => {
156
+ const key = `${APP_REGISTRY_PREFIX}${appId}`;
157
+ const snap = await appRegistry().snapshot({ prefix: key });
158
+ const entry = snap.entries.find((candidate) => candidate.key === key);
159
+ if (!entry) return null;
160
+ const reason =
161
+ !entry.value || typeof entry.value !== "object" || !("id" in entry.value) || entry.value.id !== appId
162
+ ? "registry key must match entry id"
163
+ : validateAppRegistryEntry(entry.value);
164
+ if (!reason) {
165
+ loggedInvalidReasons.delete(key);
166
+ return entry.value;
167
+ }
168
+ reportInvalidEntry({ key, version: entry.revision, reason });
169
+ return null;
170
+ };
171
+
172
+ const capabilityEndpoint = (baseUrl: string): string | null => {
173
+ try {
174
+ const base = new URL(baseUrl);
175
+ if (!(["http:", "https:"] as const).includes(base.protocol as "http:" | "https:") || base.username || base.password) return null;
176
+ return new URL("/api/_internal/capabilities/v1", base).toString();
177
+ } catch {
178
+ return null;
179
+ }
180
+ };
181
+
182
+ const appAccent = (value: string | undefined): AppAppearanceColor | undefined =>
183
+ /^#[0-9a-f]{6}$/i.test(value ?? "") ? (value as AppAppearanceColor) : undefined;
184
+
185
+ export const resolveLiveCapabilityRegistryEntry = (
186
+ key: string,
187
+ value: unknown,
188
+ app: AppRegistryEntry | undefined,
189
+ ): CapabilityRegistryEntry | null => {
190
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
191
+ if (Object.keys(value).some((field) => field !== "appId" && field !== "manifest" && field !== "presentation")) return null;
192
+ const record = value as Partial<CapabilityRegistryRecord>;
193
+ if (!app || typeof record.appId !== "string" || key !== `capabilities/${record.appId}` || record.appId !== app.id) return null;
194
+ if (!app.capabilities) return null;
195
+ const endpoint = capabilityEndpoint(app.baseUrl);
196
+ if (!endpoint) return null;
197
+ try {
198
+ const manifest = parseCapabilityManifest(record.manifest, app.id);
199
+ const presentation = compileCapabilityPresentation(manifest, record.presentation);
200
+ if (app.capabilities.protocolVersion !== manifest.protocolVersion || app.capabilities.manifestHash !== manifest.manifestHash) {
201
+ return null;
202
+ }
203
+ return {
204
+ appId: app.id,
205
+ appName: app.name,
206
+ appIcon: app.icon,
207
+ appAccent: appAccent(app.appearance?.accent),
208
+ appDescription: app.description,
209
+ endpoint,
210
+ manifest,
211
+ presentation,
212
+ };
213
+ } catch {
214
+ return null;
215
+ }
216
+ };
217
+
218
+ export const listCapabilities = async (appSnapshot?: AppRegistryEntry[]): Promise<CapabilityRegistryEntry[]> => {
219
+ const [snap, apps] = await Promise.all([capabilityRegistry().snapshot({ prefix: "capabilities/" }), appSnapshot ?? listApps()]);
220
+ const byId = new Map(apps.map((app) => [app.id, app]));
221
+ return snap.entries.flatMap((entry) => {
222
+ const appId = entry.key.startsWith("capabilities/") ? entry.key.slice("capabilities/".length) : "";
223
+ const capability = resolveLiveCapabilityRegistryEntry(entry.key, entry.value, byId.get(appId));
224
+ return capability ? [capability] : [];
225
+ });
226
+ };
227
+
228
+ export const getCapability = async (appId: string): Promise<CapabilityRegistryEntry | null> => {
229
+ const key = `capabilities/${appId}`;
230
+ const [snap, app] = await Promise.all([capabilityRegistry().snapshot({ prefix: key }), getApp(appId)]);
231
+ const entry = snap.entries.find((candidate) => candidate.key === key);
232
+ return entry ? resolveLiveCapabilityRegistryEntry(entry.key, entry.value, app ?? undefined) : null;
233
+ };
234
+
235
+ const isHelpRegistryEntry = (value: unknown): value is HelpRegistryEntry => {
236
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
237
+ const entry = value as Partial<HelpRegistryEntry>;
238
+ const validDocument = (document: unknown) =>
239
+ !!document &&
240
+ typeof document === "object" &&
241
+ typeof (document as { id?: unknown }).id === "string" &&
242
+ typeof (document as { title?: unknown }).title === "string" &&
243
+ typeof (document as { order?: unknown }).order === "number" &&
244
+ typeof (document as { markdown?: unknown }).markdown === "string" &&
245
+ ((document as { searchText?: unknown }).searchText === undefined ||
246
+ typeof (document as { searchText?: unknown }).searchText === "string") &&
247
+ ((document as { icon?: unknown }).icon === undefined || typeof (document as { icon?: unknown }).icon === "string") &&
248
+ ((document as { description?: unknown }).description === undefined ||
249
+ typeof (document as { description?: unknown }).description === "string");
250
+ return (
251
+ typeof entry.appId === "string" &&
252
+ typeof entry.appName === "string" &&
253
+ typeof entry.appIcon === "string" &&
254
+ typeof entry.manifestHash === "string" &&
255
+ (entry.baseLocale === undefined || typeof entry.baseLocale === "string") &&
256
+ Array.isArray(entry.documents) &&
257
+ entry.documents.every(validDocument) &&
258
+ (entry.documentsByLocale === undefined ||
259
+ (!!entry.documentsByLocale &&
260
+ typeof entry.documentsByLocale === "object" &&
261
+ !Array.isArray(entry.documentsByLocale) &&
262
+ Object.values(entry.documentsByLocale).every((documents) => Array.isArray(documents) && documents.every(validDocument))))
263
+ );
264
+ };
265
+
266
+ export const listHelp = async (): Promise<HelpRegistryEntry[]> => {
267
+ const snap = await helpRegistry().snapshot({ prefix: "help/" });
268
+ return snap.entries.map((entry) => entry.value).filter(isHelpRegistryEntry);
269
+ };
270
+
271
+ export const getHelp = async (appId: string): Promise<HelpRegistryEntry | null> => {
272
+ const key = `help/${appId}`;
273
+ const snap = await helpRegistry().snapshot({ prefix: key });
274
+ const value = snap.entries.find((entry) => entry.key === key)?.value;
275
+ return isHelpRegistryEntry(value) ? value : null;
276
+ };
277
+
278
+ /**
279
+ * Same as `listApps` but returns registry metadata for admin observability.
280
+ */
281
+ export const listAppsDetailed = async (): Promise<AppRegistryDetail[]> => {
282
+ const snapshot = await readAppRegistrySnapshot();
283
+ return snapshot.apps;
284
+ };
285
+
286
+ /**
287
+ * Aggregate every running app's `legalLinks` into one flat list. Used by the
288
+ * login footer, app Footer, and rail "more" dropdown to render a unified set
289
+ * of legal/info links (Imprint, Privacy, Terms, FAQ, …).
290
+ *
291
+ * Order = registration order across apps (no explicit weights — KISS). Within
292
+ * one app, declaration order is preserved. Duplicate `href`s are de-duped
293
+ * (last-seen wins).
294
+ */
295
+ export const listLegalLinks = async (locale?: string): Promise<Array<{ label: string; href: string; icon?: string }>> => {
296
+ const registeredApps = await listApps();
297
+ const apps = locale ? resolveAppPresentations(registeredApps, locale) : registeredApps;
298
+ const seen = new Map<string, { label: string; href: string; icon?: string }>();
299
+ for (const app of apps) {
300
+ for (const link of app.legalLinks ?? []) seen.set(link.href, { ...link });
301
+ }
302
+ return [...seen.values()];
303
+ };
304
+
305
+ /**
306
+ * Aggregate every running app's widget endpoints into one flat list. Used by
307
+ * the dashboard app to build the widget grid and for Core to resolve the
308
+ * exact target before proxying a widget request.
309
+ *
310
+ * Order = registration order across apps.
311
+ */
312
+ export type DashboardWidget = {
313
+ appId: string;
314
+ appName: string;
315
+ appIcon: string;
316
+ widgetId: string;
317
+ /** Fully-qualified URL — `<baseUrl>/<path>`. */
318
+ url: string;
319
+ presentation?: DashboardWidgetPresentation;
320
+ };
321
+
322
+ export const listWidgets = async (): Promise<DashboardWidget[]> => {
323
+ const apps = await listApps();
324
+ const out: DashboardWidget[] = [];
325
+ for (const app of apps) {
326
+ for (const w of app.widgets ?? []) {
327
+ out.push({
328
+ appId: app.id,
329
+ appName: app.name,
330
+ appIcon: app.icon,
331
+ widgetId: w.id,
332
+ url: `${app.baseUrl.replace(/\/$/, "")}${w.path.startsWith("/") ? w.path : `/${w.path}`}`,
333
+ presentation: w.presentation,
334
+ });
335
+ }
336
+ }
337
+ return out;
338
+ };
@@ -0,0 +1,64 @@
1
+ import type { AppRegistryEntry } from "../contracts/registry";
2
+
3
+ export type RuntimeCompatibilityIssue = {
4
+ code: "invalid-sync-version" | "mixed-sync-generation" | "mixed-sync-version";
5
+ severity: "warn" | "error";
6
+ message: string;
7
+ appIds: string[];
8
+ };
9
+
10
+ type ParsedVersion = { major: number; minor: number; patch: number };
11
+
12
+ const parseVersion = (value: string): ParsedVersion | null => {
13
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:[-+][0-9A-Za-z.-]+)?$/.exec(value);
14
+ if (!match) return null;
15
+ return { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) };
16
+ };
17
+
18
+ /** @k2b/sync 6 moved from Redis to NATS; a 5.x process cannot see 6.x registrations or durable work. */
19
+ const isLegacyGeneration = (version: ParsedVersion): boolean => version.major < 6;
20
+
21
+ export const assessRuntimeCompatibility = (apps: readonly AppRegistryEntry[]): RuntimeCompatibilityIssue[] => {
22
+ const issues: RuntimeCompatibilityIssue[] = [];
23
+ const known: Array<{ id: string; version: string; parsed: ParsedVersion }> = [];
24
+
25
+ for (const app of apps) {
26
+ const version = app.runtime?.syncVersion;
27
+ if (!version || version === "unknown") continue;
28
+ const parsed = parseVersion(version);
29
+ if (!parsed) {
30
+ issues.push({
31
+ code: "invalid-sync-version",
32
+ severity: "warn",
33
+ message: `App reports invalid @k2b/sync version ${JSON.stringify(version)}.`,
34
+ appIds: [app.id],
35
+ });
36
+ continue;
37
+ }
38
+ known.push({ id: app.id, version, parsed });
39
+ }
40
+
41
+ const legacy = known.filter(({ parsed }) => isLegacyGeneration(parsed));
42
+ const current = known.filter(({ parsed }) => !isLegacyGeneration(parsed));
43
+ if (legacy.length > 0 && current.length > 0) {
44
+ issues.push({
45
+ code: "mixed-sync-generation",
46
+ severity: "error",
47
+ message: "Mixed @k2b/sync 5.x (Redis) and 6.x (NATS) runtimes cannot see each other's registrations or durable work.",
48
+ appIds: [...legacy, ...current].map(({ id }) => id),
49
+ });
50
+ return issues;
51
+ }
52
+
53
+ const versions = [...new Set(known.map(({ version }) => version))];
54
+ if (versions.length > 1) {
55
+ issues.push({
56
+ code: "mixed-sync-version",
57
+ severity: "warn",
58
+ message: `Mixed @k2b/sync versions are running (${versions.sort().join(", ")}).`,
59
+ appIds: known.map(({ id }) => id),
60
+ });
61
+ }
62
+
63
+ return issues;
64
+ };
@@ -0,0 +1,84 @@
1
+ import type { CloudRuntime, RuntimeAppMeta } from "../contracts/app";
2
+ import type { AppRegistryEntry, CapabilityRegistryEntry } from "../contracts/registry";
3
+ import type { Role } from "../contracts/shared";
4
+
5
+ /**
6
+ * Builds a `CloudRuntime` (the shape consumed by Layout, AdminSidebar, NavMenu)
7
+ * from registry entries.
8
+ *
9
+ * This produces the exact same shape as `createRuntimeContext()` in core/runtime.ts,
10
+ * so all existing UI components work unchanged.
11
+ */
12
+ export const buildRuntimeFromRegistry = (entries: AppRegistryEntry[], capabilities: CapabilityRegistryEntry[] = []): CloudRuntime => ({
13
+ apps: entries.map((e): RuntimeAppMeta => {
14
+ const searchQueries = capabilities.find((entry) => entry.appId === e.id)?.manifest.queries.filter((query) => query.universalSearch);
15
+ const searchTags = searchQueries?.flatMap((query) => query.universalSearch!.tags.flatMap((tag) => [tag.tag, ...(tag.aliases ?? [])]));
16
+ return {
17
+ id: e.id,
18
+ name: e.name,
19
+ icon: e.icon,
20
+ description: e.description,
21
+ presentation: e.presentation
22
+ ? {
23
+ baseLocale: e.presentation.baseLocale,
24
+ translations: Object.fromEntries(
25
+ Object.entries(e.presentation.translations).map(([locale, translation]) => [
26
+ locale,
27
+ {
28
+ ...translation,
29
+ adminGroups: translation.adminGroups ? { ...translation.adminGroups } : undefined,
30
+ adminLinks: translation.adminLinks ? { ...translation.adminLinks } : undefined,
31
+ legalLinks: translation.legalLinks ? { ...translation.legalLinks } : undefined,
32
+ },
33
+ ]),
34
+ ),
35
+ }
36
+ : undefined,
37
+ appearance: e.appearance,
38
+ adminHref: e.nav?.adminHref,
39
+ adminNav: e.adminNav?.map((group) => ({
40
+ id: group.id,
41
+ label: group.label,
42
+ links: group.links.map((link) => ({ ...link })),
43
+ })),
44
+ routes: e.routes,
45
+ nav: e.nav
46
+ ? {
47
+ href: e.nav.href,
48
+ match: e.nav.match,
49
+ section: e.nav.section,
50
+ requiresAuth: e.nav.requiresAuth,
51
+ // Registry stores roles as serialized strings; the source type is
52
+ // Role[] and round-trip is value-preserving.
53
+ requiresRoles: e.nav.requiresRoles as Role[] | undefined,
54
+ }
55
+ : undefined,
56
+ help: e.help
57
+ ? {
58
+ manifestHash: e.help.manifestHash,
59
+ pageBase: e.help.pageBase,
60
+ baseLocale: e.help.baseLocale,
61
+ documentsByLocale: e.help.documentsByLocale
62
+ ? Object.fromEntries(
63
+ Object.entries(e.help.documentsByLocale).map(([locale, documents]) => [
64
+ locale,
65
+ documents.map((document) => ({ ...document })),
66
+ ]),
67
+ )
68
+ : undefined,
69
+ documents: e.help.documents.map((document) => ({ ...document })),
70
+ }
71
+ : undefined,
72
+ searchTags,
73
+ searchHelp: searchQueries?.map((query) => query.description).join(" "),
74
+ searchTagHelp: searchQueries?.flatMap((query) =>
75
+ query.universalSearch!.tags.flatMap((tag) => [
76
+ { tag: tag.tag, help: tag.description },
77
+ ...(tag.aliases ?? []).map((alias) => ({ tag: alias, help: `${tag.description} (alias of #${tag.tag})` })),
78
+ ]),
79
+ ),
80
+ legalLinks: e.legalLinks ? e.legalLinks.map((l) => ({ ...l })) : undefined,
81
+ openapi: e.openapi,
82
+ };
83
+ }),
84
+ });