@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,79 @@
1
+ import { z } from "zod";
2
+ import { CloudResourceRefSchema } from "../../contracts";
3
+
4
+ const TAG_PATTERN = /^[^\s#]+$/;
5
+ const SEARCH_BASE_URL = "https://cloud.invalid";
6
+
7
+ const isSameOriginPath = (value: string): boolean => {
8
+ if (!value.startsWith("/")) return false;
9
+ try {
10
+ return new URL(value, SEARCH_BASE_URL).origin === SEARCH_BASE_URL;
11
+ } catch {
12
+ return false;
13
+ }
14
+ };
15
+
16
+ const SameOriginPathSchema = z.string().refine(isSameOriginPath, {
17
+ message: "Expected a root-relative same-origin path",
18
+ });
19
+
20
+ const TagArraySchema = z.preprocess(
21
+ (value) => {
22
+ if (Array.isArray(value)) return value;
23
+ if (typeof value === "string") return [value];
24
+ return [];
25
+ },
26
+ z
27
+ .array(z.string().trim().min(1).max(64).regex(TAG_PATTERN))
28
+ .max(20)
29
+ .transform((tags) => [...new Set(tags.map((tag) => tag.toLowerCase()))]),
30
+ );
31
+
32
+ export const SearchQuerySchema = z.object({
33
+ q: z
34
+ .string()
35
+ .max(500)
36
+ .optional()
37
+ .default("")
38
+ .transform((query) => query.trim()),
39
+ tag: TagArraySchema.optional().default([]),
40
+ app: z.string().trim().min(1).max(120).optional(),
41
+ require_reader: z
42
+ .enum(["true"])
43
+ .optional()
44
+ .transform((value) => value === "true"),
45
+ provider_limit: z.coerce.number().int().min(1).max(30).optional().default(10),
46
+ });
47
+
48
+ export const SearchAppSchema = z.object({
49
+ id: z.string(),
50
+ name: z.string(),
51
+ icon: z.string(),
52
+ });
53
+
54
+ export const SearchItemSchema = z.object({
55
+ appId: z.string(),
56
+ appName: z.string(),
57
+ appIcon: z.string(),
58
+ readable: z.boolean(),
59
+ ref: CloudResourceRefSchema,
60
+ title: z.string(),
61
+ href: SameOriginPathSchema,
62
+ preview: z.string().optional(),
63
+ icon: z.string().optional(),
64
+ priority: z.number().int().min(0).max(9).optional(),
65
+ metadata: z.array(z.object({ label: z.string(), value: z.string() })).optional(),
66
+ previewUrl: SameOriginPathSchema.optional(),
67
+ });
68
+
69
+ export const SearchResponseSchema = z.object({
70
+ query: z.string(),
71
+ count: z.number().int().nonnegative(),
72
+ items: z.array(SearchItemSchema),
73
+ apps: z.array(SearchAppSchema),
74
+ unsupportedTags: z.array(z.string()).optional(),
75
+ });
76
+
77
+ export type SearchApp = z.infer<typeof SearchAppSchema>;
78
+ export type SearchItem = z.infer<typeof SearchItemSchema>;
79
+ export type SearchResponse = z.infer<typeof SearchResponseSchema>;
@@ -0,0 +1,185 @@
1
+ import { ok } from "@k2b/stdlib";
2
+ import { sql } from "bun";
3
+ import type { MiddlewareHandler } from "hono";
4
+ import { generateKeyPair, jwtVerify, SignJWT } from "jose";
5
+ import { compileCapabilities } from "../_internal/capabilities";
6
+ import { defineCapabilities, UniversalSearchDataSchema, UniversalSearchInputSchema } from "../contracts/capabilities";
7
+ import type { CapabilityRegistryEntry } from "../contracts/registry";
8
+ import type { AuthContext } from "../server";
9
+ import type { signInvocationToken } from "../services/identity/invocation-token";
10
+ import type { withActiveIdentitySigner } from "../services/identity/key-ring";
11
+ import { createSearchRoutes } from "./search";
12
+
13
+ const ITERATIONS = 30;
14
+ const WARMUP_ITERATIONS = 5;
15
+ const PROVIDER_LATENCY_MS = 5;
16
+ const ISSUER = "https://cloud.test";
17
+
18
+ const capabilities = defineCapabilities({
19
+ protocolVersion: 1,
20
+ types: { item: { title: "Item", description: "A benchmark item." } },
21
+ queries: {
22
+ search: {
23
+ title: "Search items",
24
+ description: "Find benchmark items.",
25
+ input: UniversalSearchInputSchema,
26
+ data: UniversalSearchDataSchema,
27
+ openWorld: false,
28
+ universalSearch: { tags: [{ tag: "item", title: "Items", description: "Show benchmark items." }] },
29
+ run: async () => ok({ data: [] }),
30
+ },
31
+ },
32
+ });
33
+
34
+ const authenticate: MiddlewareHandler<AuthContext> = async (c, next) => {
35
+ const user = { id: "11111111-1111-4111-8111-111111111111", roles: ["user"] } as AuthContext["Variables"]["user"];
36
+ c.set("actor", { kind: "user", user });
37
+ c.set("accessSubject", { type: "user", userId: user.id });
38
+ c.set("user", user);
39
+ c.set("credentialKind", "session");
40
+ c.set("credentialScopes", []);
41
+ await next();
42
+ };
43
+
44
+ const providers = (count: number): CapabilityRegistryEntry[] =>
45
+ Array.from({ length: count }, (_, index) => {
46
+ const appId = `bench-${String(index).padStart(2, "0")}`;
47
+ return {
48
+ appId,
49
+ appName: `Bench ${index}`,
50
+ appIcon: "ti ti-box",
51
+ appDescription: "",
52
+ endpoint: `http://${appId}:3000/api/_internal/capabilities/v1`,
53
+ manifest: compileCapabilities(appId, capabilities).manifest,
54
+ };
55
+ });
56
+
57
+ const { privateKey, publicKey } = await generateKeyPair("RS256");
58
+ const withActiveSigner: typeof withActiveIdentitySigner = async (_purpose, callback) =>
59
+ callback(
60
+ {
61
+ kid: "22222222-2222-4222-8222-222222222222",
62
+ key: privateKey,
63
+ issuer: ISSUER,
64
+ signUntil: new Date(Date.now() + 60_000),
65
+ },
66
+ sql,
67
+ );
68
+
69
+ const signInvocation: typeof signInvocationToken = async (params) => {
70
+ const issuedAt = Math.floor(Date.now() / 1_000);
71
+ const claims = {
72
+ ...params.authority,
73
+ iss: ISSUER,
74
+ aud: `app:${params.targetAppId}`,
75
+ token_use: "invocation" as const,
76
+ act: { sub: `app:${params.callingAppId}` },
77
+ op: params.operation,
78
+ schema_hash: params.schemaHash,
79
+ ver: 1 as const,
80
+ ...(params.requestId ? { request_id: params.requestId } : {}),
81
+ jti: crypto.randomUUID(),
82
+ iat: issuedAt,
83
+ nbf: issuedAt,
84
+ exp: issuedAt + 30,
85
+ };
86
+ const token = await new SignJWT(claims)
87
+ .setProtectedHeader({ alg: "RS256", typ: "cloud-invocation+jwt", kid: "22222222-2222-4222-8222-222222222222" })
88
+ .sign(privateKey);
89
+ return {
90
+ token,
91
+ kid: "22222222-2222-4222-8222-222222222222",
92
+ claims,
93
+ };
94
+ };
95
+
96
+ const request = new Request("http://cloud.test/search?q=needle", { headers: { cookie: "session_token=benchmark" } });
97
+ const p95 = (samples: number[]): number => [...samples].sort((left, right) => left - right)[Math.ceil(samples.length * 0.95) - 1] ?? 0;
98
+
99
+ const microSamples = async (run: () => Promise<void>): Promise<{ meanMs: number; p95Ms: number }> => {
100
+ const samples: number[] = [];
101
+ for (let index = 0; index < 100; index += 1) {
102
+ const startedAt = performance.now();
103
+ await run();
104
+ samples.push(performance.now() - startedAt);
105
+ }
106
+ return {
107
+ meanMs: Number((samples.reduce((total, value) => total + value, 0) / samples.length).toFixed(4)),
108
+ p95Ms: Number(p95(samples).toFixed(4)),
109
+ };
110
+ };
111
+
112
+ let microToken = "";
113
+ const signing = await microSamples(async () => {
114
+ microToken = (
115
+ await signInvocation({
116
+ targetAppId: "bench-00",
117
+ callingAppId: "core",
118
+ operation: "search.query",
119
+ schemaHash: "a".repeat(64),
120
+ authority: {
121
+ sub: "11111111-1111-4111-8111-111111111111",
122
+ principal_type: "user",
123
+ access_subject_type: "user",
124
+ access_subject_id: "11111111-1111-4111-8111-111111111111",
125
+ credential_kind: "session",
126
+ scopes: [],
127
+ },
128
+ })
129
+ ).token;
130
+ });
131
+ const verification = await microSamples(async () => {
132
+ await jwtVerify(microToken, publicKey, { algorithms: ["RS256"], issuer: ISSUER, audience: "app:bench-00" });
133
+ });
134
+ console.log(JSON.stringify({ benchmark: "invocation-jwt", signing, verification }));
135
+
136
+ const summary = (samples: number[]): { meanMs: number; p95Ms: number } => ({
137
+ meanMs: Number((samples.reduce((total, value) => total + value, 0) / samples.length).toFixed(4)),
138
+ p95Ms: Number(p95(samples).toFixed(4)),
139
+ });
140
+
141
+ for (const providerCount of [1, 8, 30]) {
142
+ for (const includeTargetVerification of [false, true]) {
143
+ const routes = createSearchRoutes({
144
+ authenticate,
145
+ listCapabilities: async () => providers(providerCount),
146
+ withActiveSigner,
147
+ signInvocation,
148
+ fetch: async (input, init) => {
149
+ const target = input instanceof Request ? input : new Request(input, init);
150
+ const authorization = target.headers.get("authorization");
151
+ if (includeTargetVerification && authorization?.startsWith("Bearer ")) {
152
+ const targetAppId = new URL(target.url).hostname;
153
+ await jwtVerify(authorization.slice(7), publicKey, {
154
+ algorithms: ["RS256"],
155
+ issuer: ISSUER,
156
+ audience: `app:${targetAppId}`,
157
+ });
158
+ }
159
+ await Bun.sleep(PROVIDER_LATENCY_MS);
160
+ return Response.json({ data: [] });
161
+ },
162
+ });
163
+
164
+ for (let index = 0; index < WARMUP_ITERATIONS; index += 1) await routes.request(request.clone());
165
+ const samples: number[] = [];
166
+ for (let index = 0; index < ITERATIONS; index += 1) {
167
+ const startedAt = performance.now();
168
+ const response = await routes.request(request.clone());
169
+ if (!response.ok) throw new Error(`Search benchmark failed with ${response.status}`);
170
+ samples.push(performance.now() - startedAt);
171
+ }
172
+ const jwt = summary(samples);
173
+ console.log(
174
+ JSON.stringify({
175
+ benchmark: "search-fanout",
176
+ phase: includeTargetVerification ? "end-to-end" : "dispatcher",
177
+ providers: providerCount,
178
+ iterations: ITERATIONS,
179
+ controlledProviderLatencyMs: PROVIDER_LATENCY_MS,
180
+ jwt,
181
+ latencyGate: "descriptive JWT-only measurement; no legacy comparison",
182
+ }),
183
+ );
184
+ }
185
+ }
@@ -0,0 +1,407 @@
1
+ import { Hono, type MiddlewareHandler } from "hono";
2
+ import { describeRoute } from "hono-openapi";
3
+ import { readBoundedJson } from "../_internal/bounded-json";
4
+ import { listCapabilities } from "../_internal/registry";
5
+ import type { CapabilityRegistryEntry } from "../contracts";
6
+ import {
7
+ CAPABILITY_FRAMEWORK_ERROR_CODES,
8
+ CAPABILITY_MAX_RESULT_BYTES,
9
+ capabilityResultSchema,
10
+ ErrorResponseSchema,
11
+ UniversalSearchDataSchema,
12
+ } from "../contracts";
13
+ import { type AuthContext, auth, expectUserBackedActor, jsonResponse, preferredLocale, requiresAuth, v } from "../server";
14
+ import { logger } from "../services";
15
+ import { invocationAuthorityFromRequest } from "../services/identity/invocation-authority";
16
+ import { searchInvocationOperation } from "../services/identity/invocation-operations";
17
+ import { normalizeInvocationRequestId, signInvocationToken } from "../services/identity/invocation-token";
18
+ import { withActiveIdentitySigner } from "../services/identity/key-ring";
19
+ import { LOCALE_HEADER } from "../shared/locale";
20
+ import { type SearchItem, SearchItemSchema, SearchQuerySchema, SearchResponseSchema } from "./search/schemas";
21
+
22
+ const log = logger("search");
23
+
24
+ /**
25
+ * Maximum items returned to the client after merging across providers.
26
+ * The frontend has no further limit — this caps the rendered list.
27
+ */
28
+ const GLOBAL_RESULT_LIMIT = 30;
29
+ const PROVIDER_CONCURRENCY = 8;
30
+ const PROVIDER_TIMEOUT_MS = 8_000;
31
+ const SIGNING_TIMEOUT_MS = 500;
32
+
33
+ type HttpSearchProvider = {
34
+ appId: string;
35
+ appName: string;
36
+ appIcon: string;
37
+ endpoint: string;
38
+ tags: string[];
39
+ typeIds: Set<string>;
40
+ schemaHash: string;
41
+ };
42
+
43
+ /**
44
+ * Discovers search providers from live capability manifests.
45
+ * Only live apps with a Query that opts into Universal Search are included.
46
+ */
47
+ const getSearchProviders = (entries: CapabilityRegistryEntry[]): HttpSearchProvider[] => {
48
+ return entries.flatMap((entry) => {
49
+ return entry.manifest.queries.flatMap((query) =>
50
+ query.universalSearch
51
+ ? [
52
+ {
53
+ appId: entry.appId,
54
+ appName: entry.appName,
55
+ appIcon: entry.appIcon,
56
+ endpoint: `${entry.endpoint}/queries/${encodeURIComponent(query.localId)}`,
57
+ tags: query.universalSearch.tags.flatMap((tag) => [tag.tag, ...(tag.aliases ?? [])]),
58
+ typeIds: new Set(entry.manifest.types.map((type) => `${entry.appId}.${type.localId}`)),
59
+ schemaHash: query.schemaHash,
60
+ },
61
+ ]
62
+ : [],
63
+ );
64
+ });
65
+ };
66
+
67
+ type SearchRouteDependencies = {
68
+ listCapabilities?: () => Promise<CapabilityRegistryEntry[]>;
69
+ fetch?: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
70
+ authenticate?: MiddlewareHandler<AuthContext>;
71
+ signInvocation?: typeof signInvocationToken;
72
+ withActiveSigner?: typeof withActiveIdentitySigner;
73
+ };
74
+
75
+ const startBounded = <T, R>(
76
+ items: readonly T[],
77
+ concurrency: number,
78
+ run: (item: T, index: number) => Promise<R>,
79
+ signal?: AbortSignal,
80
+ ) => {
81
+ const deferred = items.map(() => {
82
+ let settled = false;
83
+ let resolve!: (result: PromiseSettledResult<R>) => void;
84
+ const promise = new Promise<PromiseSettledResult<R>>((done) => {
85
+ resolve = (result) => {
86
+ if (settled) return;
87
+ settled = true;
88
+ done(result);
89
+ };
90
+ });
91
+ return { promise, resolve };
92
+ });
93
+ const abort = () => {
94
+ const reason = signal?.reason ?? new Error("Search fan-out deadline exceeded");
95
+ for (const entry of deferred) entry.resolve({ status: "rejected", reason });
96
+ };
97
+ signal?.addEventListener("abort", abort, { once: true });
98
+ if (signal?.aborted) abort();
99
+ let next = 0;
100
+ void Promise.all(
101
+ Array.from({ length: Math.min(concurrency, items.length) }, async () => {
102
+ while (true) {
103
+ if (signal?.aborted) return;
104
+ const index = next;
105
+ next += 1;
106
+ const item = items[index];
107
+ if (item === undefined) return;
108
+ try {
109
+ deferred[index]?.resolve({
110
+ status: "fulfilled",
111
+ value: await run(item, index),
112
+ });
113
+ } catch (reason) {
114
+ deferred[index]?.resolve({ status: "rejected", reason });
115
+ }
116
+ }
117
+ }),
118
+ ).then(() => signal?.removeEventListener("abort", abort));
119
+ return deferred.map((entry) => entry.promise);
120
+ };
121
+
122
+ const settleBounded = async <T, R>(items: readonly T[], concurrency: number, run: (item: T, index: number) => Promise<R>) =>
123
+ Promise.all(startBounded(items, concurrency, run));
124
+
125
+ const waitWithin = <T>(value: Promise<T>, signal: AbortSignal): Promise<T> =>
126
+ new Promise<T>((resolve, reject) => {
127
+ const aborted = () => reject(signal.reason);
128
+ if (signal.aborted) return aborted();
129
+ signal.addEventListener("abort", aborted, { once: true });
130
+ value.then(
131
+ (result) => {
132
+ signal.removeEventListener("abort", aborted);
133
+ resolve(result);
134
+ },
135
+ (error) => {
136
+ signal.removeEventListener("abort", aborted);
137
+ reject(error);
138
+ },
139
+ );
140
+ });
141
+
142
+ /**
143
+ * Creates the global search route.
144
+ * Discovers search providers from the capability registry and fetches results via HTTP,
145
+ * authenticating each provider with an operation-bound invocation JWT.
146
+ */
147
+ export const createSearchRoutes = (dependencies: SearchRouteDependencies = {}) => {
148
+ const registry = dependencies.listCapabilities ?? listCapabilities;
149
+ const fetchProvider = dependencies.fetch ?? globalThis.fetch;
150
+ return new Hono<AuthContext>().use(dependencies.authenticate ?? auth.requireRole("authenticated")).get(
151
+ "/search",
152
+ describeRoute({
153
+ tags: ["Search"],
154
+ summary: "Global search",
155
+ description: "Searches across app providers discovered via the service registry with optional tag filters.",
156
+ ...requiresAuth,
157
+ responses: {
158
+ 200: jsonResponse(SearchResponseSchema, "Merged search results"),
159
+ 400: jsonResponse(ErrorResponseSchema, "Invalid query"),
160
+ 401: jsonResponse(ErrorResponseSchema, "Authentication required"),
161
+ 403: jsonResponse(ErrorResponseSchema, "User-backed actor required"),
162
+ 503: jsonResponse(ErrorResponseSchema, "Capability registry or invocation authority unavailable"),
163
+ },
164
+ }),
165
+ v("query", SearchQuerySchema),
166
+ async (c) => {
167
+ expectUserBackedActor(c);
168
+
169
+ const query = c.req.valid("query");
170
+ const requestId = normalizeInvocationRequestId(c.req.header("x-request-id"));
171
+ const invocationAuthority = invocationAuthorityFromRequest(auth.getAuthority(c));
172
+ let entries: CapabilityRegistryEntry[];
173
+ try {
174
+ entries = await registry();
175
+ } catch (error) {
176
+ log.warn("Search capability registry unavailable", { error: error instanceof Error ? error.message : String(error) });
177
+ return c.json(
178
+ {
179
+ code: CAPABILITY_FRAMEWORK_ERROR_CODES.appUnavailable,
180
+ message: "Capability registry is currently unavailable",
181
+ },
182
+ 503,
183
+ );
184
+ }
185
+ const providers = getSearchProviders(entries);
186
+ const apps = [
187
+ ...new Map(
188
+ providers.map((provider) => [provider.appId, { id: provider.appId, name: provider.appName, icon: provider.appIcon }]),
189
+ ).values(),
190
+ ].sort((a, b) => a.name.localeCompare(b.name));
191
+ const readableTypes = new Set(
192
+ entries.flatMap((entry) => entry.manifest.types.filter((type) => type.reader).map((type) => `${entry.appId}.${type.localId}`)),
193
+ );
194
+
195
+ // Pre-filter providers by tag overlap. With no tags, every provider
196
+ // runs (text-only search). With tags, only providers that own at least
197
+ // one requested tag participate — saves fanout to apps that can't
198
+ // contribute. Tags the user typed that no provider declares are
199
+ // returned to the client so it can render a helpful empty state.
200
+ const knownTags = new Set(providers.flatMap((p) => p.tags));
201
+ const unsupportedTags = query.tag.filter((t) => !knownTags.has(t));
202
+ const appProviders = query.app ? providers.filter((provider) => provider.appId === query.app) : providers;
203
+ const active =
204
+ query.tag.length === 0 ? appProviders : appProviders.filter((provider) => provider.tags.some((tag) => query.tag.includes(tag)));
205
+
206
+ if (query.q.length === 0 && query.tag.length === 0 && !query.app) {
207
+ return c.json({ query: "", count: 0, items: [], apps });
208
+ }
209
+
210
+ if (query.tag.length > 0 && active.length === 0) {
211
+ return c.json({
212
+ query: query.q,
213
+ count: 0,
214
+ items: [],
215
+ apps,
216
+ unsupportedTags,
217
+ });
218
+ }
219
+
220
+ // Single-provider queries get a larger sample for better local
221
+ // ranking — the global slice below still caps the response. Capped
222
+ // at GLOBAL_RESULT_LIMIT so a single app can saturate the response
223
+ // but no more.
224
+ const effectiveProviderLimit = active.length === 1 ? Math.min(GLOBAL_RESULT_LIMIT, query.provider_limit * 3) : query.provider_limit;
225
+ // The same request-wide budget covers signing and provider I/O.
226
+ const providerDeadline = AbortSignal.timeout(PROVIDER_TIMEOUT_MS);
227
+ const fanoutSignal = AbortSignal.any([c.req.raw.signal, providerDeadline]);
228
+ const signingSignal = AbortSignal.any([fanoutSignal, AbortSignal.timeout(SIGNING_TIMEOUT_MS)]);
229
+ // Guard the whole issuance batch once, then reuse the prepared signer for
230
+ // every target. This is one Postgres check per outer search request, not
231
+ // one check per provider.
232
+ let signedInvocations: PromiseSettledResult<Awaited<ReturnType<typeof signInvocationToken>>>[] | null = null;
233
+ try {
234
+ if (active.length > 0) {
235
+ signedInvocations = await waitWithin(
236
+ (dependencies.withActiveSigner ?? withActiveIdentitySigner)(
237
+ "invocation",
238
+ (signer) =>
239
+ Promise.all(
240
+ startBounded(
241
+ active,
242
+ PROVIDER_CONCURRENCY,
243
+ (provider) =>
244
+ (dependencies.signInvocation ?? signInvocationToken)({
245
+ targetAppId: provider.appId,
246
+ callingAppId: "core",
247
+ operation: searchInvocationOperation,
248
+ schemaHash: provider.schemaHash,
249
+ authority: invocationAuthority,
250
+ requestId,
251
+ signer,
252
+ issuer: signer.issuer,
253
+ }),
254
+ signingSignal,
255
+ ),
256
+ ),
257
+ { signal: signingSignal, timeoutMs: SIGNING_TIMEOUT_MS },
258
+ ),
259
+ signingSignal,
260
+ );
261
+ }
262
+ } catch {
263
+ log.warn("Search invocation authority unavailable", {
264
+ reason: signingSignal.aborted ? "signing_deadline" : "signing_guard_failed",
265
+ });
266
+ return c.json(
267
+ { code: CAPABILITY_FRAMEWORK_ERROR_CODES.appUnavailable, message: "Search invocation authority is currently unavailable" },
268
+ 503,
269
+ );
270
+ }
271
+ // One request-wide budget keeps latency flat as the number of apps grows.
272
+ // Workers that have not started when the deadline expires fail fast on
273
+ // the already-aborted signal instead of opening a fresh timeout window.
274
+ const settled = await settleBounded(active, PROVIDER_CONCURRENCY, async (provider, index) => {
275
+ // Scope tags to those this provider declared. Apps no longer need
276
+ // their own gate — the framework guarantees they only see tags
277
+ // they understand.
278
+ const scopedTags = query.tag.filter((t) => provider.tags.includes(t));
279
+
280
+ const headers = await (async () => {
281
+ const signed = await signedInvocations?.[index];
282
+ if (!signed || signed.status === "rejected") {
283
+ throw signed?.reason ?? new Error("Resolved request authority is required for search invocation issuance");
284
+ }
285
+ return new Headers({
286
+ "content-type": "application/json",
287
+ accept: "application/json",
288
+ authorization: `Bearer ${signed.value.token}`,
289
+ });
290
+ })();
291
+ if (requestId) headers.set("x-request-id", requestId);
292
+ for (const name of ["traceparent", "tracestate"] as const) {
293
+ const value = c.req.header(name);
294
+ if (value) headers.set(name, value);
295
+ }
296
+ const requestLocale = preferredLocale(c.req.raw.headers);
297
+ if (requestLocale) headers.set(LOCALE_HEADER, requestLocale);
298
+ headers.set("x-cloud-capability-schema-hash", provider.schemaHash);
299
+ headers.set("x-cloud-invocation-operation", searchInvocationOperation);
300
+
301
+ const res = await fetchProvider(provider.endpoint, {
302
+ method: "POST",
303
+ headers,
304
+ body: JSON.stringify({
305
+ input: {
306
+ query: query.q,
307
+ tags: scopedTags,
308
+ limit: effectiveProviderLimit,
309
+ },
310
+ }),
311
+ signal: fanoutSignal,
312
+ });
313
+
314
+ if (!res.ok) {
315
+ throw new Error(`Search provider ${provider.appId} returned ${res.status}`);
316
+ }
317
+
318
+ const parsedBody = await readBoundedJson(res, CAPABILITY_MAX_RESULT_BYTES);
319
+ if (!parsedBody.ok) throw new Error(`Search provider ${provider.appId} returned invalid or oversized JSON`);
320
+ const envelope = capabilityResultSchema(UniversalSearchDataSchema).safeParse(parsedBody.data);
321
+ if (!envelope.success) throw new Error(`Search provider ${provider.appId} returned an invalid capability result`);
322
+ const results = envelope.data.data;
323
+ const validItems: SearchItem[] = [];
324
+
325
+ for (const view of results) {
326
+ if (view.ref.type.startsWith(`${provider.appId}.`) && !provider.typeIds.has(view.ref.type)) {
327
+ log.warn("Search capability returned an undeclared provider-owned resource type", {
328
+ appId: provider.appId,
329
+ type: view.ref.type,
330
+ });
331
+ continue;
332
+ }
333
+ const open = view.links.find((link) => link.rel === "open");
334
+ if (!open) {
335
+ log.warn("Search capability returned a resource without an open link", { appId: provider.appId, type: view.ref.type });
336
+ continue;
337
+ }
338
+ const preview = view.links.find((link) => link.rel === "preview");
339
+ const parsed = SearchItemSchema.safeParse({
340
+ ref: view.ref,
341
+ title: view.title,
342
+ href: open.href,
343
+ preview: view.preview,
344
+ icon: view.icon,
345
+ priority: view.priority,
346
+ metadata: view.metadata,
347
+ previewUrl: preview?.href,
348
+ appId: provider.appId,
349
+ appName: provider.appName,
350
+ appIcon: provider.appIcon,
351
+ readable: readableTypes.has(view.ref.type),
352
+ });
353
+ if (!parsed.success) {
354
+ log.warn("Search provider returned invalid item", {
355
+ appId: provider.appId,
356
+ tags: query.tag,
357
+ issues: parsed.error.issues.map((issue) => issue.message),
358
+ });
359
+ continue;
360
+ }
361
+ if (!query.require_reader || parsed.data.readable) validItems.push(parsed.data);
362
+ if (validItems.length >= effectiveProviderLimit) break;
363
+ }
364
+
365
+ return validItems;
366
+ });
367
+
368
+ const items = settled.flatMap((result, index) => {
369
+ if (result.status === "fulfilled") return result.value;
370
+
371
+ log.warn("Search provider failed", {
372
+ appId: active[index]?.appId ?? "unknown",
373
+ tags: query.tag,
374
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason),
375
+ });
376
+ return [];
377
+ });
378
+
379
+ items.sort((a, b) => {
380
+ const priorityDiff = (b.priority ?? 0) - (a.priority ?? 0);
381
+ if (priorityDiff !== 0) return priorityDiff;
382
+ return a.title.localeCompare(b.title);
383
+ });
384
+
385
+ // Multiple focused Queries from one app must not buy that app a larger
386
+ // share of the merged result set.
387
+ const appCounts = new Map<string, number>();
388
+ const appBounded = items.filter((item) => {
389
+ const count = appCounts.get(item.appId) ?? 0;
390
+ if (count >= effectiveProviderLimit) return false;
391
+ appCounts.set(item.appId, count + 1);
392
+ return true;
393
+ });
394
+ const sliced = appBounded.slice(0, GLOBAL_RESULT_LIMIT);
395
+
396
+ return c.json({
397
+ query: query.q,
398
+ count: sliced.length,
399
+ items: sliced,
400
+ apps,
401
+ ...(unsupportedTags.length > 0 ? { unsupportedTags } : {}),
402
+ });
403
+ },
404
+ );
405
+ };
406
+
407
+ export type SearchApiType = ReturnType<typeof createSearchRoutes>;