@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,855 @@
1
+ import { err, fail, ok, type PageParams, type Paginated, paginate, type Result } from "@k2b/stdlib";
2
+ import { type SQL, sql } from "bun";
3
+ import { z } from "zod";
4
+ import { CapabilityAppIdSchema } from "../../contracts/capabilities";
5
+ import { isAccountCategoryAllowed } from "../account-category-policy";
6
+ import { audit } from "../audit";
7
+ import { logger } from "../logging";
8
+ import { parsePgJsonValue } from "../postgres";
9
+ import { isMandatePolicyNarrowing, type MandatePolicyV1, MandatePolicyV1Schema, mandatePolicyAllows, parseMandatePolicy } from "./policy";
10
+
11
+ export {
12
+ isMandatePolicyNarrowing,
13
+ MANDATE_POLICY_MAX_IDENTIFIERS,
14
+ MANDATE_POLICY_VERSION,
15
+ type MandatePolicyV1,
16
+ MandatePolicyV1Schema,
17
+ mandatePolicyAllows,
18
+ parseMandatePolicy,
19
+ } from "./policy";
20
+
21
+ export type MandateState = "active" | "paused" | "revoked";
22
+ export type MandateSubject = { type: "user"; id: string } | { type: "service_account"; id: string };
23
+ export const MANDATE_CONFIRMATION_TTL_MS = 15 * 60 * 1_000;
24
+ /** One maximum-size provisioning page may await confirmation per creator. */
25
+ export const MANDATE_MAX_PENDING_PER_USER = 100;
26
+
27
+ export type Mandate = {
28
+ id: string;
29
+ subject: MandateSubject;
30
+ ownerAppId: string;
31
+ workloadType: string;
32
+ workloadId: string;
33
+ policy: MandatePolicyV1;
34
+ state: MandateState;
35
+ revision: number;
36
+ expiresAt: string | null;
37
+ confirmedAt: string | null;
38
+ confirmationDeadline: string | null;
39
+ createdByUserId: string | null;
40
+ createdAt: string;
41
+ updatedAt: string;
42
+ revokedAt: string | null;
43
+ revokedByUserId: string | null;
44
+ revokedByAppId: string | null;
45
+ revokeReason: string | null;
46
+ };
47
+
48
+ export type MandateInteractiveAuthority = { kind: "interactive"; userId: string };
49
+ export type MandateAdminAuthority = { kind: "admin"; userId: string };
50
+ export type MandateWorkloadAuthority = { kind: "workload"; ownerAppId: string };
51
+ export type MandateCreateAuthority = MandateInteractiveAuthority | MandateAdminAuthority;
52
+ export type MandateMutationAuthority = MandateInteractiveAuthority | MandateAdminAuthority | MandateWorkloadAuthority;
53
+
54
+ export type MandateMetricName =
55
+ | "issue_allowed"
56
+ | "issue_denied_invalid"
57
+ | "issue_denied_unavailable"
58
+ | "issue_denied_subject"
59
+ | "issue_denied_owner"
60
+ | "issue_denied_inactive"
61
+ | "issue_denied_revision"
62
+ | "issue_denied_policy"
63
+ | "issue_failed_internal";
64
+
65
+ const mandateMetricNames: MandateMetricName[] = [
66
+ "issue_allowed",
67
+ "issue_denied_invalid",
68
+ "issue_denied_unavailable",
69
+ "issue_denied_subject",
70
+ "issue_denied_owner",
71
+ "issue_denied_inactive",
72
+ "issue_denied_revision",
73
+ "issue_denied_policy",
74
+ "issue_failed_internal",
75
+ ];
76
+ const mandateMetricCounters = new Map<MandateMetricName, number>(mandateMetricNames.map((name) => [name, 0]));
77
+
78
+ /** Process-local counters with a fixed label set; no user/workload IDs enter metric cardinality. */
79
+ export const mandateMetrics = {
80
+ increment(name: MandateMetricName): void {
81
+ mandateMetricCounters.set(name, (mandateMetricCounters.get(name) ?? 0) + 1);
82
+ },
83
+ snapshot(): Readonly<Record<MandateMetricName, number>> {
84
+ return Object.freeze(
85
+ Object.fromEntries(mandateMetricNames.map((name) => [name, mandateMetricCounters.get(name) ?? 0])) as Record<
86
+ MandateMetricName,
87
+ number
88
+ >,
89
+ );
90
+ },
91
+ };
92
+
93
+ export type MandateIssueAuthority = {
94
+ mandateId: string;
95
+ mandateRevision: number;
96
+ subject: MandateSubject;
97
+ ownerAppId: string;
98
+ workloadType: string;
99
+ workloadId: string;
100
+ targetAppId: string;
101
+ operation: string;
102
+ policy: MandatePolicyV1;
103
+ };
104
+
105
+ type MandateIssueError = Extract<Result<never>, { ok: false }>["error"];
106
+
107
+ const denyMandateIssue = (error: MandateIssueError): Result<MandateIssueAuthority> => fail(error);
108
+
109
+ type MandateRow = {
110
+ id: string;
111
+ subject_kind: MandateSubject["type"];
112
+ subject_user_id: string | null;
113
+ subject_service_account_id: string | null;
114
+ owner_app_id: string;
115
+ workload_type: string;
116
+ workload_id: string;
117
+ policy: unknown;
118
+ state: MandateState;
119
+ revision: number | bigint;
120
+ expires_at: Date | string | null;
121
+ confirmed_at: Date | string | null;
122
+ confirmation_deadline: Date | string | null;
123
+ created_by_user_id: string | null;
124
+ created_at: Date | string;
125
+ updated_at: Date | string;
126
+ revoked_at: Date | string | null;
127
+ revoked_by_user_id: string | null;
128
+ revoked_by_app_id: string | null;
129
+ revoke_reason: string | null;
130
+ };
131
+
132
+ type MandateCurrentRow = MandateRow & { subject_active: boolean; not_expired: boolean };
133
+
134
+ const UUID = z.string().uuid();
135
+ const WorkloadTypeSchema = z
136
+ .string()
137
+ .trim()
138
+ .min(1)
139
+ .max(120)
140
+ .regex(/^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/);
141
+ const WorkloadIdSchema = z.string().trim().min(1).max(200);
142
+ const OperationSchema = z.string().trim().min(1).max(180);
143
+ const RequestIdSchema = z
144
+ .string()
145
+ .min(1)
146
+ .max(200)
147
+ .regex(/^[\x21-\x7e]+$/);
148
+ const RevisionSchema = z.number().int().positive();
149
+
150
+ const iso = (value: Date | string): string => new Date(value).toISOString();
151
+ const nullableIso = (value: Date | string | null): string | null => (value === null ? null : iso(value));
152
+
153
+ const subjectFromRow = (row: MandateRow): MandateSubject =>
154
+ row.subject_kind === "user"
155
+ ? { type: "user", id: UUID.parse(row.subject_user_id) }
156
+ : { type: "service_account", id: UUID.parse(row.subject_service_account_id) };
157
+
158
+ const mapMandate = (row: MandateRow): Mandate => ({
159
+ id: row.id,
160
+ subject: subjectFromRow(row),
161
+ ownerAppId: row.owner_app_id,
162
+ workloadType: row.workload_type,
163
+ workloadId: row.workload_id,
164
+ policy: parseMandatePolicy(parsePgJsonValue(row.policy)),
165
+ state: row.state,
166
+ revision: Number(row.revision),
167
+ expiresAt: nullableIso(row.expires_at),
168
+ confirmedAt: nullableIso(row.confirmed_at),
169
+ confirmationDeadline: nullableIso(row.confirmation_deadline),
170
+ createdByUserId: row.created_by_user_id,
171
+ createdAt: iso(row.created_at),
172
+ updatedAt: iso(row.updated_at),
173
+ revokedAt: nullableIso(row.revoked_at),
174
+ revokedByUserId: row.revoked_by_user_id,
175
+ revokedByAppId: row.revoked_by_app_id,
176
+ revokeReason: row.revoke_reason,
177
+ });
178
+
179
+ const selectMandate = sql`
180
+ SELECT id, subject_kind, subject_user_id, subject_service_account_id,
181
+ owner_app_id, workload_type, workload_id, policy, state, revision, expires_at,
182
+ confirmed_at, confirmation_deadline,
183
+ created_by_user_id, created_at, updated_at, revoked_at, revoked_by_user_id,
184
+ revoked_by_app_id, revoke_reason
185
+ FROM auth.mandates
186
+ `;
187
+
188
+ const validSubject = async (subject: MandateSubject, db: SQL): Promise<boolean> => {
189
+ if (subject.type === "user") {
190
+ const [row] = await db<{ provider: "local" | "ipa"; profile: "guest" | "user" }[]>`
191
+ SELECT provider, profile FROM auth.users
192
+ WHERE id = ${subject.id}::uuid
193
+ AND (account_expires IS NULL OR account_expires > now())
194
+ `;
195
+ return !!row && (await isAccountCategoryAllowed(row, db));
196
+ }
197
+ const [row] = await db<{ active: boolean }[]>`
198
+ SELECT EXISTS (
199
+ SELECT 1 FROM auth.service_accounts
200
+ WHERE id = ${subject.id}::uuid
201
+ AND kind = 'resource_bound'
202
+ AND status = 'active'
203
+ ) AS active
204
+ `;
205
+ return row?.active === true;
206
+ };
207
+
208
+ const canActForSubject = (authority: MandateInteractiveAuthority | MandateAdminAuthority, subject: MandateSubject): boolean =>
209
+ authority.kind === "admin" || (subject.type === "user" && subject.id === authority.userId);
210
+
211
+ const parsePolicyForWorkload = (
212
+ policy: unknown,
213
+ workload: { subject: MandateSubject; ownerAppId: string; workloadType: string },
214
+ ): Result<MandatePolicyV1> => {
215
+ const parsed = MandatePolicyV1Schema.safeParse(policy);
216
+ if (!parsed.success) return fail(err.badInput(parsed.error.issues[0]?.message ?? "Invalid mandate policy"));
217
+ const hasWildcard = parsed.data.apps === "*" || parsed.data.operations === "*";
218
+ const isCoreAiChatTask = workload.subject.type === "user" && workload.ownerAppId === "core" && workload.workloadType === "ai.chat-task";
219
+ if (hasWildcard && (!isCoreAiChatTask || parsed.data.actions !== "require_approval")) {
220
+ return fail(err.forbidden("Wildcard mandate policy is not allowed for this workload"));
221
+ }
222
+ return ok(parsed.data);
223
+ };
224
+
225
+ const load = async (mandateId: string, db: SQL): Promise<Mandate | null> => {
226
+ if (!UUID.safeParse(mandateId).success) return null;
227
+ const [row] = await db<MandateRow[]>`${selectMandate} WHERE id = ${mandateId}::uuid`;
228
+ return row ? mapMandate(row) : null;
229
+ };
230
+
231
+ const mutationAccess = (mandate: Mandate, authority: MandateMutationAuthority): boolean =>
232
+ authority.kind === "workload" ? authority.ownerAppId === mandate.ownerAppId : canActForSubject(authority, mandate.subject);
233
+
234
+ const mutationAuditActor = (authority: MandateMutationAuthority): { userId: string } | null =>
235
+ (authority.kind === "interactive" || authority.kind === "admin") && UUID.safeParse(authority.userId).success
236
+ ? { userId: authority.userId }
237
+ : null;
238
+
239
+ const mutationAuditMetadata = (authority: MandateMutationAuthority, metadata: Record<string, unknown>): Record<string, unknown> => ({
240
+ ...metadata,
241
+ ...(authority.kind === "workload" ? { ownerAppId: authority.ownerAppId.slice(0, 80) } : {}),
242
+ ...(authority.kind === "admin" ? { adminAction: true } : {}),
243
+ });
244
+
245
+ const createAuditActor = (authority: MandateCreateAuthority): { userId: string } | null =>
246
+ UUID.safeParse(authority.userId).success ? { userId: authority.userId } : null;
247
+
248
+ const createAuditMetadata = (authority: MandateCreateAuthority): Record<string, unknown> =>
249
+ authority.kind === "admin" ? { adminAction: true } : {};
250
+
251
+ const auditedMutation = <T>(
252
+ options: { db?: SQL },
253
+ params: {
254
+ action: string;
255
+ authority: MandateMutationAuthority;
256
+ targetId: (result: Result<T>) => string | null;
257
+ metadata: Record<string, unknown>;
258
+ mutate: (db: SQL) => Promise<Result<T>>;
259
+ },
260
+ ): Promise<Result<T>> => {
261
+ const run = async (db: SQL): Promise<Result<T>> => {
262
+ const result = await params.mutate(db);
263
+ return audit.recordResult({
264
+ action: params.action,
265
+ actor: mutationAuditActor(params.authority),
266
+ target: { type: "mandate", id: params.targetId(result) },
267
+ metadata: mutationAuditMetadata(params.authority, params.metadata),
268
+ result,
269
+ db,
270
+ });
271
+ };
272
+ return options.db ? run(options.db) : sql.begin(run);
273
+ };
274
+
275
+ const changedRow = async (
276
+ mandateId: string,
277
+ expectedRevision: number,
278
+ authority: MandateMutationAuthority,
279
+ db: SQL,
280
+ update: (mandate: Mandate) => Promise<Result<MandateRow | Mandate | null>>,
281
+ ): Promise<Result<Mandate>> => {
282
+ const current = await load(mandateId, db);
283
+ if (!current) return fail(err.notFound("Mandate"));
284
+ if (!mutationAccess(current, authority)) return fail(err.forbidden("Cannot change this mandate"));
285
+ if (current.revision !== expectedRevision) return fail(err.conflict("Mandate revision changed"));
286
+ if (current.state === "revoked") return fail(err.conflict("Mandate is revoked"));
287
+ const result = await update(current);
288
+ if (!result.ok) return result;
289
+ const row = result.data;
290
+ if (row) return ok("ownerAppId" in row ? row : mapMandate(row));
291
+ return fail(err.conflict("Mandate changed; read it and retry"));
292
+ };
293
+
294
+ const lockWorkload = async (ownerAppId: string, workloadType: string, workloadId: string, db: SQL): Promise<void> => {
295
+ const coordinate = JSON.stringify([ownerAppId, workloadType, workloadId]);
296
+ await db`SELECT pg_advisory_xact_lock(hashtextextended(${"mandate-workload:" + coordinate}, 0))`;
297
+ };
298
+
299
+ const createMandateInDb = async (
300
+ input: {
301
+ authority: MandateCreateAuthority;
302
+ subject: MandateSubject;
303
+ ownerAppId: string;
304
+ workloadType: string;
305
+ workloadId: string;
306
+ policy: unknown;
307
+ expiresAt?: string | null;
308
+ confirmation: "immediate" | "owner";
309
+ },
310
+ db: SQL,
311
+ ): Promise<Result<Mandate>> => {
312
+ const authorityUserId = input.authority.userId;
313
+ if (!UUID.safeParse(authorityUserId).success || !UUID.safeParse(input.subject.id).success) {
314
+ return fail(err.badInput("Invalid mandate subject"));
315
+ }
316
+ const ownerAppId = CapabilityAppIdSchema.safeParse(input.ownerAppId.trim());
317
+ const workloadType = WorkloadTypeSchema.safeParse(input.workloadType);
318
+ const workloadId = WorkloadIdSchema.safeParse(input.workloadId);
319
+ if (!ownerAppId.success || !workloadType.success || !workloadId.success) return fail(err.badInput("Invalid mandate workload"));
320
+ if (!canActForSubject(input.authority, input.subject)) return fail(err.forbidden("Cannot create a mandate for this subject"));
321
+ const policy = parsePolicyForWorkload(input.policy, input);
322
+ if (!policy.ok) return policy;
323
+ const expiresAt = input.expiresAt == null ? null : new Date(input.expiresAt);
324
+ if (expiresAt && (!Number.isFinite(expiresAt.getTime()) || expiresAt.getTime() <= Date.now())) {
325
+ return fail(err.badInput("Mandate expiry must be in the future"));
326
+ }
327
+ if (!(await validSubject(input.subject, db))) return fail(err.forbidden("Mandate subject is not active"));
328
+ const ownerConfirmation = input.confirmation === "owner";
329
+ if (ownerConfirmation) {
330
+ // Serialize quota decisions across Core replicas; expired registrations do
331
+ // not consume capacity while the bounded orphan reconciler catches up.
332
+ await db`SELECT pg_advisory_xact_lock(hashtextextended(${"mandate-pending:" + authorityUserId}, 0))`;
333
+ const [pending] = await db<{ count: number }[]>`
334
+ SELECT count(*)::int AS count FROM (
335
+ SELECT 1 FROM auth.mandates
336
+ WHERE created_by_user_id = ${authorityUserId}::uuid
337
+ AND confirmed_at IS NULL AND state IN ('active', 'paused')
338
+ AND confirmation_deadline > now()
339
+ AND (expires_at IS NULL OR expires_at > now())
340
+ LIMIT ${MANDATE_MAX_PENDING_PER_USER}
341
+ ) pending
342
+ `;
343
+ if ((pending?.count ?? 0) >= MANDATE_MAX_PENDING_PER_USER) {
344
+ return fail(err.conflict("Pending mandate limit reached; confirm or revoke an existing registration"));
345
+ }
346
+ } else {
347
+ await lockWorkload(ownerAppId.data, workloadType.data, workloadId.data, db);
348
+ }
349
+ const confirmationTtlSeconds = MANDATE_CONFIRMATION_TTL_MS / 1_000;
350
+ const [row] = await db<MandateRow[]>`
351
+ INSERT INTO auth.mandates (
352
+ subject_kind, subject_user_id, subject_service_account_id,
353
+ owner_app_id, workload_type, workload_id, policy, expires_at,
354
+ confirmed_at, confirmation_deadline, created_by_user_id
355
+ ) VALUES (
356
+ ${input.subject.type},
357
+ ${input.subject.type === "user" ? input.subject.id : null}::uuid,
358
+ ${input.subject.type === "service_account" ? input.subject.id : null}::uuid,
359
+ ${ownerAppId.data}, ${workloadType.data}, ${workloadId.data}, ${policy.data}::jsonb,
360
+ ${expiresAt},
361
+ CASE WHEN ${ownerConfirmation} THEN NULL ELSE now() END,
362
+ CASE WHEN ${ownerConfirmation} THEN now() + (${confirmationTtlSeconds} * interval '1 second') ELSE NULL END,
363
+ ${input.authority.userId}::uuid
364
+ )
365
+ ON CONFLICT DO NOTHING
366
+ RETURNING *
367
+ `;
368
+ return row ? ok(mapMandate(row)) : fail(err.conflict("Mandate workload"));
369
+ };
370
+
371
+ type CreateMandateInput = Omit<Parameters<typeof createMandateInDb>[0], "confirmation">;
372
+
373
+ const createMandateWithConfirmation = (
374
+ input: CreateMandateInput,
375
+ confirmation: "immediate" | "owner",
376
+ options: { db?: SQL } = {},
377
+ ): Promise<Result<Mandate>> => {
378
+ const run = async (db: SQL): Promise<Result<Mandate>> => {
379
+ const result = await createMandateInDb({ ...input, confirmation }, db);
380
+ return audit.recordResult({
381
+ action: "mandate.create",
382
+ actor: createAuditActor(input.authority),
383
+ target: { type: "mandate", id: result.ok ? result.data.id : null },
384
+ metadata: {
385
+ subjectType: input.subject.type,
386
+ ownerAppId: input.ownerAppId.slice(0, 80),
387
+ workloadType: input.workloadType.slice(0, 120),
388
+ workloadId: input.workloadId.slice(0, 200),
389
+ hasExpiry: input.expiresAt != null,
390
+ confirmation,
391
+ ...createAuditMetadata(input.authority),
392
+ },
393
+ result,
394
+ db,
395
+ });
396
+ };
397
+ return options.db ? run(options.db) : sql.begin(run);
398
+ };
399
+
400
+ /** Creates a mandate that is immediately usable inside the caller's workload transaction. */
401
+ export const createMandate = (input: CreateMandateInput, options: { db?: SQL } = {}): Promise<Result<Mandate>> =>
402
+ createMandateWithConfirmation(input, "immediate", options);
403
+
404
+ /** Creates a remote-workload mandate that remains unusable until its owner confirms persistence. */
405
+ export const createPendingMandate = (input: CreateMandateInput, options: { db?: SQL } = {}): Promise<Result<Mandate>> =>
406
+ createMandateWithConfirmation(input, "owner", options);
407
+
408
+ export const getMandate = (mandateId: string, options: { db?: SQL } = {}): Promise<Mandate | null> => load(mandateId, options.db ?? sql);
409
+
410
+ export type MandateListScope = { kind: "user"; userId: string } | { kind: "owner"; ownerAppId: string } | { kind: "admin" };
411
+
412
+ export const listMandates = async (config: {
413
+ scope: MandateListScope;
414
+ pagination?: PageParams;
415
+ filter?: { ownerAppId?: string; state?: MandateState };
416
+ db?: SQL;
417
+ }): Promise<Paginated<Mandate>> => {
418
+ const db = config.db ?? sql;
419
+ const requestedPerPage = config.pagination?.perPage ?? 50;
420
+ const { page, perPage, offset } = paginate({
421
+ ...config.pagination,
422
+ perPage: Math.max(1, Math.min(100, Number.isFinite(requestedPerPage) ? Math.floor(requestedPerPage) : 50)),
423
+ });
424
+ const conditions = [sql`TRUE`];
425
+ if (config.scope.kind === "user") {
426
+ const userId = UUID.safeParse(config.scope.userId);
427
+ conditions.push(userId.success ? sql`subject_kind = 'user' AND subject_user_id = ${userId.data}::uuid` : sql`FALSE`);
428
+ } else if (config.scope.kind === "owner") {
429
+ const owner = CapabilityAppIdSchema.safeParse(config.scope.ownerAppId);
430
+ conditions.push(owner.success ? sql`owner_app_id = ${owner.data}` : sql`FALSE`);
431
+ }
432
+ if (config.filter?.ownerAppId) {
433
+ const owner = CapabilityAppIdSchema.safeParse(config.filter.ownerAppId);
434
+ conditions.push(owner.success ? sql`owner_app_id = ${owner.data}` : sql`FALSE`);
435
+ }
436
+ if (config.filter?.state) conditions.push(sql`state = ${config.filter.state}`);
437
+ const where = conditions.slice(1).reduce((combined, condition) => sql`${combined} AND ${condition}`, conditions[0]!);
438
+ const [countRows, rows] = await Promise.all([
439
+ db<{ count: number }[]>`SELECT COUNT(*)::int AS count FROM auth.mandates WHERE ${where}`,
440
+ db<MandateRow[]>`${selectMandate} WHERE ${where} ORDER BY created_at DESC, id DESC LIMIT ${perPage} OFFSET ${offset}`,
441
+ ]);
442
+ const total = countRows[0]?.count ?? 0;
443
+ return { items: rows.map(mapMandate), page, perPage, total, hasNext: page * perPage < total };
444
+ };
445
+
446
+ const confirmMandateInDb = async (
447
+ input: { mandateId: string; expectedRevision: number; authority: MandateWorkloadAuthority },
448
+ db: SQL,
449
+ ): Promise<Result<Mandate>> => {
450
+ let existing = await load(input.mandateId, db);
451
+ if (!existing) return fail(err.notFound("Mandate"));
452
+ if (existing.ownerAppId !== input.authority.ownerAppId) return fail(err.forbidden("Cannot confirm this mandate"));
453
+ await lockWorkload(existing.ownerAppId, existing.workloadType, existing.workloadId, db);
454
+ existing = await load(input.mandateId, db);
455
+ if (!existing) return fail(err.notFound("Mandate"));
456
+ if (existing.state === "revoked") return fail(err.conflict("Mandate is revoked"));
457
+ if (existing.revision !== input.expectedRevision) return fail(err.conflict("Mandate revision changed"));
458
+ if (existing.confirmedAt) return ok(existing);
459
+ const [claimed] = await db<{ id: string }[]>`
460
+ SELECT id FROM auth.mandates
461
+ WHERE owner_app_id = ${existing.ownerAppId}
462
+ AND workload_type = ${existing.workloadType} AND workload_id = ${existing.workloadId}
463
+ AND confirmed_at IS NOT NULL AND state IN ('active', 'paused')
464
+ AND id <> ${existing.id}::uuid
465
+ `;
466
+ if (claimed) return fail(err.conflict("Mandate workload already has confirmed authority"));
467
+ const [row] = await db<MandateRow[]>`
468
+ UPDATE auth.mandates
469
+ SET confirmed_at = now(), confirmation_deadline = NULL, updated_at = now()
470
+ WHERE id = ${existing.id}::uuid
471
+ AND revision = ${input.expectedRevision}::bigint
472
+ AND state <> 'revoked'
473
+ AND confirmed_at IS NULL
474
+ AND confirmation_deadline > now()
475
+ RETURNING *
476
+ `;
477
+ return row ? ok(mapMandate(row)) : fail(err.conflict("Mandate confirmation expired or changed"));
478
+ };
479
+
480
+ export const confirmMandate = (input: Parameters<typeof confirmMandateInDb>[0], options: { db?: SQL } = {}): Promise<Result<Mandate>> =>
481
+ auditedMutation(options, {
482
+ action: "mandate.confirm",
483
+ authority: input.authority,
484
+ targetId: () => input.mandateId,
485
+ metadata: { expectedRevision: input.expectedRevision },
486
+ mutate: (db) => confirmMandateInDb(input, db),
487
+ });
488
+
489
+ const updateMandatePolicyInDb = async (
490
+ input: {
491
+ mandateId: string;
492
+ expectedRevision: number;
493
+ authority: MandateMutationAuthority;
494
+ policy: unknown;
495
+ },
496
+ db: SQL,
497
+ ): Promise<Result<Mandate>> => {
498
+ return changedRow(input.mandateId, input.expectedRevision, input.authority, db, async (current) => {
499
+ const policy = parsePolicyForWorkload(input.policy, {
500
+ subject: current.subject,
501
+ ownerAppId: current.ownerAppId,
502
+ workloadType: current.workloadType,
503
+ });
504
+ if (!policy.ok) return policy;
505
+ if (input.authority.kind === "workload" && !isMandatePolicyNarrowing(current.policy, policy.data)) {
506
+ return fail(err.forbidden("Workloads may only narrow mandate policy"));
507
+ }
508
+ if (JSON.stringify(current.policy) === JSON.stringify(policy.data)) return ok(current);
509
+ const [row] = await db<MandateRow[]>`
510
+ UPDATE auth.mandates
511
+ SET policy = ${policy.data}::jsonb, revision = revision + 1, updated_at = now()
512
+ WHERE id = ${current.id}::uuid AND revision = ${input.expectedRevision}::bigint AND state <> 'revoked'
513
+ RETURNING *
514
+ `;
515
+ return ok(row ?? null);
516
+ });
517
+ };
518
+
519
+ export const updateMandatePolicy = (
520
+ input: Parameters<typeof updateMandatePolicyInDb>[0],
521
+ options: { db?: SQL } = {},
522
+ ): Promise<Result<Mandate>> =>
523
+ auditedMutation(options, {
524
+ action: "mandate.policy.update",
525
+ authority: input.authority,
526
+ targetId: () => input.mandateId,
527
+ metadata: { expectedRevision: input.expectedRevision },
528
+ mutate: (db) => updateMandatePolicyInDb(input, db),
529
+ });
530
+
531
+ /** Policy is the only mutable authority payload; lifecycle has explicit operations below. */
532
+ export const updateMandate = updateMandatePolicy;
533
+
534
+ const setMandateState = async (
535
+ input: { mandateId: string; expectedRevision: number; authority: MandateMutationAuthority },
536
+ state: "paused" | "active",
537
+ options: { db?: SQL } = {},
538
+ ): Promise<Result<Mandate>> => {
539
+ const db = options.db ?? sql;
540
+ return changedRow(input.mandateId, input.expectedRevision, input.authority, db, async (current) => {
541
+ if (state === "active" && input.authority.kind === "workload") return fail(err.forbidden("Workloads cannot resume mandates"));
542
+ if (state === "active" && !(await validSubject(current.subject, db))) return ok(null);
543
+ if (current.state === state) return ok(current);
544
+ if (state === "paused" && current.state !== "active") return ok(null);
545
+ if (state === "active" && current.state !== "paused") return ok(null);
546
+ const [row] = await db<MandateRow[]>`
547
+ UPDATE auth.mandates
548
+ SET state = ${state}, revision = revision + 1, updated_at = now()
549
+ WHERE id = ${current.id}::uuid AND revision = ${input.expectedRevision}::bigint AND state = ${current.state}
550
+ RETURNING *
551
+ `;
552
+ return ok(row ?? null);
553
+ });
554
+ };
555
+
556
+ export const pauseMandate = (
557
+ input: { mandateId: string; expectedRevision: number; authority: MandateMutationAuthority },
558
+ options: { db?: SQL } = {},
559
+ ): Promise<Result<Mandate>> =>
560
+ auditedMutation(options, {
561
+ action: "mandate.pause",
562
+ authority: input.authority,
563
+ targetId: () => input.mandateId,
564
+ metadata: { expectedRevision: input.expectedRevision },
565
+ mutate: (db) => setMandateState(input, "paused", { db }),
566
+ });
567
+
568
+ export const resumeMandate = (
569
+ input: { mandateId: string; expectedRevision: number; authority: MandateInteractiveAuthority | MandateAdminAuthority },
570
+ options: { db?: SQL } = {},
571
+ ): Promise<Result<Mandate>> =>
572
+ auditedMutation(options, {
573
+ action: "mandate.resume",
574
+ authority: input.authority,
575
+ targetId: () => input.mandateId,
576
+ metadata: { expectedRevision: input.expectedRevision },
577
+ mutate: (db) => setMandateState(input, "active", { db }),
578
+ });
579
+
580
+ const revokeMandateInDb = async (
581
+ input: { mandateId: string; expectedRevision: number; authority: MandateMutationAuthority; reason: string },
582
+ db: SQL,
583
+ ): Promise<Result<Mandate>> => {
584
+ const reason = input.reason.trim();
585
+ if (!reason || reason.length > 500) return fail(err.badInput("Mandate revocation reason must be between 1 and 500 characters"));
586
+ const existing = await load(input.mandateId, db);
587
+ if (!existing) return fail(err.notFound("Mandate"));
588
+ if (!mutationAccess(existing, input.authority)) return fail(err.forbidden("Cannot revoke this mandate"));
589
+ if (existing.state === "revoked") return ok(existing);
590
+ if (existing.revision !== input.expectedRevision) return fail(err.conflict("Mandate revision changed"));
591
+ const [row] = await db<MandateRow[]>`
592
+ UPDATE auth.mandates
593
+ SET state = 'revoked', revision = revision + 1, updated_at = now(), revoked_at = now(),
594
+ revoked_by_user_id = ${input.authority.kind === "interactive" || input.authority.kind === "admin" ? input.authority.userId : null}::uuid,
595
+ revoked_by_app_id = ${input.authority.kind === "workload" ? input.authority.ownerAppId : null},
596
+ revoke_reason = ${reason}
597
+ WHERE id = ${existing.id}::uuid AND revision = ${input.expectedRevision}::bigint AND state <> 'revoked'
598
+ RETURNING *
599
+ `;
600
+ return row ? ok(mapMandate(row)) : fail(err.conflict("Mandate changed; read it and retry"));
601
+ };
602
+
603
+ export const revokeMandate = (input: Parameters<typeof revokeMandateInDb>[0], options: { db?: SQL } = {}): Promise<Result<Mandate>> =>
604
+ auditedMutation(options, {
605
+ action: "mandate.revoke",
606
+ authority: input.authority,
607
+ targetId: () => input.mandateId,
608
+ metadata: { expectedRevision: input.expectedRevision },
609
+ mutate: (db) => revokeMandateInDb(input, db),
610
+ });
611
+
612
+ export const validateMandateIssueAuthority = async (
613
+ input: {
614
+ mandateId: string;
615
+ ownerAppId: string;
616
+ targetAppId: string;
617
+ operation: string;
618
+ actionApproval?: "none" | "approved";
619
+ expectedRevision?: number;
620
+ requestId?: string;
621
+ },
622
+ options: { db?: SQL } = {},
623
+ ): Promise<Result<MandateIssueAuthority>> => {
624
+ if (!UUID.safeParse(input.mandateId).success) {
625
+ return denyMandateIssue(err.forbidden("Mandate identifier is invalid"));
626
+ }
627
+ const owner = CapabilityAppIdSchema.safeParse(input.ownerAppId);
628
+ const target = CapabilityAppIdSchema.safeParse(input.targetAppId);
629
+ const operation = OperationSchema.safeParse(input.operation);
630
+ const expectedRevision = input.expectedRevision === undefined ? null : RevisionSchema.safeParse(input.expectedRevision);
631
+ const requestId = input.requestId === undefined ? null : RequestIdSchema.safeParse(input.requestId);
632
+ if (!owner.success || !target.success || !operation.success || expectedRevision?.success === false || requestId?.success === false) {
633
+ return denyMandateIssue(err.badInput("Invalid mandate invocation"));
634
+ }
635
+ const db = options.db ?? sql;
636
+ const [row] = await db<MandateCurrentRow[]>`
637
+ SELECT mandate.*,
638
+ (mandate.expires_at IS NULL OR mandate.expires_at > now()) AS not_expired,
639
+ CASE mandate.subject_kind
640
+ WHEN 'user' THEN user_account.id IS NOT NULL
641
+ AND (user_account.account_expires IS NULL OR user_account.account_expires > now())
642
+ WHEN 'service_account' THEN service_account.id IS NOT NULL
643
+ AND service_account.kind = 'resource_bound'
644
+ AND service_account.status = 'active'
645
+ ELSE false
646
+ END AS subject_active
647
+ FROM auth.mandates mandate
648
+ LEFT JOIN auth.users user_account ON user_account.id = mandate.subject_user_id
649
+ LEFT JOIN auth.service_accounts service_account ON service_account.id = mandate.subject_service_account_id
650
+ WHERE mandate.id = ${input.mandateId}::uuid
651
+ LIMIT 1
652
+ FOR SHARE OF mandate
653
+ `;
654
+ if (!row) return denyMandateIssue(err.forbidden("Mandate is unavailable"));
655
+ if (!row.subject_active || !(await validSubject(subjectFromRow(row), db)))
656
+ return denyMandateIssue(err.forbidden("Mandate subject is unavailable"));
657
+ if (row.owner_app_id !== owner.data) {
658
+ return denyMandateIssue(err.forbidden("Mandate owner does not match"));
659
+ }
660
+ if (row.state !== "active" || row.confirmed_at === null || !row.not_expired) {
661
+ return denyMandateIssue(err.forbidden("Mandate is not active"));
662
+ }
663
+ if (input.expectedRevision !== undefined && Number(row.revision) !== input.expectedRevision) {
664
+ return denyMandateIssue(err.conflict("Mandate revision changed"));
665
+ }
666
+ let policy: MandatePolicyV1;
667
+ try {
668
+ policy = parseMandatePolicy(parsePgJsonValue(row.policy));
669
+ } catch {
670
+ return denyMandateIssue(err.forbidden("Mandate policy is invalid"));
671
+ }
672
+ if (!mandatePolicyAllows(policy, { appId: target.data, operation: operation.data, actionApproval: input.actionApproval ?? "none" })) {
673
+ return denyMandateIssue(err.forbidden("Mandate policy does not allow this operation"));
674
+ }
675
+ const mandate = mapMandate(row);
676
+ return ok({
677
+ mandateId: mandate.id,
678
+ mandateRevision: mandate.revision,
679
+ subject: mandate.subject,
680
+ ownerAppId: mandate.ownerAppId,
681
+ workloadType: mandate.workloadType,
682
+ workloadId: mandate.workloadId,
683
+ targetAppId: target.data,
684
+ operation: operation.data,
685
+ policy,
686
+ });
687
+ };
688
+
689
+ const metricForIssueDenial = (error: MandateIssueError): Exclude<MandateMetricName, "issue_allowed"> => {
690
+ switch (error.message) {
691
+ case "Mandate is unavailable":
692
+ return "issue_denied_unavailable";
693
+ case "Mandate subject is unavailable":
694
+ return "issue_denied_subject";
695
+ case "Mandate owner does not match":
696
+ return "issue_denied_owner";
697
+ case "Mandate is not active":
698
+ return "issue_denied_inactive";
699
+ case "Mandate revision changed":
700
+ return "issue_denied_revision";
701
+ case "Mandate policy is invalid":
702
+ case "Mandate policy does not allow this operation":
703
+ return "issue_denied_policy";
704
+ default:
705
+ return "issue_denied_invalid";
706
+ }
707
+ };
708
+
709
+ const issueAuditParams = <T>(
710
+ input: Parameters<typeof validateMandateIssueAuthority>[0],
711
+ result: Result<T>,
712
+ mandateRevision: number | null,
713
+ db: SQL,
714
+ ) => {
715
+ const requestId = input.requestId === undefined ? null : RequestIdSchema.safeParse(input.requestId);
716
+ return {
717
+ action: "mandate.issue",
718
+ target: { type: "mandate", id: input.mandateId.slice(0, 36) },
719
+ requestId: requestId?.success ? requestId.data : null,
720
+ metadata: {
721
+ ownerAppId: input.ownerAppId.slice(0, 80),
722
+ targetAppId: input.targetAppId.slice(0, 80),
723
+ operation: input.operation.slice(0, 180),
724
+ revision: mandateRevision,
725
+ ...(requestId?.success ? { requestId: requestId.data } : {}),
726
+ provenance: "background-broker",
727
+ },
728
+ result,
729
+ db,
730
+ };
731
+ };
732
+
733
+ /**
734
+ * Holds the mandate row through issuance so a concurrent pause or revoke
735
+ * cannot commit between validation and the caller's in-memory signing step.
736
+ */
737
+ export const withMandateIssueAuthority = <T>(
738
+ input: Parameters<typeof validateMandateIssueAuthority>[0],
739
+ use: (authority: MandateIssueAuthority) => Promise<T>,
740
+ options: { db?: SQL } = {},
741
+ ): Promise<Result<T>> => {
742
+ const run = async (db: SQL): Promise<Result<T>> => {
743
+ const authority = await validateMandateIssueAuthority(input, { db });
744
+ if (!authority.ok) {
745
+ mandateMetrics.increment(metricForIssueDenial(authority.error));
746
+ return audit.recordResult(issueAuditParams(input, authority, input.expectedRevision ?? null, db));
747
+ }
748
+ let issued: Result<T>;
749
+ let callbackFailed = false;
750
+ try {
751
+ issued = ok(await use(authority.data));
752
+ } catch {
753
+ callbackFailed = true;
754
+ issued = fail(err.internal("Mandate invocation issuance failed"));
755
+ }
756
+ const audited = await audit.recordResult(issueAuditParams(input, issued, authority.data.mandateRevision, db));
757
+ mandateMetrics.increment(callbackFailed ? "issue_failed_internal" : "issue_allowed");
758
+ return audited;
759
+ };
760
+ // Return failures as data so an enclosing key-lock transaction can commit
761
+ // the failure audit before its caller maps the Result to an HTTP error.
762
+ return options.db ? run(options.db) : sql.begin(run);
763
+ };
764
+
765
+ const ORPHAN_RECONCILE_LIMIT = 100;
766
+ const ORPHAN_RECONCILE_INTERVAL_MS = 60_000;
767
+ const log = logger("mandates");
768
+ let mandateMaintenanceTimer: ReturnType<typeof setInterval> | null = null;
769
+ let mandateMaintenanceRunning = false;
770
+
771
+ /** Revokes a bounded batch of remote mandates whose owner never confirmed persistence. */
772
+ export const reconcileUnconfirmedMandates = async (options: { db?: SQL; limit?: number } = {}): Promise<number> => {
773
+ const limit = Math.max(1, Math.min(ORPHAN_RECONCILE_LIMIT, Math.floor(options.limit ?? ORPHAN_RECONCILE_LIMIT)));
774
+ const run = async (db: SQL): Promise<number> => {
775
+ const rows = await db<MandateRow[]>`
776
+ WITH candidates AS (
777
+ SELECT id
778
+ FROM auth.mandates
779
+ WHERE state IN ('active', 'paused')
780
+ AND confirmed_at IS NULL
781
+ AND confirmation_deadline <= now()
782
+ ORDER BY confirmation_deadline, id
783
+ LIMIT ${limit}
784
+ FOR UPDATE SKIP LOCKED
785
+ )
786
+ UPDATE auth.mandates mandate
787
+ SET state = 'revoked',
788
+ revision = mandate.revision + 1,
789
+ updated_at = now(),
790
+ revoked_at = now(),
791
+ revoke_reason = 'Workload registration was not confirmed'
792
+ FROM candidates
793
+ WHERE mandate.id = candidates.id
794
+ RETURNING mandate.*
795
+ `;
796
+ for (const row of rows) {
797
+ await audit.record(
798
+ {
799
+ action: "mandate.orphan.revoke",
800
+ outcome: "allowed",
801
+ target: { type: "mandate", id: row.id },
802
+ reason: "Workload registration was not confirmed",
803
+ metadata: {
804
+ ownerAppId: row.owner_app_id,
805
+ workloadType: row.workload_type,
806
+ workloadId: row.workload_id,
807
+ revision: Number(row.revision),
808
+ },
809
+ },
810
+ db,
811
+ );
812
+ }
813
+ return rows.length;
814
+ };
815
+ return options.db ? run(options.db) : sql.begin(run);
816
+ };
817
+
818
+ export const startMandateMaintenance = (): (() => void) => {
819
+ if (mandateMaintenanceTimer) return () => undefined;
820
+ const reconcile = async (): Promise<void> => {
821
+ if (mandateMaintenanceRunning) return;
822
+ mandateMaintenanceRunning = true;
823
+ try {
824
+ await reconcileUnconfirmedMandates();
825
+ } catch (error) {
826
+ log.error("Mandate orphan reconciliation failed", { error: error instanceof Error ? error.message : String(error) });
827
+ } finally {
828
+ mandateMaintenanceRunning = false;
829
+ }
830
+ };
831
+ void reconcile();
832
+ mandateMaintenanceTimer = setInterval(() => void reconcile(), ORPHAN_RECONCILE_INTERVAL_MS);
833
+ mandateMaintenanceTimer.unref?.();
834
+ return () => {
835
+ if (mandateMaintenanceTimer) clearInterval(mandateMaintenanceTimer);
836
+ mandateMaintenanceTimer = null;
837
+ };
838
+ };
839
+
840
+ export const mandates = {
841
+ create: createMandate,
842
+ createPending: createPendingMandate,
843
+ get: getMandate,
844
+ list: listMandates,
845
+ confirm: confirmMandate,
846
+ update: updateMandate,
847
+ updatePolicy: updateMandatePolicy,
848
+ pause: pauseMandate,
849
+ resume: resumeMandate,
850
+ revoke: revokeMandate,
851
+ validateIssueAuthority: validateMandateIssueAuthority,
852
+ withIssueAuthority: withMandateIssueAuthority,
853
+ reconcileUnconfirmed: reconcileUnconfirmedMandates,
854
+ metrics: mandateMetrics,
855
+ } as const;