@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,76 @@
1
+ import { createHash } from "node:crypto";
2
+ import { sql } from "bun";
3
+ import type { Context } from "hono";
4
+ import { HTTPException } from "hono/http-exception";
5
+ import { isAccountCategoryAllowed } from "./account-category-policy";
6
+ import { isAccountExpired } from "./account-model";
7
+ import { verifySessionToken } from "./identity";
8
+ import { getIdentityRuntimeConfig } from "./identity/runtime-config";
9
+ import { session } from "./session";
10
+ import { loadJwtSessionUser } from "./session/user";
11
+ import * as settings from "./settings";
12
+
13
+ /** First-use acceptance, not an automatic re-consent policy for later edits. */
14
+ export const legalConsent = {
15
+ documents: async () => {
16
+ const document = async (kind: "terms" | "privacy") => {
17
+ const [mode, url, content] = await Promise.all([
18
+ settings.get<string>(`legal.${kind}.mode`),
19
+ settings.get<string>(`legal.${kind}.url`),
20
+ settings.get<string>(`legal.${kind}.content`),
21
+ ]);
22
+ return mode === "external" && url?.trim()
23
+ ? { kind, mode: "external", value: url.trim() }
24
+ : { kind, mode: "local", value: (content ?? "").trim() };
25
+ };
26
+ const documents = await Promise.all([document("terms"), document("privacy")]);
27
+ const version = createHash("sha256").update(JSON.stringify(documents)).digest("hex");
28
+ return { documents, version };
29
+ },
30
+
31
+ // Deliberately separate from ordinary session authentication. Callers must
32
+ // never use this identity to authorize application data or issue credentials.
33
+ pending: async (c: Context) => {
34
+ const token = session.getToken(c);
35
+ const claims = token ? await verifySessionToken(token) : null;
36
+ if (!claims) return null;
37
+ const { groupsAdmin } = await getIdentityRuntimeConfig();
38
+ const user = await loadJwtSessionUser({
39
+ userId: claims.sub,
40
+ sid: claims.sid,
41
+ authEpoch: claims.auth_epoch,
42
+ groupsAdmin,
43
+ allowPendingLegalConsent: true,
44
+ });
45
+ if (!user || isAccountExpired(user.accountExpires) || !(await isAccountCategoryAllowed(user))) return null;
46
+ const [row] = await sql<{ pending: boolean }[]>`
47
+ SELECT legal_pending AND NOT EXISTS (SELECT 1 FROM auth.legal_acceptances WHERE user_id = ${user.id}::uuid) AS pending
48
+ FROM auth.session_families WHERE sid = ${claims.sid}::uuid
49
+ `;
50
+ return row?.pending ? { user, sid: claims.sid, authEpoch: claims.auth_epoch } : null;
51
+ },
52
+
53
+ accept: async (c: Context, version: string) => {
54
+ const pending = await legalConsent.pending(c);
55
+ if (!pending) throw new HTTPException(401, { message: "Sign in again to continue." });
56
+ const current = await legalConsent.documents();
57
+ if (current.version !== version) throw new HTTPException(409, { message: "The documents changed. Reload and review them again." });
58
+ await sql.begin(async (tx) => {
59
+ const [user] = await tx<{ provider: "local" | "ipa"; profile: "guest" | "user" }[]>`
60
+ SELECT u.provider, u.profile FROM auth.users u JOIN auth.session_families sf ON sf.user_id = u.id
61
+ JOIN auth.signing_keys sk ON sk.kid = sf.signing_kid AND sk.state <> 'revoked'
62
+ WHERE u.id = ${pending.user.id}::uuid AND sf.sid = ${pending.sid}::uuid
63
+ AND u.auth_epoch = ${pending.authEpoch} AND sf.auth_epoch = u.auth_epoch
64
+ AND sf.revoked_at IS NULL AND sf.expires_at > now()
65
+ AND (u.account_expires IS NULL OR u.account_expires > now())
66
+ FOR UPDATE OF u, sf
67
+ `;
68
+ if (!user || !(await isAccountCategoryAllowed(user, tx))) throw new HTTPException(401, { message: "Sign in again to continue." });
69
+ await tx`
70
+ INSERT INTO auth.legal_acceptances (user_id, session_id, document_version, documents)
71
+ VALUES (${pending.user.id}::uuid, ${pending.sid}::uuid, ${current.version}, ${JSON.stringify(current.documents)}::text::jsonb)
72
+ ON CONFLICT (user_id) DO NOTHING
73
+ `;
74
+ });
75
+ },
76
+ };
@@ -0,0 +1,477 @@
1
+ import { sql } from "bun";
2
+ import type { PaginationParams } from "../../contracts/shared";
3
+ import { escapeLikePattern, parsePgJsonRecord, toPgTextArray } from "../postgres";
4
+ import { registerSettings } from "../settings/defaults";
5
+ import { redactMetadata } from "./redaction";
6
+ import { observeSyncEvent, TRACE_STUCK_AFTER_MS, trace } from "./trace";
7
+
8
+ export type {
9
+ TraceAttributes,
10
+ TraceAttributeValue,
11
+ TraceCategory,
12
+ TraceContext,
13
+ TraceEvent,
14
+ TraceListFilter,
15
+ TraceRunStats,
16
+ TraceSeverity,
17
+ TraceSourceGroup,
18
+ TraceSpan,
19
+ TraceSpanKind,
20
+ TraceStatus,
21
+ TraceSummary,
22
+ TraceWindow,
23
+ } from "./trace";
24
+ export { observeSyncEvent, TRACE_STUCK_AFTER_MS, trace };
25
+
26
+ // ── Settings Registration ──────────────────────────────────────────────
27
+
28
+ registerSettings([
29
+ {
30
+ key: "logs.retention_days",
31
+ kind: "number",
32
+ default: 30,
33
+ description: "Automatically delete log entries older than this many days",
34
+ group: "logs",
35
+ },
36
+ {
37
+ key: "logs.trace_retention_days",
38
+ kind: "number",
39
+ default: 30,
40
+ description: "Automatically delete completed traces older than this many days",
41
+ group: "logs",
42
+ },
43
+ ]);
44
+
45
+ // ==========================
46
+ // Types
47
+ // ==========================
48
+
49
+ type LogLevel = "debug" | "info" | "warn" | "error";
50
+
51
+ type WriteParams = {
52
+ level: LogLevel;
53
+ source: string;
54
+ message: string;
55
+ metadata?: Record<string, unknown>;
56
+ };
57
+
58
+ type Logger = {
59
+ debug: (message: string, metadata?: Record<string, unknown>) => void;
60
+ info: (message: string, metadata?: Record<string, unknown>) => void;
61
+ warn: (message: string, metadata?: Record<string, unknown>) => void;
62
+ error: (message: string, metadata?: Record<string, unknown>) => void;
63
+ };
64
+
65
+ export type LogEntry = {
66
+ id: string;
67
+ level: LogLevel;
68
+ source: string;
69
+ message: string;
70
+ metadata: Record<string, unknown> | null;
71
+ createdAt: string;
72
+ };
73
+
74
+ type DbLogRow = {
75
+ id: string | number;
76
+ level: string;
77
+ source: string;
78
+ message: string;
79
+ metadata: Record<string, unknown> | null;
80
+ created_at: string;
81
+ };
82
+
83
+ export type LogStatsGroupBy = "source" | "level";
84
+
85
+ export type LogStatsRow = {
86
+ key: string;
87
+ count: number;
88
+ };
89
+
90
+ export type LogTimeseriesPoint = {
91
+ at: Date;
92
+ debug: number;
93
+ info: number;
94
+ warn: number;
95
+ error: number;
96
+ total: number;
97
+ };
98
+
99
+ // ==========================
100
+ // Helpers
101
+ // ==========================
102
+
103
+ /**
104
+ * Maps one logging row to the public log entry shape.
105
+ */
106
+ const mapRow = (row: DbLogRow): LogEntry => ({
107
+ id: String(row.id),
108
+ level: row.level as LogLevel,
109
+ source: row.source,
110
+ message: row.message,
111
+ metadata: parsePgJsonRecord(row.metadata),
112
+ createdAt: row.created_at,
113
+ });
114
+
115
+ // ==========================
116
+ // Core: Write + Logger
117
+ // ==========================
118
+
119
+ /** Fire-and-forget write — mirrors to console, then inserts to DB async. */
120
+ function write(params: WriteParams): void {
121
+ const safeMetadata = params.metadata ? (redactMetadata(params.metadata) as Record<string, unknown>) : undefined;
122
+ const prefix = `[${params.source}]`;
123
+ const consoleFn = params.level === "error" ? console.error : params.level === "warn" ? console.warn : console.log;
124
+ consoleFn(prefix, params.message, ...(safeMetadata ? [safeMetadata] : []));
125
+
126
+ sql`
127
+ INSERT INTO logging.entries (level, source, message, metadata)
128
+ VALUES (
129
+ ${params.level},
130
+ ${params.source},
131
+ ${params.message},
132
+ ${safeMetadata ? JSON.stringify(safeMetadata) : null}::jsonb
133
+ )
134
+ `.catch((err: Error) => console.error("[logging] DB write failed:", err.message));
135
+ }
136
+
137
+ /**
138
+ * Stateless logger factory. Creates an object with .debug/.info/.warn/.error methods
139
+ * bound to the given source. Can be called inline or cached — no state is held.
140
+ *
141
+ * @example
142
+ * // Inline
143
+ * logger("yjs").info("Document loaded", { noteId });
144
+ *
145
+ * // Cached
146
+ * const log = logger("weather");
147
+ * log.error("API error", { status: 500 });
148
+ */
149
+ export function logger(source: string): Logger {
150
+ return {
151
+ debug: (msg, meta) => write({ level: "debug", source, message: msg, metadata: meta }),
152
+ info: (msg, meta) => write({ level: "info", source, message: msg, metadata: meta }),
153
+ warn: (msg, meta) => write({ level: "warn", source, message: msg, metadata: meta }),
154
+ error: (msg, meta) => write({ level: "error", source, message: msg, metadata: meta }),
155
+ };
156
+ }
157
+
158
+ // ==========================
159
+ // Admin: List / Sources / Cleanup
160
+ // ==========================
161
+
162
+ /** List log entries with pagination and optional filters. */
163
+ const list = async (
164
+ pagination: PaginationParams,
165
+ options?: {
166
+ source?: string;
167
+ sources?: string[];
168
+ level?: string;
169
+ search?: string;
170
+ sinceHours?: number;
171
+ },
172
+ ): Promise<{ entries: LogEntry[]; total: number }> => {
173
+ const { offset, perPage } = pagination;
174
+ const { source, sources, level, search, sinceHours } = options ?? {};
175
+
176
+ const searchPattern = search ? `%${escapeLikePattern(search)}%` : null;
177
+ const filterSource = source && source !== "all" ? source : null;
178
+ const filterSources =
179
+ !filterSource && Array.isArray(sources) && sources.length > 0
180
+ ? [...new Set(sources.map((value) => value.trim()).filter(Boolean))]
181
+ : null;
182
+ const filterSourcesLiteral = filterSources ? toPgTextArray(filterSources) : null;
183
+ const filterLevel = level && level !== "all" ? level : null;
184
+ const hasSourceList = !!filterSources && filterSources.length > 0;
185
+ const filterSinceHours = Number.isFinite(sinceHours) && sinceHours && sinceHours > 0 ? Math.trunc(sinceHours) : null;
186
+
187
+ let countRows: Array<{ count: number }> = [];
188
+ let dataRows: DbLogRow[] = [];
189
+
190
+ if (filterSource) {
191
+ countRows = await sql`
192
+ SELECT COUNT(*)::int as count
193
+ FROM logging.entries
194
+ WHERE source = ${filterSource}
195
+ AND (${filterLevel}::text IS NULL OR level = ${filterLevel})
196
+ AND (${filterSinceHours}::int IS NULL OR created_at >= now() - (${filterSinceHours}::int * INTERVAL '1 hour'))
197
+ AND (${searchPattern}::text IS NULL OR message ILIKE ${searchPattern} ESCAPE '\' OR metadata::text ILIKE ${searchPattern} ESCAPE '\')
198
+ `;
199
+
200
+ dataRows = await sql`
201
+ SELECT id, level, source, message, metadata, created_at
202
+ FROM logging.entries
203
+ WHERE source = ${filterSource}
204
+ AND (${filterLevel}::text IS NULL OR level = ${filterLevel})
205
+ AND (${filterSinceHours}::int IS NULL OR created_at >= now() - (${filterSinceHours}::int * INTERVAL '1 hour'))
206
+ AND (${searchPattern}::text IS NULL OR message ILIKE ${searchPattern} ESCAPE '\' OR metadata::text ILIKE ${searchPattern} ESCAPE '\')
207
+ ORDER BY created_at DESC
208
+ LIMIT ${perPage} OFFSET ${offset}
209
+ `;
210
+ } else if (hasSourceList) {
211
+ countRows = await sql`
212
+ SELECT COUNT(*)::int as count
213
+ FROM logging.entries
214
+ WHERE source = ANY(${filterSourcesLiteral}::text[])
215
+ AND (${filterLevel}::text IS NULL OR level = ${filterLevel})
216
+ AND (${filterSinceHours}::int IS NULL OR created_at >= now() - (${filterSinceHours}::int * INTERVAL '1 hour'))
217
+ AND (${searchPattern}::text IS NULL OR message ILIKE ${searchPattern} ESCAPE '\' OR metadata::text ILIKE ${searchPattern} ESCAPE '\')
218
+ `;
219
+
220
+ dataRows = await sql`
221
+ SELECT id, level, source, message, metadata, created_at
222
+ FROM logging.entries
223
+ WHERE source = ANY(${filterSourcesLiteral}::text[])
224
+ AND (${filterLevel}::text IS NULL OR level = ${filterLevel})
225
+ AND (${filterSinceHours}::int IS NULL OR created_at >= now() - (${filterSinceHours}::int * INTERVAL '1 hour'))
226
+ AND (${searchPattern}::text IS NULL OR message ILIKE ${searchPattern} ESCAPE '\' OR metadata::text ILIKE ${searchPattern} ESCAPE '\')
227
+ ORDER BY created_at DESC
228
+ LIMIT ${perPage} OFFSET ${offset}
229
+ `;
230
+ } else {
231
+ countRows = await sql`
232
+ SELECT COUNT(*)::int as count
233
+ FROM logging.entries
234
+ WHERE (${filterLevel}::text IS NULL OR level = ${filterLevel})
235
+ AND (${filterSinceHours}::int IS NULL OR created_at >= now() - (${filterSinceHours}::int * INTERVAL '1 hour'))
236
+ AND (${searchPattern}::text IS NULL OR message ILIKE ${searchPattern} ESCAPE '\' OR metadata::text ILIKE ${searchPattern} ESCAPE '\')
237
+ `;
238
+
239
+ dataRows = await sql`
240
+ SELECT id, level, source, message, metadata, created_at
241
+ FROM logging.entries
242
+ WHERE (${filterLevel}::text IS NULL OR level = ${filterLevel})
243
+ AND (${filterSinceHours}::int IS NULL OR created_at >= now() - (${filterSinceHours}::int * INTERVAL '1 hour'))
244
+ AND (${searchPattern}::text IS NULL OR message ILIKE ${searchPattern} ESCAPE '\' OR metadata::text ILIKE ${searchPattern} ESCAPE '\')
245
+ ORDER BY created_at DESC
246
+ LIMIT ${perPage} OFFSET ${offset}
247
+ `;
248
+ }
249
+
250
+ return {
251
+ entries: dataRows.map((row: DbLogRow) => mapRow(row)),
252
+ total: countRows[0]?.count ?? 0,
253
+ };
254
+ };
255
+
256
+ /** Get all unique source names (for filter dropdown). */
257
+ const getSources = async (): Promise<string[]> => {
258
+ const rows = await sql`
259
+ SELECT DISTINCT source FROM logging.entries ORDER BY source
260
+ `;
261
+ return rows.map((row: { source: string }) => row.source);
262
+ };
263
+
264
+ const normalizeSinceHours = (sinceHours: number | undefined): number | null =>
265
+ Number.isFinite(sinceHours) && sinceHours && sinceHours > 0 ? Math.trunc(sinceHours) : null;
266
+
267
+ const normalizeStatsLimit = (limit: number | undefined): number => Math.min(Math.max(Math.trunc(limit ?? 50), 1), 200);
268
+
269
+ const logBucketMinutes = (sinceHours: number): number => {
270
+ if (sinceHours <= 1) return 5;
271
+ if (sinceHours <= 24) return 60;
272
+ if (sinceHours <= 168) return 360;
273
+ return 1_440;
274
+ };
275
+
276
+ /**
277
+ * Gap-filled log volume by level for operator charts.
278
+ *
279
+ * The lookback is capped at 30 days and the bucket ladder keeps the response
280
+ * between 12 and 31 points for every window exposed by Gateway Ops.
281
+ */
282
+ const timeseries = async (
283
+ options: { source?: string; sources?: string[]; level?: string; search?: string; sinceHours?: number } = {},
284
+ ): Promise<LogTimeseriesPoint[]> => {
285
+ const sinceHours = Math.min(720, Math.max(1, normalizeSinceHours(options.sinceHours) ?? 24));
286
+ const bucketMinutes = logBucketMinutes(sinceHours);
287
+ const requestedSources = options.source
288
+ ? [options.source]
289
+ : [...new Set((options.sources ?? []).map((value) => value.trim()).filter(Boolean))].slice(0, 100);
290
+ const hasSources = requestedSources.length > 0;
291
+ const sourceLiteral = toPgTextArray(requestedSources);
292
+ const level = options.level && options.level !== "all" ? options.level : null;
293
+ const search = options.search?.trim().slice(0, 200);
294
+ const searchPattern = search ? `%${escapeLikePattern(search)}%` : null;
295
+
296
+ return sql<LogTimeseriesPoint[]>`
297
+ WITH bounds AS (
298
+ SELECT
299
+ now() AS end_at,
300
+ now() - (${sinceHours}::int * INTERVAL '1 hour') AS start_at,
301
+ ${bucketMinutes}::int * INTERVAL '1 minute' AS bucket
302
+ ),
303
+ buckets AS (
304
+ SELECT generate_series(
305
+ date_bin(bucket, start_at, TIMESTAMPTZ '1970-01-01 00:00:00+00'),
306
+ date_bin(bucket, end_at, TIMESTAMPTZ '1970-01-01 00:00:00+00'),
307
+ bucket
308
+ ) AS at
309
+ FROM bounds
310
+ ),
311
+ counts AS (
312
+ SELECT
313
+ date_bin(bounds.bucket, entries.created_at, TIMESTAMPTZ '1970-01-01 00:00:00+00') AS at,
314
+ entries.level,
315
+ COUNT(*)::int AS count
316
+ FROM logging.entries AS entries
317
+ CROSS JOIN bounds
318
+ WHERE entries.created_at >= bounds.start_at
319
+ AND entries.created_at <= bounds.end_at
320
+ AND (${hasSources}::boolean = false OR entries.source = ANY(${sourceLiteral}::text[]))
321
+ AND (${level}::text IS NULL OR entries.level = ${level})
322
+ AND (
323
+ ${searchPattern}::text IS NULL
324
+ OR entries.message ILIKE ${searchPattern} ESCAPE '\'
325
+ OR entries.metadata::text ILIKE ${searchPattern} ESCAPE '\'
326
+ )
327
+ GROUP BY at, entries.level
328
+ )
329
+ SELECT
330
+ buckets.at,
331
+ COALESCE(SUM(counts.count) FILTER (WHERE counts.level = 'debug'), 0)::int AS debug,
332
+ COALESCE(SUM(counts.count) FILTER (WHERE counts.level = 'info'), 0)::int AS info,
333
+ COALESCE(SUM(counts.count) FILTER (WHERE counts.level = 'warn'), 0)::int AS warn,
334
+ COALESCE(SUM(counts.count) FILTER (WHERE counts.level = 'error'), 0)::int AS error,
335
+ COALESCE(SUM(counts.count), 0)::int AS total
336
+ FROM buckets
337
+ LEFT JOIN counts USING (at)
338
+ GROUP BY buckets.at
339
+ ORDER BY buckets.at
340
+ `;
341
+ };
342
+
343
+ /** Group log volume by source or level for admin diagnostics. */
344
+ const statsBy = async (
345
+ groupBy: LogStatsGroupBy,
346
+ options?: {
347
+ sinceHours?: number;
348
+ limit?: number;
349
+ sources?: string[];
350
+ level?: string;
351
+ search?: string;
352
+ },
353
+ ): Promise<LogStatsRow[]> => {
354
+ const filterSinceHours = normalizeSinceHours(options?.sinceHours);
355
+ const limit = normalizeStatsLimit(options?.limit);
356
+ const sources = [...new Set((options?.sources ?? []).map((value) => value.trim()).filter(Boolean))].slice(0, 100);
357
+ const hasSources = sources.length > 0;
358
+ const sourceLiteral = toPgTextArray(sources);
359
+ const level = options?.level && options.level !== "all" ? options.level : null;
360
+ const search = options?.search?.trim().slice(0, 200);
361
+ const searchPattern = search ? `%${escapeLikePattern(search)}%` : null;
362
+
363
+ if (groupBy === "level") {
364
+ const rows = await sql<{ key: string; count: number }[]>`
365
+ SELECT level as key, COUNT(*)::int as count
366
+ FROM logging.entries
367
+ WHERE (${filterSinceHours}::int IS NULL OR created_at >= now() - (${filterSinceHours}::int * INTERVAL '1 hour'))
368
+ AND (${hasSources}::boolean = false OR source = ANY(${sourceLiteral}::text[]))
369
+ AND (${level}::text IS NULL OR level = ${level})
370
+ AND (
371
+ ${searchPattern}::text IS NULL
372
+ OR message ILIKE ${searchPattern} ESCAPE '\'
373
+ OR metadata::text ILIKE ${searchPattern} ESCAPE '\'
374
+ )
375
+ GROUP BY level
376
+ ORDER BY count DESC, key ASC
377
+ LIMIT ${limit}
378
+ `;
379
+ return rows;
380
+ }
381
+
382
+ const rows = await sql<{ key: string; count: number }[]>`
383
+ SELECT source as key, COUNT(*)::int as count
384
+ FROM logging.entries
385
+ WHERE (${filterSinceHours}::int IS NULL OR created_at >= now() - (${filterSinceHours}::int * INTERVAL '1 hour'))
386
+ AND (${hasSources}::boolean = false OR source = ANY(${sourceLiteral}::text[]))
387
+ AND (${level}::text IS NULL OR level = ${level})
388
+ AND (
389
+ ${searchPattern}::text IS NULL
390
+ OR message ILIKE ${searchPattern} ESCAPE '\'
391
+ OR metadata::text ILIKE ${searchPattern} ESCAPE '\'
392
+ )
393
+ GROUP BY source
394
+ ORDER BY count DESC, key ASC
395
+ LIMIT ${limit}
396
+ `;
397
+ return rows;
398
+ };
399
+
400
+ /** Get a single log entry by id. */
401
+ const getById = async (id: string): Promise<LogEntry | null> => {
402
+ const rows = await sql<DbLogRow[]>`
403
+ SELECT id, level, source, message, metadata, created_at
404
+ FROM logging.entries
405
+ WHERE id = ${id}::bigint
406
+ `;
407
+ return rows[0] ? mapRow(rows[0]) : null;
408
+ };
409
+
410
+ /** Delete log entries older than the given number of days. */
411
+ const cleanup = async (olderThanDays: number): Promise<{ deleted: number }> => {
412
+ const rows = await sql`
413
+ DELETE FROM logging.entries
414
+ WHERE created_at < now() - ${olderThanDays + " days"}::interval
415
+ `;
416
+ return { deleted: rows.count };
417
+ };
418
+
419
+ export type LogSummary = {
420
+ total: number;
421
+ errors24h: number;
422
+ warnings24h: number;
423
+ total24h: number;
424
+ sources: number;
425
+ lastErrorAt: string | null;
426
+ };
427
+
428
+ /**
429
+ * Aggregated stats for the admin dashboard. Single SQL roundtrip, but split
430
+ * into independent subqueries so each one can use `idx_logging_entries_level`
431
+ * and `idx_logging_level_created_at` (composite, see migration). The original
432
+ * `COUNT(*) FILTER (...)` form forced a full-table scan because there was no
433
+ * top-level `WHERE`.
434
+ *
435
+ * `last_error_at::text` because `bun.sql` returns timestamps as `Date` and the
436
+ * `LogSummary.lastErrorAt: string | null` contract expects an ISO-ish string.
437
+ */
438
+ const summary = async (): Promise<LogSummary> => {
439
+ const [row] = await sql<
440
+ {
441
+ total: number;
442
+ total_24h: number;
443
+ errors_24h: number;
444
+ warnings_24h: number;
445
+ sources: number;
446
+ last_error_at: string | null;
447
+ }[]
448
+ >`
449
+ SELECT
450
+ (SELECT COUNT(*)::int FROM logging.entries) AS total,
451
+ (SELECT COUNT(*)::int FROM logging.entries WHERE created_at > now() - interval '24 hours') AS total_24h,
452
+ (SELECT COUNT(*)::int FROM logging.entries WHERE level = 'error' AND created_at > now() - interval '24 hours') AS errors_24h,
453
+ (SELECT COUNT(*)::int FROM logging.entries WHERE level = 'warn' AND created_at > now() - interval '24 hours') AS warnings_24h,
454
+ (SELECT COUNT(*)::int FROM (SELECT DISTINCT source FROM logging.entries) s) AS sources,
455
+ (SELECT created_at::text FROM logging.entries WHERE level = 'error' ORDER BY created_at DESC LIMIT 1) AS last_error_at
456
+ `;
457
+ return {
458
+ total: row?.total ?? 0,
459
+ errors24h: row?.errors_24h ?? 0,
460
+ warnings24h: row?.warnings_24h ?? 0,
461
+ total24h: row?.total_24h ?? 0,
462
+ sources: row?.sources ?? 0,
463
+ lastErrorAt: row?.last_error_at ?? null,
464
+ };
465
+ };
466
+
467
+ /** Admin service object for querying/managing logs. */
468
+ export const logging = {
469
+ list,
470
+ getById,
471
+ getSources,
472
+ cleanup,
473
+ summary,
474
+ statsBy,
475
+ timeseries,
476
+ trace,
477
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Keys whose values are scrubbed from log and trace metadata before storage.
3
+ * Match is case-insensitive and substring-based on the key (e.g. `apiKey`,
4
+ * `accessToken`, `clientSecret` all trip).
5
+ */
6
+ const SENSITIVE_KEY_PATTERN = /(password|secret|token|cookie|authorization|api[_-]?key|private[_-]?key|session)/i;
7
+
8
+ export const REDACTED = "[REDACTED]";
9
+
10
+ export const isSensitiveMetadataKey = (key: string): boolean => SENSITIVE_KEY_PATTERN.test(key);
11
+
12
+ export const redactMetadata = (input: unknown): unknown => {
13
+ if (input === null || typeof input !== "object") return input;
14
+ if (Array.isArray(input)) return input.map(redactMetadata);
15
+ const out: Record<string, unknown> = {};
16
+ for (const [key, value] of Object.entries(input)) {
17
+ out[key] = isSensitiveMetadataKey(key) ? REDACTED : redactMetadata(value);
18
+ }
19
+ return out;
20
+ };