@opengeni/api-router 3.0.1 → 3.1.2

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 (546) hide show
  1. package/dist/access-grant-rls.d.ts +8 -1
  2. package/dist/admission-trace.d.ts +8 -0
  3. package/dist/app.d.ts +50 -3
  4. package/dist/app.js +10 -1
  5. package/dist/artifact-catalog-cursor.d.ts +1 -0
  6. package/dist/attached-browser-viewer.d.ts +11 -0
  7. package/dist/auth/managed-auth-attempt-context.d.ts +2 -0
  8. package/dist/auth/managed-auth-rate-limits.d.ts +167 -0
  9. package/dist/auth/managed-auth-session-adapter.d.ts +9 -1
  10. package/dist/auth/managed-auth.d.ts +72 -5
  11. package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
  12. package/dist/auth/new-signups-gate.d.ts +23 -0
  13. package/dist/auth/signup-funnel-metrics.d.ts +44 -0
  14. package/dist/azure-live.d.ts +28 -0
  15. package/dist/browser-action-fences.d.ts +13 -0
  16. package/dist/browser-controller-authority.d.ts +1 -61
  17. package/dist/browser-ephemeral.d.ts +5 -0
  18. package/dist/browser-lost-runtime-end.d.ts +24 -0
  19. package/dist/browser-state-authority.d.ts +1 -27
  20. package/dist/chunk-OGQDXVXY.js +18 -0
  21. package/dist/chunk-OGQDXVXY.js.map +1 -0
  22. package/dist/{chunk-AJ62T5QN.js → chunk-OT56OY4V.js} +80503 -39869
  23. package/dist/chunk-OT56OY4V.js.map +1 -0
  24. package/dist/claude-default-models.d.ts +19 -0
  25. package/dist/claude-subscription-core.d.ts +50 -0
  26. package/dist/claude-subscription-oauth.d.ts +36 -0
  27. package/dist/claude-subscription-profile.d.ts +9 -0
  28. package/dist/claude-subscription-usage.d.ts +6 -0
  29. package/dist/claude-workspace-connection.d.ts +15 -0
  30. package/dist/codemode.d.ts +40 -5
  31. package/dist/codex-realtime.d.ts +29 -3
  32. package/dist/codex-session-pointers.d.ts +19 -0
  33. package/dist/connection-ownership.d.ts +6 -3
  34. package/dist/controller-data-plane.d.ts +16 -0
  35. package/dist/credit-promotion-snapshot.d.ts +6 -0
  36. package/dist/editable-artifact-source-session.d.ts +3 -0
  37. package/dist/editable-artifact-websocket.d.ts +5 -3
  38. package/dist/fatal-process-boundary.d.ts +9 -1
  39. package/dist/github-access.d.ts +7 -1
  40. package/dist/github-browser-flow.d.ts +1 -1
  41. package/dist/http/acting-person.d.ts +36 -0
  42. package/dist/http/api-contract-compat.d.ts +19 -0
  43. package/dist/http/api-error.d.ts +50 -1
  44. package/dist/http/auth.d.ts +8 -1
  45. package/dist/http/cors.d.ts +12 -0
  46. package/dist/http/deprecation.d.ts +42 -0
  47. package/dist/http/keyed-admission.d.ts +12 -0
  48. package/dist/http/local-browser-boundary.d.ts +72 -0
  49. package/dist/http/metrics-listener.d.ts +22 -0
  50. package/dist/http/path-identifier.d.ts +17 -0
  51. package/dist/http/registered-route-label.d.ts +16 -0
  52. package/dist/http/rejection-telemetry.d.ts +34 -0
  53. package/dist/http/request-body.d.ts +27 -0
  54. package/dist/http/request-source.d.ts +41 -3
  55. package/dist/http/sse.d.ts +12 -1
  56. package/dist/http/unmatched-route.d.ts +18 -0
  57. package/dist/http/user-content.d.ts +36 -0
  58. package/dist/http/variable-sets-removed.d.ts +41 -0
  59. package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
  60. package/dist/index.js +1279 -108
  61. package/dist/index.js.map +1 -1
  62. package/dist/integration-connect-metrics.d.ts +35 -0
  63. package/dist/integration-endpoint-test.d.ts +20 -0
  64. package/dist/integrations/api-integrations.d.ts +2 -2
  65. package/dist/integrations/atlassian.d.ts +8 -3
  66. package/dist/integrations/fiken.d.ts +3 -1
  67. package/dist/integrations/github-lens-connect.d.ts +37 -66
  68. package/dist/integrations/google-drive.d.ts +2 -0
  69. package/dist/integrations/integration-commit-authority.d.ts +13 -0
  70. package/dist/integrations/marketplace-plugin.d.ts +43 -43
  71. package/dist/integrations/oauth-client.d.ts +171 -12
  72. package/dist/integrations/oauth-profiles.d.ts +42 -13
  73. package/dist/integrations/onepassword-vault-provider.d.ts +9 -0
  74. package/dist/integrations/slack-bot.d.ts +285 -7
  75. package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
  76. package/dist/integrations/slack-interactions.d.ts +89 -10
  77. package/dist/integrations/slack-mrkdwn.d.ts +41 -0
  78. package/dist/integrations/slack-routing.d.ts +1 -1
  79. package/dist/integrations/slack-session-defaults.d.ts +58 -0
  80. package/dist/integrations/slack-task-file-upload.d.ts +36 -0
  81. package/dist/integrations/slack-tool-review.d.ts +5 -0
  82. package/dist/interaction-agent-access.d.ts +13 -8
  83. package/dist/interaction-agent-version.d.ts +21 -0
  84. package/dist/interaction-holder-heartbeat.d.ts +5 -0
  85. package/dist/interaction-metrics.d.ts +27 -2
  86. package/dist/interaction-route-stages.d.ts +25 -0
  87. package/dist/interaction-secrets-vault.d.ts +20 -0
  88. package/dist/interaction-secrets.d.ts +103 -0
  89. package/dist/mcp/action-catalog-types.d.ts +14 -0
  90. package/dist/mcp/action-catalog.gen.d.ts +2 -0
  91. package/dist/mcp/action-descriptions.d.ts +4 -0
  92. package/dist/mcp/action-schema-imports.gen.d.ts +1 -0
  93. package/dist/mcp/capability-account-readiness.d.ts +7 -0
  94. package/dist/mcp/child-read-evidence.d.ts +9 -0
  95. package/dist/mcp/company-profile-agent-admin.d.ts +12 -0
  96. package/dist/mcp/contract-input.d.ts +13 -0
  97. package/dist/mcp/documents.d.ts +2 -0
  98. package/dist/mcp/first-party-tool-permissions.d.ts +3 -606
  99. package/dist/mcp/notification-tools.d.ts +26 -0
  100. package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
  101. package/dist/mcp/scheduled-task-input.d.ts +1309 -0
  102. package/dist/mcp/scheduled-task-view.d.ts +217 -3
  103. package/dist/mcp/secret-tools.d.ts +14 -0
  104. package/dist/mcp/server.d.ts +108 -5
  105. package/dist/mcp/session-admin-access-tools.d.ts +33 -0
  106. package/dist/mcp/session-event-errors.d.ts +28 -0
  107. package/dist/mcp/session-event-view.d.ts +62 -0
  108. package/dist/mcp/session-view.d.ts +15 -0
  109. package/dist/mcp/session-wait.d.ts +18 -0
  110. package/dist/mcp/site-agent-clamp-tools.d.ts +38 -0
  111. package/dist/mcp/site-data-tools.d.ts +22 -0
  112. package/dist/mcp/site-publication-tools.d.ts +19 -0
  113. package/dist/mcp/site-sql-tools.d.ts +27 -0
  114. package/dist/mcp-oauth.d.ts +37 -4
  115. package/dist/model-calls/chat-completions.d.ts +87 -0
  116. package/dist/model-calls/pending-usage-settlements.d.ts +57 -0
  117. package/dist/model-calls/service.d.ts +36 -0
  118. package/dist/model-catalog.d.ts +9 -3
  119. package/dist/native-push-dispatch.d.ts +69 -0
  120. package/dist/observability.d.ts +3 -1
  121. package/dist/organization-mcp.d.ts +82 -0
  122. package/dist/prepared-mcp-actor.d.ts +77 -0
  123. package/dist/prepared-mcp-permissions.d.ts +8 -0
  124. package/dist/routes/analytics-consent.d.ts +26 -0
  125. package/dist/routes/api-integrations.d.ts +11 -0
  126. package/dist/routes/api-keys.d.ts +7 -7
  127. package/dist/routes/automations.d.ts +2 -0
  128. package/dist/routes/billing.d.ts +59 -0
  129. package/dist/routes/browser-sessions.d.ts +65 -2
  130. package/dist/routes/chat-completions.d.ts +11 -0
  131. package/dist/routes/claude-core-usage.d.ts +20 -0
  132. package/dist/routes/claude-subscription-accounts.d.ts +3 -0
  133. package/dist/routes/claude-subscription-oauth.d.ts +3 -0
  134. package/dist/routes/client-errors.d.ts +182 -0
  135. package/dist/routes/codex-core.d.ts +401 -0
  136. package/dist/routes/codex.d.ts +64 -2
  137. package/dist/routes/computer-sessions.d.ts +6 -1
  138. package/dist/routes/desktop-distribution.d.ts +32 -0
  139. package/dist/routes/github-browser-fonts.gen.d.ts +2 -0
  140. package/dist/routes/github-browser-pages.d.ts +27 -0
  141. package/dist/routes/inbox.d.ts +13 -0
  142. package/dist/routes/insights-response-cache.d.ts +10 -0
  143. package/dist/routes/insights-usage.d.ts +12 -0
  144. package/dist/routes/insights.d.ts +28 -0
  145. package/dist/routes/interaction-placement-message.d.ts +7 -0
  146. package/dist/routes/managed-sign-in-methods.d.ts +6 -0
  147. package/dist/routes/model-account-choice.d.ts +13 -0
  148. package/dist/routes/model-account-settings.d.ts +19 -0
  149. package/dist/routes/native-app-auth.d.ts +12 -0
  150. package/dist/routes/organization-integration-policy.d.ts +8 -0
  151. package/dist/routes/organization-integrations.d.ts +6 -0
  152. package/dist/routes/organization-mcp-connections.d.ts +3 -0
  153. package/dist/routes/organization-model-defaults.d.ts +9 -0
  154. package/dist/routes/organization-service-accounts.d.ts +3 -0
  155. package/dist/routes/personal-api-keys.d.ts +3 -0
  156. package/dist/routes/session-admin-access.d.ts +3 -0
  157. package/dist/routes/session-artifact-associations.d.ts +4 -0
  158. package/dist/routes/session-history-imports.d.ts +20 -0
  159. package/dist/routes/sessions.d.ts +38 -2
  160. package/dist/routes/site-data.d.ts +18 -0
  161. package/dist/routes/site-publishing.d.ts +5 -0
  162. package/dist/routes/site-runtime.d.ts +31 -0
  163. package/dist/routes/site-sql.d.ts +16 -0
  164. package/dist/routes/slack-bot-access.d.ts +3 -0
  165. package/dist/routes/subscription-account-pools.d.ts +21 -0
  166. package/dist/routes/subscription-core-http.d.ts +5 -0
  167. package/dist/routes/subscription-pool-access.d.ts +37 -0
  168. package/dist/routes/subscription-pool-core.d.ts +153 -0
  169. package/dist/routes/supergrok.d.ts +6 -6
  170. package/dist/routes/usage-allowances.d.ts +12 -0
  171. package/dist/routes/variable-sets-removed.d.ts +2 -0
  172. package/dist/routes/vault-providers.d.ts +34 -0
  173. package/dist/routes/vaults.d.ts +3 -0
  174. package/dist/routes/workspace-integrations.d.ts +35 -0
  175. package/dist/routes/workspace-model-providers.d.ts +4 -0
  176. package/dist/routes/workspaces.d.ts +11 -2
  177. package/dist/sandbox/agent-update.d.ts +64 -0
  178. package/dist/sandbox/auth-callout.d.ts +8 -0
  179. package/dist/sandbox/channel-a.d.ts +15 -1
  180. package/dist/sandbox/enrollment.d.ts +26 -4
  181. package/dist/sandbox/machine-audience.d.ts +29 -0
  182. package/dist/sandbox/machines.d.ts +15 -2
  183. package/dist/sandbox/metrics-ingestion.d.ts +6 -2
  184. package/dist/sandbox/screen-control.d.ts +85 -0
  185. package/dist/sandbox/viewer.d.ts +5 -1
  186. package/dist/sandbox-file-artifacts.d.ts +17 -1
  187. package/dist/scheduled-task-blockers.d.ts +39 -0
  188. package/dist/scheduled-task-deletion.d.ts +3 -4
  189. package/dist/scheduled-task-lifecycle.d.ts +14 -0
  190. package/dist/secret-answers.d.ts +40 -0
  191. package/dist/session-create-observability.d.ts +5 -0
  192. package/dist/session-event-view-36GO3IL7.js +528 -0
  193. package/dist/session-event-view-36GO3IL7.js.map +1 -0
  194. package/dist/signed-state-secret.d.ts +18 -0
  195. package/dist/site-agent-guard.d.ts +53 -0
  196. package/dist/site-data/operations.d.ts +103 -0
  197. package/dist/site-data/service.d.ts +203 -0
  198. package/dist/site-frame-guard.d.ts +32 -0
  199. package/dist/site-publishing/agent-data-tools.d.ts +84 -0
  200. package/dist/site-publishing/agent-model.d.ts +58 -0
  201. package/dist/site-publishing/agent-session.d.ts +16 -0
  202. package/dist/site-publishing/content-scanner.d.ts +31 -0
  203. package/dist/site-publishing/edge-sync.d.ts +48 -0
  204. package/dist/site-publishing/gateway-signature.d.ts +56 -0
  205. package/dist/site-publishing/limits.d.ts +63 -0
  206. package/dist/site-publishing/page-tool-authority.d.ts +30 -0
  207. package/dist/site-publishing/platform-usage.d.ts +113 -0
  208. package/dist/site-publishing/platform.d.ts +21 -0
  209. package/dist/site-publishing/readiness.d.ts +62 -0
  210. package/dist/site-publishing/runtime.d.ts +179 -0
  211. package/dist/site-publishing/service.d.ts +264 -0
  212. package/dist/site-publishing/session-events.d.ts +22 -0
  213. package/dist/site-publishing/session-views.d.ts +36 -0
  214. package/dist/site-publishing/viewer.d.ts +40 -0
  215. package/dist/site-session-origin.d.ts +31 -6
  216. package/dist/site-sql/format.d.ts +47 -0
  217. package/dist/site-sql/gateway.d.ts +50 -0
  218. package/dist/site-sql/service.d.ts +213 -0
  219. package/dist/temporal-schedule-sync.d.ts +26 -0
  220. package/dist/transcription/billing-refusal.d.ts +8 -0
  221. package/dist/transcription/normalize.d.ts +23 -0
  222. package/dist/transcription/providers/azure-mai.d.ts +12 -0
  223. package/dist/transcription/providers/azure-openai.d.ts +4 -0
  224. package/dist/transcription/providers/openai.d.ts +16 -0
  225. package/dist/transcription/providers/subscription-core.d.ts +39 -0
  226. package/dist/transcription/service.d.ts +10 -1
  227. package/dist/transcription/wav-duration.d.ts +8 -0
  228. package/dist/workspace-tool-gateway-attestations.d.ts +37 -0
  229. package/dist/workspace-tool-gateway-observability.d.ts +16 -0
  230. package/dist/workspace-tool-gateway-secrets.d.ts +27 -0
  231. package/dist/workspace-tool-gateway.d.ts +102 -6
  232. package/dist/workspace-tool-target-fence.d.ts +116 -0
  233. package/dist/workspace-tool-target.d.ts +225 -0
  234. package/dist/workspace-webhook-dispatch.d.ts +26 -0
  235. package/dist/xai-subscription-core.d.ts +60 -0
  236. package/package.json +25 -20
  237. package/src/access-grant-rls.ts +64 -14
  238. package/src/admission-trace.ts +14 -0
  239. package/src/app.ts +1684 -252
  240. package/src/artifact-catalog-cursor.ts +1 -0
  241. package/src/attached-browser-viewer.ts +26 -0
  242. package/src/auth/managed-auth-attempt-context.ts +9 -0
  243. package/src/auth/managed-auth-rate-limits.ts +252 -0
  244. package/src/auth/managed-auth-session-adapter.ts +64 -29
  245. package/src/auth/managed-auth.ts +303 -35
  246. package/src/auth/managed-email.ts +18 -1
  247. package/src/auth/managed-sign-in-notifications.ts +81 -0
  248. package/src/auth/new-signups-gate.ts +49 -0
  249. package/src/auth/organization-user-setup.ts +3 -3
  250. package/src/auth/signup-funnel-metrics.ts +164 -0
  251. package/src/azure-live.ts +103 -0
  252. package/src/browser-action-fences.ts +45 -0
  253. package/src/browser-controller-authority.ts +1 -186
  254. package/src/browser-ephemeral.ts +39 -0
  255. package/src/browser-lost-runtime-end.ts +71 -0
  256. package/src/browser-state-authority.ts +1 -236
  257. package/src/claude-default-models.ts +37 -0
  258. package/src/claude-subscription-core.ts +154 -0
  259. package/src/claude-subscription-oauth.ts +352 -0
  260. package/src/claude-subscription-profile.ts +73 -0
  261. package/src/claude-subscription-usage.ts +51 -0
  262. package/src/claude-workspace-connection.ts +82 -0
  263. package/src/codemode.ts +236 -39
  264. package/src/codex-realtime.ts +181 -53
  265. package/src/codex-session-pointers.ts +80 -0
  266. package/src/company-brain-okf.ts +1 -1
  267. package/src/connected-machine-computer-access.ts +2 -1
  268. package/src/connection-ownership.ts +19 -13
  269. package/src/controller-data-plane.ts +34 -0
  270. package/src/credit-promotion-snapshot.ts +36 -0
  271. package/src/editable-artifact-production.ts +10 -0
  272. package/src/editable-artifact-source-session.ts +123 -0
  273. package/src/editable-artifact-websocket.ts +16 -3
  274. package/src/fatal-process-boundary.ts +67 -3
  275. package/src/gateway-realtime.ts +12 -8
  276. package/src/github-access.ts +31 -5
  277. package/src/github-browser-flow.ts +1 -1
  278. package/src/http/acting-person.ts +105 -0
  279. package/src/http/api-contract-compat.ts +49 -0
  280. package/src/http/api-error.ts +210 -2
  281. package/src/http/auth.ts +68 -4
  282. package/src/http/cors.ts +37 -5
  283. package/src/http/deprecation.ts +118 -0
  284. package/src/http/keyed-admission.ts +29 -0
  285. package/src/http/local-browser-boundary.ts +343 -0
  286. package/src/http/metrics-listener.ts +57 -0
  287. package/src/http/path-identifier.ts +54 -0
  288. package/src/http/registered-route-label.ts +34 -0
  289. package/src/http/rejection-telemetry.ts +120 -0
  290. package/src/http/request-body.ts +127 -0
  291. package/src/http/request-source.ts +154 -13
  292. package/src/http/sse.ts +88 -28
  293. package/src/http/unmatched-route.ts +60 -0
  294. package/src/http/user-content.ts +158 -0
  295. package/src/http/variable-sets-removed.ts +122 -0
  296. package/src/http/websocket-upgrade-dispatch.ts +29 -0
  297. package/src/index.ts +107 -57
  298. package/src/integration-connect-metrics.ts +228 -0
  299. package/src/integration-endpoint-test.ts +291 -0
  300. package/src/integrations/api-integrations.ts +81 -7
  301. package/src/integrations/atlassian.ts +202 -83
  302. package/src/integrations/fiken.ts +66 -38
  303. package/src/integrations/github-app-connect.ts +64 -18
  304. package/src/integrations/github-lens-connect.ts +8 -29
  305. package/src/integrations/google-drive.ts +448 -152
  306. package/src/integrations/integration-commit-authority.ts +69 -0
  307. package/src/integrations/marketplace-plugin.ts +1 -1
  308. package/src/integrations/oauth-client.ts +733 -193
  309. package/src/integrations/oauth-profiles.ts +116 -45
  310. package/src/integrations/onepassword-vault-provider.ts +16 -0
  311. package/src/integrations/personal-github-repositories.ts +1 -1
  312. package/src/integrations/personal-github.ts +61 -35
  313. package/src/integrations/provider-oauth.ts +67 -85
  314. package/src/integrations/slack-app-home.ts +5 -5
  315. package/src/integrations/slack-bot.ts +893 -201
  316. package/src/integrations/slack-file-upload-flow.ts +147 -0
  317. package/src/integrations/slack-install.ts +7 -7
  318. package/src/integrations/slack-interactions.ts +1589 -299
  319. package/src/integrations/slack-mrkdwn.ts +193 -0
  320. package/src/integrations/slack-routing.ts +2 -2
  321. package/src/integrations/slack-session-defaults.ts +224 -0
  322. package/src/integrations/slack-task-file-upload.ts +323 -0
  323. package/src/integrations/slack-tool-review.ts +65 -0
  324. package/src/integrations/social-oauth.ts +90 -58
  325. package/src/interaction-agent-access.ts +40 -20
  326. package/src/interaction-agent-version.ts +85 -0
  327. package/src/interaction-holder-heartbeat.ts +24 -8
  328. package/src/interaction-metrics.ts +164 -2
  329. package/src/interaction-route-stages.ts +94 -0
  330. package/src/interaction-secrets-vault.ts +213 -0
  331. package/src/interaction-secrets.ts +426 -0
  332. package/src/mcp/action-catalog-types.ts +14 -0
  333. package/src/mcp/action-catalog.gen.ts +8014 -0
  334. package/src/mcp/action-descriptions.ts +299 -0
  335. package/src/mcp/action-schema-imports.gen.ts +304 -0
  336. package/src/mcp/capability-account-readiness.ts +17 -0
  337. package/src/mcp/child-read-evidence.ts +19 -0
  338. package/src/mcp/company-profile-agent-admin.ts +13 -0
  339. package/src/mcp/contract-input.ts +165 -0
  340. package/src/mcp/documents.ts +9 -2
  341. package/src/mcp/editable-artifacts.ts +29 -11
  342. package/src/mcp/files.ts +5 -1
  343. package/src/mcp/first-party-tool-permissions.ts +7 -285
  344. package/src/mcp/knowledge-entries.ts +108 -34
  345. package/src/mcp/notification-tools.ts +480 -0
  346. package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
  347. package/src/mcp/scheduled-task-input.ts +89 -0
  348. package/src/mcp/scheduled-task-view.ts +63 -3
  349. package/src/mcp/secret-tools.ts +185 -0
  350. package/src/mcp/server.ts +2213 -1116
  351. package/src/mcp/session-admin-access-tools.ts +163 -0
  352. package/src/mcp/session-event-errors.ts +123 -0
  353. package/src/mcp/session-event-view.ts +395 -13
  354. package/src/mcp/session-view.ts +3 -0
  355. package/src/mcp/session-wait.ts +67 -7
  356. package/src/mcp/site-agent-clamp-tools.ts +140 -0
  357. package/src/mcp/site-data-tools.ts +339 -0
  358. package/src/mcp/site-publication-tools.ts +201 -0
  359. package/src/mcp/site-sql-tools.ts +427 -0
  360. package/src/mcp-oauth.ts +635 -45
  361. package/src/model-calls/chat-completions.ts +352 -0
  362. package/src/model-calls/pending-usage-settlements.ts +125 -0
  363. package/src/model-calls/service.ts +694 -0
  364. package/src/model-catalog.ts +68 -17
  365. package/src/native-push-dispatch.ts +457 -0
  366. package/src/observability.ts +15 -1
  367. package/src/organization-mcp.ts +485 -0
  368. package/src/prepared-mcp-actor.ts +154 -0
  369. package/src/prepared-mcp-permissions.ts +38 -0
  370. package/src/routes/analytics-consent.ts +123 -0
  371. package/src/routes/api-integrations.ts +268 -9
  372. package/src/routes/api-keys.ts +164 -25
  373. package/src/routes/artifact-catalog.ts +111 -0
  374. package/src/routes/automations.ts +163 -90
  375. package/src/routes/billing.ts +535 -59
  376. package/src/routes/browser-sessions.ts +966 -342
  377. package/src/routes/canonical-human-identities.ts +61 -0
  378. package/src/routes/capabilities.ts +112 -5
  379. package/src/routes/channels.ts +4 -3
  380. package/src/routes/chat-completions.ts +235 -0
  381. package/src/routes/claude-core-usage.ts +147 -0
  382. package/src/routes/claude-subscription-accounts.ts +233 -0
  383. package/src/routes/claude-subscription-oauth.ts +175 -0
  384. package/src/routes/client-errors.ts +394 -0
  385. package/src/routes/codex-core.ts +687 -0
  386. package/src/routes/codex.ts +1277 -1119
  387. package/src/routes/company-brain.ts +4 -0
  388. package/src/routes/computer-sessions.ts +319 -85
  389. package/src/routes/connect.ts +277 -175
  390. package/src/routes/connections.ts +690 -363
  391. package/src/routes/desktop-distribution.ts +217 -0
  392. package/src/routes/documents.ts +13 -7
  393. package/src/routes/editable-artifacts.ts +74 -4
  394. package/src/routes/enrollments.ts +110 -16
  395. package/src/routes/external-identity-links.ts +13 -12
  396. package/src/routes/files.ts +41 -16
  397. package/src/routes/github-browser-fonts.gen.ts +12 -0
  398. package/src/routes/github-browser-pages.ts +258 -0
  399. package/src/routes/github.ts +200 -75
  400. package/src/routes/inbox.ts +353 -0
  401. package/src/routes/insights-response-cache.ts +83 -0
  402. package/src/routes/insights-usage.ts +345 -0
  403. package/src/routes/insights.ts +103 -8
  404. package/src/routes/install.ts +51 -10
  405. package/src/routes/integration-facets.ts +70 -28
  406. package/src/routes/interaction-placement-message.ts +12 -0
  407. package/src/routes/knowledge.ts +136 -27
  408. package/src/routes/machines.ts +189 -135
  409. package/src/routes/managed-auth-session-sets.ts +43 -10
  410. package/src/routes/managed-onboarding.ts +39 -19
  411. package/src/routes/managed-sign-in-methods.ts +433 -0
  412. package/src/routes/memory-slack-publications.ts +3 -2
  413. package/src/routes/model-account-choice.ts +194 -0
  414. package/src/routes/model-account-settings.ts +235 -0
  415. package/src/routes/model-connection-access.ts +210 -48
  416. package/src/routes/native-app-auth.ts +296 -0
  417. package/src/routes/organization-integration-policy.ts +116 -0
  418. package/src/routes/organization-integrations.ts +326 -0
  419. package/src/routes/organization-mcp-connections.ts +183 -0
  420. package/src/routes/organization-memberships.ts +205 -19
  421. package/src/routes/organization-model-defaults.ts +110 -0
  422. package/src/routes/organization-model-providers.ts +74 -3
  423. package/src/routes/organization-service-accounts.ts +128 -0
  424. package/src/routes/organization-sessions.ts +21 -5
  425. package/src/routes/personal-api-keys.ts +184 -0
  426. package/src/routes/personal-github-git-broker.ts +2 -2
  427. package/src/routes/personal-github.ts +9 -4
  428. package/src/routes/plugins.ts +46 -7
  429. package/src/routes/pr-review-github.ts +105 -118
  430. package/src/routes/pr-review.ts +88 -82
  431. package/src/routes/preference-registry.ts +18 -0
  432. package/src/routes/rigs.ts +48 -13
  433. package/src/routes/scheduled-tasks.ts +347 -27
  434. package/src/routes/session-admin-access.ts +271 -0
  435. package/src/routes/session-artifact-associations.ts +164 -0
  436. package/src/routes/session-history-imports.ts +305 -0
  437. package/src/routes/sessions.ts +1682 -363
  438. package/src/routes/site-data.ts +340 -0
  439. package/src/routes/site-publishing.ts +584 -0
  440. package/src/routes/site-runtime.ts +1944 -0
  441. package/src/routes/site-sql.ts +263 -0
  442. package/src/routes/skill-content.ts +72 -7
  443. package/src/routes/skills.ts +5 -4
  444. package/src/routes/slack-bot-access.ts +108 -0
  445. package/src/routes/social.ts +27 -15
  446. package/src/routes/subscription-account-pools.ts +209 -0
  447. package/src/routes/subscription-core-http.ts +18 -0
  448. package/src/routes/subscription-pool-access.ts +180 -0
  449. package/src/routes/subscription-pool-core.ts +552 -0
  450. package/src/routes/supergrok.ts +174 -655
  451. package/src/routes/transcription-recordings.ts +71 -4
  452. package/src/routes/transcriptions.ts +26 -2
  453. package/src/routes/usage-allowances.ts +381 -0
  454. package/src/routes/variable-sets-removed.ts +19 -0
  455. package/src/routes/vault-providers.ts +782 -0
  456. package/src/routes/vaults.ts +421 -0
  457. package/src/routes/video-generation.ts +30 -12
  458. package/src/routes/workspace-artifacts.ts +165 -53
  459. package/src/routes/workspace-integrations.ts +576 -0
  460. package/src/routes/workspace-model-providers.ts +148 -0
  461. package/src/routes/workspace-state.ts +4 -0
  462. package/src/routes/workspaces.ts +625 -117
  463. package/src/sandbox/agent-update.ts +315 -0
  464. package/src/sandbox/auth-callout.ts +104 -21
  465. package/src/sandbox/channel-a.ts +165 -55
  466. package/src/sandbox/enrollment.ts +131 -22
  467. package/src/sandbox/machine-audience.ts +192 -0
  468. package/src/sandbox/machines.ts +75 -31
  469. package/src/sandbox/metrics-ingestion.ts +133 -2
  470. package/src/sandbox/rematerialize.ts +51 -21
  471. package/src/sandbox/screen-control.ts +289 -0
  472. package/src/sandbox/viewer.ts +33 -58
  473. package/src/sandbox-file-artifacts.ts +53 -27
  474. package/src/scheduled-task-blockers.ts +293 -0
  475. package/src/scheduled-task-deletion.ts +13 -3
  476. package/src/scheduled-task-lifecycle.ts +84 -0
  477. package/src/secret-answers.ts +119 -0
  478. package/src/session-create-observability.ts +46 -0
  479. package/src/session-realtime-context.ts +26 -4
  480. package/src/signed-state-secret.ts +58 -0
  481. package/src/site-agent-guard.ts +261 -0
  482. package/src/site-data/operations.ts +777 -0
  483. package/src/site-data/service.ts +1043 -0
  484. package/src/site-frame-guard.ts +107 -0
  485. package/src/site-publishing/agent-data-tools.ts +250 -0
  486. package/src/site-publishing/agent-model.ts +127 -0
  487. package/src/site-publishing/agent-session.ts +148 -0
  488. package/src/site-publishing/content-scanner.ts +93 -0
  489. package/src/site-publishing/edge-sync.ts +148 -0
  490. package/src/site-publishing/gateway-signature.ts +198 -0
  491. package/src/site-publishing/limits.ts +315 -0
  492. package/src/site-publishing/page-tool-authority.ts +111 -0
  493. package/src/site-publishing/platform-usage.ts +479 -0
  494. package/src/site-publishing/platform.ts +98 -0
  495. package/src/site-publishing/readiness.ts +330 -0
  496. package/src/site-publishing/runtime.ts +831 -0
  497. package/src/site-publishing/service.ts +1282 -0
  498. package/src/site-publishing/session-events.ts +262 -0
  499. package/src/site-publishing/session-views.ts +262 -0
  500. package/src/site-publishing/viewer.ts +75 -0
  501. package/src/site-session-origin.ts +91 -6
  502. package/src/site-sql/format.ts +134 -0
  503. package/src/site-sql/gateway.ts +168 -0
  504. package/src/site-sql/service.ts +1636 -0
  505. package/src/site-uploads.ts +27 -5
  506. package/src/temporal-schedule-sync.ts +74 -0
  507. package/src/transcription/billing-refusal.ts +14 -0
  508. package/src/transcription/normalize.ts +77 -0
  509. package/src/transcription/providers/azure-mai.ts +98 -0
  510. package/src/transcription/providers/azure-openai.ts +16 -7
  511. package/src/transcription/providers/codex-subscription.ts +190 -67
  512. package/src/transcription/providers/openai.ts +79 -5
  513. package/src/transcription/providers/subscription-core.ts +125 -0
  514. package/src/transcription/providers/xai-subscription.ts +81 -83
  515. package/src/transcription/segmenter.ts +7 -1
  516. package/src/transcription/service.ts +239 -10
  517. package/src/transcription/wav-duration.ts +39 -0
  518. package/src/workspace-deletion.ts +25 -1
  519. package/src/workspace-tool-gateway-attestations.ts +133 -0
  520. package/src/workspace-tool-gateway-observability.ts +64 -0
  521. package/src/workspace-tool-gateway-secrets.ts +104 -0
  522. package/src/workspace-tool-gateway.ts +921 -103
  523. package/src/workspace-tool-target-fence.ts +380 -0
  524. package/src/workspace-tool-target.ts +554 -0
  525. package/src/workspace-webhook-dispatch.ts +225 -0
  526. package/src/xai-realtime.ts +85 -81
  527. package/src/xai-subscription-core.ts +172 -0
  528. package/dist/browser-auth-broker.d.ts +0 -20
  529. package/dist/chunk-AJ62T5QN.js.map +0 -1
  530. package/dist/connection-authority-owner.d.ts +0 -59
  531. package/dist/routes/connection-authorities.d.ts +0 -3
  532. package/dist/routes/environments.d.ts +0 -5
  533. package/dist/routes/host-mcp-bindings.d.ts +0 -5
  534. package/dist/routes/host-mcp-resolvers.d.ts +0 -3
  535. package/dist/routes/packs.d.ts +0 -3
  536. package/dist/session-event-view-MU3HMNV3.js +0 -264
  537. package/dist/session-event-view-MU3HMNV3.js.map +0 -1
  538. package/dist/xai-subscription-auth.d.ts +0 -23
  539. package/src/browser-auth-broker.ts +0 -155
  540. package/src/connection-authority-owner.ts +0 -61
  541. package/src/routes/connection-authorities.ts +0 -139
  542. package/src/routes/environments.ts +0 -388
  543. package/src/routes/host-mcp-bindings.ts +0 -122
  544. package/src/routes/host-mcp-resolvers.ts +0 -41
  545. package/src/routes/packs.ts +0 -784
  546. package/src/xai-subscription-auth.ts +0 -139
@@ -0,0 +1,1043 @@
1
+ import { createHash } from "node:crypto";
2
+ import Ajv, { type ValidateFunction } from "ajv";
3
+ import { hasLiteralPermission, hasPermission, siteViewerForGrant } from "@opengeni/core";
4
+ import type { ApiRouteDeps } from "@opengeni/core";
5
+ import type { AccessGrant, SiteDataDescribeResponse } from "@opengeni/contracts";
6
+ import {
7
+ createPostgresSiteDataStore,
8
+ getSiteDataAgentSession,
9
+ getSiteDataSite,
10
+ parseSiteDataPath,
11
+ readSiteDataPath,
12
+ recordSitePlatformUsageInTransaction,
13
+ recordUsageEvent,
14
+ SiteDataError,
15
+ type Database,
16
+ type SiteDataCaller,
17
+ type SiteDataLimits,
18
+ type SiteDataSite,
19
+ type SiteDataStore,
20
+ type SiteDataWriteOptions,
21
+ type SiteDataWriteSummary,
22
+ writeSiteDataPath,
23
+ } from "@opengeni/db";
24
+
25
+ /**
26
+ * Site Data service: resolves who is calling (manager or viewer, and their
27
+ * principal), applies Site state rules, and wires validation and usage
28
+ * metering into the backend-neutral store. Every surface (HTTP, the Site
29
+ * bridge, first-party agent tools) goes through here.
30
+ */
31
+
32
+ export type SiteDataDeps = Pick<ApiRouteDeps, "db" | "settings">;
33
+
34
+ export type SiteDataAccess = {
35
+ site: SiteDataSite;
36
+ caller: SiteDataCaller;
37
+ subjectId: string | null;
38
+ store: SiteDataStore;
39
+ limits: SiteDataLimits;
40
+ writeOptions: SiteDataWriteOptions;
41
+ };
42
+
43
+ const stores = new WeakMap<object, SiteDataStore>();
44
+
45
+ /** One store per database handle. */
46
+ export function siteDataStore(db: Database): SiteDataStore {
47
+ let store = stores.get(db);
48
+ if (!store) {
49
+ store = createPostgresSiteDataStore(db);
50
+ stores.set(db, store);
51
+ }
52
+ return store;
53
+ }
54
+
55
+ export function siteDataLimits(settings: ApiRouteDeps["settings"]): SiteDataLimits {
56
+ return {
57
+ recordBytesPerSite: settings.siteDataRecordBytesPerSite,
58
+ maxRecordBytes: settings.siteDataMaxRecordBytes,
59
+ maxCollectionsPerSite: settings.siteDataMaxCollectionsPerSite,
60
+ fileBytesPerSite: settings.siteDataFileBytesPerSite,
61
+ maxFileBytes: settings.siteDataMaxFileBytes,
62
+ workspaceBytes: settings.siteDataWorkspaceBytes,
63
+ maxRecordsPerCollection: settings.siteDataMaxRecordsPerCollection,
64
+ viewerWritesPerMinute: settings.siteDataViewerWritesPerMinute,
65
+ siteWritesPerMinute: settings.siteDataSiteWritesPerMinute,
66
+ viewerReadsPerMinute: settings.siteDataViewerReadsPerMinute,
67
+ managerWritesPerMinute: settings.siteDataManagerWritesPerMinute,
68
+ siteValidationMsPerMinute: settings.siteDataSiteValidationMsPerMinute,
69
+ workspaceValidationMsPerMinute: settings.siteDataWorkspaceValidationMsPerMinute,
70
+ };
71
+ }
72
+
73
+ export function publicSiteDataLimits(limits: SiteDataLimits): SiteDataDescribeResponse["limits"] {
74
+ return {
75
+ recordBytesPerSite: limits.recordBytesPerSite,
76
+ maxRecordBytes: limits.maxRecordBytes,
77
+ maxCollectionsPerSite: limits.maxCollectionsPerSite,
78
+ fileBytesPerSite: limits.fileBytesPerSite,
79
+ maxFileBytes: limits.maxFileBytes,
80
+ workspaceBytes: limits.workspaceBytes,
81
+ };
82
+ }
83
+
84
+ // JSON Schema validation with the tool gateway's Ajv posture: no coercion,
85
+ // defaults or format assertions. Collection schemas use a strict keyword
86
+ // allowlist (no references, regexes or conditionals), so checking a record
87
+ // costs at most the schema's per-value cost for each value in the record.
88
+ // Every schema is compiled by its own Ajv instance, so nothing registers
89
+ // across tenants, and the compiled validators live in a small LRU keyed by
90
+ // the schema's digest: evicting an entry frees its Ajv.
91
+
92
+ const SCHEMA_CACHE_SIZE = 128;
93
+ /** Most schema nodes (objects or booleans) a collection schema may have. */
94
+ export const SITE_DATA_MAX_SCHEMA_NODES = 2_000;
95
+ /** Most properties one object schema may name (CSV imports allow 1,000 columns). */
96
+ export const SITE_DATA_MAX_SCHEMA_PROPERTIES = 1_000;
97
+ /** Most branches one allOf, anyOf or oneOf may have. */
98
+ export const SITE_DATA_MAX_SCHEMA_BRANCHES = 16;
99
+ /** Most enum values a whole collection schema may list. */
100
+ export const SITE_DATA_MAX_SCHEMA_ENUM_TOTAL = 500;
101
+ /** Deepest schema nesting a collection schema may have. */
102
+ export const SITE_DATA_MAX_SCHEMA_DEPTH = 16;
103
+ /** Most values an `enum` may list. */
104
+ export const SITE_DATA_MAX_SCHEMA_ENUM = 100;
105
+ /**
106
+ * Most checks a schema may run on each repeated value (an array element or an
107
+ * additional property): see siteDataSchemaRepeatedCost. A record can repeat a
108
+ * value up to about 128K times, so this bounds the cost of checking it.
109
+ */
110
+ export const SITE_DATA_MAX_SCHEMA_REPEATED_COST = 600;
111
+ /**
112
+ * Validators with more generated code than this run unoptimized, about four
113
+ * times slower (measured), so their repeated cost counts four times.
114
+ */
115
+ export const SITE_DATA_LARGE_VALIDATOR_SOURCE = 200_000;
116
+ const LARGE_VALIDATOR_FACTOR = 4;
117
+ /** The repeated cost of one allOf, anyOf or oneOf branch, before its own checks. */
118
+ const SCHEMA_BRANCH_COST = 6;
119
+ /** Validators by schema digest; a schema that failed to compile keeps its error. */
120
+ const compiled = new Map<string, ValidateFunction | SiteDataError>();
121
+ /** Per-object shortcut so a batch hashes each loaded schema once. */
122
+ const compiledByObject = new WeakMap<object, ValidateFunction>();
123
+
124
+ const SCHEMA_TYPES = new Set(["string", "number", "integer", "boolean", "null", "object", "array"]);
125
+ const SCALAR_TYPES = new Set(["string", "number", "integer", "boolean", "null"]);
126
+ const NUMBER_KEYWORDS = new Set(["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum"]);
127
+ const COUNT_KEYWORDS = new Set([
128
+ "minLength",
129
+ "maxLength",
130
+ "minItems",
131
+ "maxItems",
132
+ "minProperties",
133
+ "maxProperties",
134
+ ]);
135
+ const TEXT_KEYWORDS = new Set(["format", "title", "description"]);
136
+ const COMBINATORS = new Set(["allOf", "anyOf", "oneOf"]);
137
+ /** Every keyword a collection schema may use. */
138
+ export const SITE_DATA_SCHEMA_KEYWORDS: readonly string[] = [
139
+ "type",
140
+ "properties",
141
+ "required",
142
+ "additionalProperties",
143
+ "items",
144
+ "enum",
145
+ "const",
146
+ ...NUMBER_KEYWORDS,
147
+ "multipleOf",
148
+ ...COUNT_KEYWORDS,
149
+ ...TEXT_KEYWORDS,
150
+ ...COMBINATORS,
151
+ "uniqueItems",
152
+ ];
153
+ const ALLOWED_KEYWORDS = new Set(SITE_DATA_SCHEMA_KEYWORDS);
154
+
155
+ function isScalar(value: unknown): boolean {
156
+ return value === null || ["string", "number", "boolean"].includes(typeof value);
157
+ }
158
+
159
+ function isRecordObject(value: unknown): value is Record<string, unknown> {
160
+ return typeof value === "object" && value !== null && !Array.isArray(value);
161
+ }
162
+
163
+ /** Whether an `items` schema pins every element to scalar types (uniqueItems stays linear). */
164
+ function scalarItems(items: unknown): boolean {
165
+ if (!isRecordObject(items)) return false;
166
+ const type = items.type;
167
+ const types = typeof type === "string" ? [type] : Array.isArray(type) ? type : [];
168
+ return types.length > 0 && types.every((entry) => SCALAR_TYPES.has(entry as string));
169
+ }
170
+
171
+ /**
172
+ * The most checks a schema runs on one repeated value: an array element or an
173
+ * additional property's value, which a record can hold about 128K of. Each
174
+ * schema object costs 1, plus 1 per enum value, property and required name;
175
+ * an allOf, anyOf or oneOf branch costs 6 (a failed branch is the costliest
176
+ * check, measured). Branches apply to the same value, so their costs add;
177
+ * children apply to different values, so only the costliest counts. Per node:
178
+ *
179
+ * - all(n): the costliest value n's subtree checks, n's own value included;
180
+ * - repeated(n): the costliest value below n that sits under `items` or
181
+ * `additionalProperties`.
182
+ *
183
+ * Both are upper bounds, computed in one pass over the schema.
184
+ */
185
+ export function siteDataSchemaRepeatedCost(schema: unknown): number {
186
+ const visit = (node: unknown, branch = false): { all: number; repeated: number } => {
187
+ const own = branch ? SCHEMA_BRANCH_COST : 1;
188
+ if (!isRecordObject(node)) return { all: own, repeated: 0 };
189
+ let all = own;
190
+ let repeated = 0;
191
+ if (Array.isArray(node.enum)) all += node.enum.length;
192
+ if (Array.isArray(node.required)) all += node.required.length;
193
+ let childAll = 0;
194
+ let childRepeated = 0;
195
+ if (isRecordObject(node.properties))
196
+ for (const child of Object.values(node.properties)) {
197
+ all += 1;
198
+ const cost = visit(child);
199
+ childAll = Math.max(childAll, cost.all);
200
+ childRepeated = Math.max(childRepeated, cost.repeated);
201
+ }
202
+ for (const keyword of ["items", "additionalProperties"])
203
+ if (node[keyword] !== undefined && typeof node[keyword] !== "boolean") {
204
+ const cost = visit(node[keyword]);
205
+ childAll = Math.max(childAll, cost.all);
206
+ childRepeated = Math.max(childRepeated, cost.all);
207
+ }
208
+ for (const keyword of COMBINATORS)
209
+ if (Array.isArray(node[keyword]))
210
+ for (const child of node[keyword] as unknown[]) {
211
+ const cost = visit(child, true);
212
+ all += cost.all;
213
+ repeated += cost.repeated;
214
+ }
215
+ return { all: all + childAll, repeated: repeated + childRepeated };
216
+ };
217
+ return visit(schema).repeated;
218
+ }
219
+
220
+ /**
221
+ * Walk a collection schema against the keyword allowlist, counting nodes,
222
+ * properties, enum values and depth, then bound its cost per repeated value.
223
+ * Returns the first problem, or null when the schema is supported.
224
+ */
225
+ export function siteDataSchemaProblem(schema: unknown): string | null {
226
+ let nodes = 0;
227
+ let enumValues = 0;
228
+ const visit = (node: unknown, path: string, depth: number): string | null => {
229
+ const at = path || "/";
230
+ nodes += 1;
231
+ if (nodes > SITE_DATA_MAX_SCHEMA_NODES)
232
+ return `has more than ${SITE_DATA_MAX_SCHEMA_NODES} schemas`;
233
+ if (depth > SITE_DATA_MAX_SCHEMA_DEPTH)
234
+ return `is nested more than ${SITE_DATA_MAX_SCHEMA_DEPTH} levels deep`;
235
+ if (typeof node === "boolean") return null;
236
+ if (!isRecordObject(node)) return `has a schema at ${at} that is not an object or boolean`;
237
+ for (const [keyword, value] of Object.entries(node)) {
238
+ const where = `${path}/${keyword}`;
239
+ if (!ALLOWED_KEYWORDS.has(keyword))
240
+ return `uses ${keyword} at ${at}, which collection schemas do not support`;
241
+ if (keyword === "type") {
242
+ const types = typeof value === "string" ? [value] : value;
243
+ if (
244
+ !Array.isArray(types) ||
245
+ types.length === 0 ||
246
+ !types.every((type) => SCHEMA_TYPES.has(type as string))
247
+ )
248
+ return `has an invalid type at ${at}`;
249
+ } else if (keyword === "properties") {
250
+ if (!isRecordObject(value)) return `has properties at ${at} that are not an object`;
251
+ if (Object.keys(value).length > SITE_DATA_MAX_SCHEMA_PROPERTIES)
252
+ return `has more than ${SITE_DATA_MAX_SCHEMA_PROPERTIES} properties at ${at}`;
253
+ for (const [name, child] of Object.entries(value)) {
254
+ const problem = visit(child, `${where}/${name}`, depth + 1);
255
+ if (problem) return problem;
256
+ }
257
+ } else if (keyword === "required") {
258
+ if (!Array.isArray(value) || !value.every((name) => typeof name === "string"))
259
+ return `has required at ${at} that is not a list of names`;
260
+ } else if (keyword === "additionalProperties" || keyword === "items") {
261
+ // `items` is a single schema; tuple forms are not supported.
262
+ const problem = visit(value, where, depth + 1);
263
+ if (problem) return problem;
264
+ } else if (keyword === "enum") {
265
+ if (!Array.isArray(value) || value.length === 0 || !value.every(isScalar))
266
+ return `has an enum at ${at} that is not a list of strings, numbers, booleans or null`;
267
+ if (value.length > SITE_DATA_MAX_SCHEMA_ENUM)
268
+ return `has an enum at ${at} with more than ${SITE_DATA_MAX_SCHEMA_ENUM} values`;
269
+ enumValues += value.length;
270
+ if (enumValues > SITE_DATA_MAX_SCHEMA_ENUM_TOTAL)
271
+ return `lists more than ${SITE_DATA_MAX_SCHEMA_ENUM_TOTAL} enum values in total`;
272
+ } else if (keyword === "const") {
273
+ if (!isScalar(value)) return `has a const at ${at} that is not a scalar`;
274
+ } else if (NUMBER_KEYWORDS.has(keyword)) {
275
+ if (typeof value !== "number" || !Number.isFinite(value))
276
+ return `has ${keyword} at ${at} that is not a number`;
277
+ } else if (keyword === "multipleOf") {
278
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0)
279
+ return `has multipleOf at ${at} that is not a positive number`;
280
+ } else if (COUNT_KEYWORDS.has(keyword)) {
281
+ if (!Number.isSafeInteger(value) || (value as number) < 0)
282
+ return `has ${keyword} at ${at} that is not a non-negative integer`;
283
+ } else if (TEXT_KEYWORDS.has(keyword)) {
284
+ if (typeof value !== "string") return `has ${keyword} at ${at} that is not text`;
285
+ } else if (COMBINATORS.has(keyword)) {
286
+ if (!Array.isArray(value) || value.length === 0)
287
+ return `has ${keyword} at ${at} that is not a list of schemas`;
288
+ if (value.length > SITE_DATA_MAX_SCHEMA_BRANCHES)
289
+ return `has ${keyword} at ${at} with more than ${SITE_DATA_MAX_SCHEMA_BRANCHES} schemas`;
290
+ for (const [index, child] of value.entries()) {
291
+ const problem = visit(child, `${where}/${index}`, depth + 1);
292
+ if (problem) return problem;
293
+ }
294
+ } else if (keyword === "uniqueItems") {
295
+ if (typeof value !== "boolean") return `has uniqueItems at ${at} that is not a boolean`;
296
+ if (value && !scalarItems(node.items))
297
+ return `uses uniqueItems at ${at} without scalar items (give items a string, number, integer, boolean or null type)`;
298
+ }
299
+ }
300
+ return null;
301
+ };
302
+ const problem = visit(schema, "", 0);
303
+ if (problem) return problem;
304
+ const cost = siteDataSchemaRepeatedCost(schema);
305
+ return cost > SITE_DATA_MAX_SCHEMA_REPEATED_COST ? repeatedCostProblem(cost) : null;
306
+ }
307
+
308
+ function repeatedCostProblem(cost: number): string {
309
+ return `is too costly to check on repeated values (cost ${cost}, at most ${SITE_DATA_MAX_SCHEMA_REPEATED_COST}): use fewer branches, enum values or properties under items or additionalProperties`;
310
+ }
311
+
312
+ function schemaDigest(schema: Record<string, unknown>): string {
313
+ return createHash("sha256").update(JSON.stringify(schema)).digest("hex");
314
+ }
315
+
316
+ /** Check a schema against the allowlist and cost model, then compile it. */
317
+ function buildSiteDataValidator(schema: Record<string, unknown>): ValidateFunction {
318
+ const problem = siteDataSchemaProblem(schema);
319
+ if (problem) throw new SiteDataError("validation", `jsonSchema ${problem}`);
320
+ let validator: ValidateFunction;
321
+ try {
322
+ validator = new Ajv({
323
+ allErrors: false,
324
+ coerceTypes: false,
325
+ strict: false,
326
+ useDefaults: false,
327
+ validateFormats: false,
328
+ addUsedSchema: false,
329
+ }).compile(schema);
330
+ } catch (error) {
331
+ throw new SiteDataError(
332
+ "validation",
333
+ `jsonSchema is not a valid JSON Schema: ${error instanceof Error ? error.message : "invalid"}`,
334
+ );
335
+ }
336
+ // A validator too large to run optimized has its repeated cost counted four times.
337
+ if (validator.toString().length > SITE_DATA_LARGE_VALIDATOR_SOURCE) {
338
+ const cost = siteDataSchemaRepeatedCost(schema) * LARGE_VALIDATOR_FACTOR;
339
+ if (cost > SITE_DATA_MAX_SCHEMA_REPEATED_COST)
340
+ throw new SiteDataError(
341
+ "validation",
342
+ `jsonSchema is large, so it ${repeatedCostProblem(cost)}`,
343
+ );
344
+ }
345
+ return validator;
346
+ }
347
+
348
+ /**
349
+ * The validator for a schema: from the cache by content digest (checked
350
+ * before anything else, so a hit skips the allowlist walk), or checked and
351
+ * compiled. A schema that fails is cached too, and fails again at once.
352
+ */
353
+ function compileSiteDataSchema(schema: Record<string, unknown>, digest?: string): ValidateFunction {
354
+ const known = compiledByObject.get(schema);
355
+ if (known) return known;
356
+ const key = digest ?? schemaDigest(schema);
357
+ let result = compiled.get(key);
358
+ if (result !== undefined) compiled.delete(key);
359
+ else {
360
+ try {
361
+ result = buildSiteDataValidator(schema);
362
+ } catch (error) {
363
+ if (!(error instanceof SiteDataError)) throw error;
364
+ result = error;
365
+ }
366
+ }
367
+ compiled.set(key, result);
368
+ while (compiled.size > SCHEMA_CACHE_SIZE) compiled.delete(compiled.keys().next().value!);
369
+ if (result instanceof SiteDataError) throw result;
370
+ compiledByObject.set(schema, result);
371
+ return result;
372
+ }
373
+
374
+ /** Compiled validators currently cached (for tests). */
375
+ export function siteDataSchemaCacheSize(): number {
376
+ return compiled.size;
377
+ }
378
+
379
+ function runValidator(validator: ValidateFunction, data: Record<string, unknown>): string | null {
380
+ if (validator(data)) return null;
381
+ const issue = validator.errors?.[0];
382
+ return issue
383
+ ? `${issue.instancePath || "record"} ${issue.message ?? "is invalid"}`
384
+ : "record is invalid";
385
+ }
386
+
387
+ export function validateSiteDataRecord(
388
+ schema: Record<string, unknown>,
389
+ data: Record<string, unknown>,
390
+ ): string | null {
391
+ let validator: ValidateFunction;
392
+ try {
393
+ validator = compileSiteDataSchema(schema);
394
+ } catch {
395
+ return "the collection's JSON Schema is invalid";
396
+ }
397
+ return runValidator(validator, data);
398
+ }
399
+
400
+ /** Check a collection's JSON Schema is supported and compiles before it is stored. */
401
+ export function assertSiteDataSchema(schema: Record<string, unknown> | null | undefined): void {
402
+ if (!schema) return;
403
+ compileSiteDataSchema(schema);
404
+ }
405
+
406
+ /** Total record JSON one write, batch or import request may carry. */
407
+ export const SITE_DATA_MAX_REQUEST_RECORD_BYTES = 4 * 1024 * 1024;
408
+ /**
409
+ * Schema validation time one request may spend, checked before each record
410
+ * after the first, so one record always completes.
411
+ */
412
+ export const SITE_DATA_VALIDATION_BUDGET_MS = 250;
413
+
414
+ /** Validation time every validating request reserves at admission. */
415
+ export const SITE_DATA_VALIDATION_RESERVE_BASE_MS = 25;
416
+ /** Additional reservation per byte of JSON the request validates (1 µs). */
417
+ export const SITE_DATA_VALIDATION_RESERVE_MS_PER_BYTE = 0.001;
418
+ /** How far a write may pass its reservation before records that grew are sent again. */
419
+ export const SITE_DATA_VALIDATION_RESERVE_SLACK_MS = 25;
420
+
421
+ /**
422
+ * The validation time a request reserves at admission: a base plus a little
423
+ * per byte it will validate, capped at the request budget. Small requests
424
+ * reserve little, so many can run at once; an overrun is charged when the
425
+ * request settles.
426
+ */
427
+ export function siteDataValidationReservationMs(bytes: number): number {
428
+ const sized =
429
+ SITE_DATA_VALIDATION_RESERVE_BASE_MS +
430
+ Math.max(0, bytes) * SITE_DATA_VALIDATION_RESERVE_MS_PER_BYTE;
431
+ return Math.min(SITE_DATA_VALIDATION_BUDGET_MS, sized);
432
+ }
433
+
434
+ /**
435
+ * Bytes of JSON in these values, counting only until the reservation would
436
+ * reach the budget (larger requests reserve the whole budget anyway).
437
+ */
438
+ export function siteDataValidationBytes(values: Iterable<unknown>): number {
439
+ const enough =
440
+ (SITE_DATA_VALIDATION_BUDGET_MS - SITE_DATA_VALIDATION_RESERVE_BASE_MS) /
441
+ SITE_DATA_VALIDATION_RESERVE_MS_PER_BYTE;
442
+ let bytes = 0;
443
+ for (const value of values) {
444
+ if (value === undefined) continue;
445
+ bytes += jsonBytes(value);
446
+ if (bytes >= enough) break;
447
+ }
448
+ return bytes;
449
+ }
450
+
451
+ /** The clock validation time is measured with; tests replace `now`. */
452
+ export const siteDataValidationClock = { now: (): number => performance.now() };
453
+
454
+ /** Schema compile and validation time one request spent, in milliseconds. */
455
+ export type SiteDataValidationMeter = { ms: number };
456
+
457
+ const yieldToEventLoop = () => new Promise<void>((resolve) => setImmediate(resolve));
458
+
459
+ function jsonBytes(value: unknown): number {
460
+ return Buffer.byteLength(JSON.stringify(value) ?? "");
461
+ }
462
+
463
+ function recordRef(op: Record<string, unknown>): { id: string } | { key: string } | null {
464
+ if (typeof op.id === "string") return { id: op.id };
465
+ if (typeof op.key === "string") return { key: op.key };
466
+ return null;
467
+ }
468
+
469
+ /**
470
+ * The record a patch or increment would produce from the stored record, the
471
+ * same way the store merges it, or null when the store decides (missing
472
+ * record, bad field, non-number).
473
+ */
474
+ function mergedRecord(
475
+ op: Record<string, unknown>,
476
+ stored: Record<string, unknown> | null,
477
+ ): Record<string, unknown> | null {
478
+ if (op.op === "patch")
479
+ return stored && isRecordObject(op.data) ? { ...stored, ...op.data } : null;
480
+ if (op.op !== "increment" || typeof op.field !== "string") return null;
481
+ const segments = parseSiteDataPath(op.field);
482
+ const by = op.by ?? 1;
483
+ if (!segments || typeof by !== "number" || !Number.isFinite(by)) return null;
484
+ if (!stored) return typeof op.key === "string" ? writeSiteDataPath({}, segments, by) : null;
485
+ const current = readSiteDataPath(stored, segments);
486
+ if (current !== undefined && current !== null && typeof current !== "number") return null;
487
+ return writeSiteDataPath(stored, segments, ((current as number | null) ?? 0) + by);
488
+ }
489
+
490
+ function contentDigest(value: unknown): string {
491
+ return createHash("sha256")
492
+ .update(JSON.stringify(value) ?? "")
493
+ .digest("hex");
494
+ }
495
+
496
+ /**
497
+ * Check a write, batch or import before the store takes the Site's lock:
498
+ *
499
+ * - its records total at most 4 MB of JSON (`quota_exceeded`, `request_bytes`);
500
+ * - every insert and put record matches its collection's schema;
501
+ * - every patch and increment result, merged from the stored record, matches
502
+ * too (if the record changes before the lock, the write checks it again);
503
+ * - schema compiling and validation spend at most 250 ms per request: once
504
+ * anything was compiled or checked, each later operation is refused before
505
+ * it runs when the budget is spent, and the event loop gets a turn after
506
+ * every compile or check, even for a skipped record.
507
+ *
508
+ * Every millisecond spent, here and inside the write, is added to `meter`
509
+ * for the caller to settle. Returns write options that skip re-validating
510
+ * the records already checked and keep any check left for inside the write on
511
+ * the same budget. Inside the write only this request's compiled validators
512
+ * are used: a schema that changed since is a retryable `conflict`
513
+ * (reason `schema_changed`), never a compile under the Site's lock.
514
+ *
515
+ * With `reservedMs` (what the request reserved at admission), every patch
516
+ * or increment result it checks is counted at 1 µs per byte of JSON, and one
517
+ * is refused before it is checked when the request's total would pass its
518
+ * reservation by more than 25 ms. That happens when a record grew after
519
+ * admission (a large write landing first): a retryable `conflict` (reason
520
+ * `record_grew`), so the retry reserves for its real size. A re-check inside
521
+ * the write first uses what the check before the lock already counted, so
522
+ * only real growth counts. Inserts, puts and imports carry their data at
523
+ * admission and stay on the time budget, as does a reservation already at
524
+ * the budget (a retry could not reserve more).
525
+ */
526
+ export async function prevalidateSiteDataWrite(
527
+ access: Pick<SiteDataAccess, "store" | "caller" | "limits" | "writeOptions">,
528
+ operations: readonly unknown[],
529
+ options: { budgetMs?: number; meter?: SiteDataValidationMeter; reservedMs?: number } = {},
530
+ ): Promise<SiteDataWriteOptions> {
531
+ const budgetMs = options.budgetMs ?? SITE_DATA_VALIDATION_BUDGET_MS;
532
+ const meter = options.meter ?? { ms: 0 };
533
+ // The merged records this request has checked, at the reservation's 1 µs
534
+ // per byte. Only a reservation below the budget is enforced: a retry could
535
+ // reserve more.
536
+ const reservedMs =
537
+ options.reservedMs !== undefined && options.reservedMs < budgetMs
538
+ ? options.reservedMs
539
+ : undefined;
540
+ let checkedMs = 0;
541
+ // What the checks before the lock counted, which re-checks inside the
542
+ // write use first: checking the same record again is not growth.
543
+ let creditMs = 0;
544
+ const reserveForMerge = (data: Record<string, unknown>, recheck: boolean) => {
545
+ const needMs = jsonBytes(data) * SITE_DATA_VALIDATION_RESERVE_MS_PER_BYTE;
546
+ const fromCredit = recheck ? Math.min(creditMs, needMs) : 0;
547
+ const addMs = needMs - fromCredit;
548
+ if (
549
+ reservedMs !== undefined &&
550
+ checkedMs + addMs > reservedMs + SITE_DATA_VALIDATION_RESERVE_SLACK_MS
551
+ )
552
+ throw new SiteDataError(
553
+ "conflict",
554
+ "A record grew after this request reserved time to check it against the schema; send it again",
555
+ { reason: "record_grew", retryable: true },
556
+ );
557
+ creditMs = recheck ? creditMs - fromCredit : creditMs + needMs;
558
+ checkedMs += addMs;
559
+ };
560
+ let requestBytes = 0;
561
+ for (const op of operations)
562
+ if (isRecordObject(op) && op.data !== undefined) requestBytes += jsonBytes(op.data);
563
+ if (requestBytes > SITE_DATA_MAX_REQUEST_RECORD_BYTES)
564
+ throw new SiteDataError(
565
+ "quota_exceeded",
566
+ `One request can carry at most ${SITE_DATA_MAX_REQUEST_RECORD_BYTES / 1024 / 1024} MB of records; send them in smaller requests`,
567
+ { quota: "request_bytes", limit: SITE_DATA_MAX_REQUEST_RECORD_BYTES },
568
+ );
569
+ let spentMs = 0;
570
+ // Compiles and validations measured so far.
571
+ let measured = 0;
572
+ const overBudget = (index?: number) =>
573
+ new SiteDataError(
574
+ "validation",
575
+ "Checking these records against the collection schema took too long; send fewer or smaller records per request",
576
+ { reason: "validation_budget", budgetMs, ...(index === undefined ? {} : { index }) },
577
+ );
578
+ const timed = <T>(run: () => T): T => {
579
+ const started = siteDataValidationClock.now();
580
+ measured += 1;
581
+ try {
582
+ return run();
583
+ } finally {
584
+ const elapsed = Math.max(0, siteDataValidationClock.now() - started);
585
+ spentMs += elapsed;
586
+ meter.ms += elapsed;
587
+ }
588
+ };
589
+ // This request's validators (or compile errors) by schema digest. The write
590
+ // reuses them even if the shared cache evicted them meanwhile, and never
591
+ // compiles under the Site's lock. Compiling counts as spent time.
592
+ const validators = new Map<string, ValidateFunction | SiteDataError>();
593
+ const digests = new WeakMap<object, string>();
594
+ const digestOf = (schema: Record<string, unknown>) => {
595
+ let digest = digests.get(schema);
596
+ if (digest === undefined) {
597
+ digest = schemaDigest(schema);
598
+ digests.set(schema, digest);
599
+ }
600
+ return digest;
601
+ };
602
+ const validatorFor = (schema: Record<string, unknown>): ValidateFunction => {
603
+ const digest = digestOf(schema);
604
+ let validator = validators.get(digest);
605
+ if (validator === undefined) {
606
+ validator = timed(() => {
607
+ try {
608
+ return compileSiteDataSchema(schema, digest);
609
+ } catch (error) {
610
+ if (error instanceof SiteDataError) return error;
611
+ throw error;
612
+ }
613
+ });
614
+ validators.set(digest, validator);
615
+ }
616
+ if (validator instanceof SiteDataError) throw validator;
617
+ return validator;
618
+ };
619
+ const check = (validator: ValidateFunction, data: Record<string, unknown>) =>
620
+ timed(() => runValidator(validator, data));
621
+ const schemas = new Map<string, Record<string, unknown> | null>();
622
+ const validated = new WeakMap<object, ValidateFunction>();
623
+ const validatedMerges = new Map<string, ValidateFunction>();
624
+ const prevalidate = async (index: number, op: Record<string, unknown>) => {
625
+ const collection = op.collection as string;
626
+ if (!schemas.has(collection)) {
627
+ const loaded = await access.store.getCollection(access.caller, collection);
628
+ schemas.set(collection, loaded?.jsonSchema ?? null);
629
+ }
630
+ const schema = schemas.get(collection);
631
+ if (!schema) return;
632
+ // Once anything was compiled or checked, every later operation waits for
633
+ // budget: one operation always completes.
634
+ if (measured > 0 && spentMs > budgetMs) throw overBudget(index);
635
+ let validator: ValidateFunction;
636
+ try {
637
+ validator = validatorFor(schema);
638
+ } catch {
639
+ return; // The write reports the invalid schema.
640
+ }
641
+ if (op.op === "insert" || op.op === "put") {
642
+ if (!isRecordObject(op.data)) return;
643
+ // Oversized records are refused by the store's size check instead.
644
+ if (jsonBytes(op.data) > access.limits.maxRecordBytes) return;
645
+ const problem = check(validator, op.data);
646
+ if (problem)
647
+ throw new SiteDataError(
648
+ "validation",
649
+ `Record does not match the collection schema: ${problem}`,
650
+ { index },
651
+ );
652
+ validated.set(op.data, validator);
653
+ } else {
654
+ const ref = recordRef(op);
655
+ if (!ref) return;
656
+ const stored = await access.store.get(access.caller, collection, ref).catch(() => null);
657
+ const merged = mergedRecord(op, stored ? stored.data : null);
658
+ if (!merged || jsonBytes(merged) > access.limits.maxRecordBytes) return;
659
+ // A record that grew since admission is sent again, not checked here.
660
+ reserveForMerge(merged, false);
661
+ // An invalid result is left to the write, which sees the current record.
662
+ if (check(validator, merged) === null) validatedMerges.set(contentDigest(merged), validator);
663
+ }
664
+ };
665
+ for (const [index, op] of operations.entries()) {
666
+ if (!isRecordObject(op) || typeof op.collection !== "string") continue;
667
+ if (op.op !== "insert" && op.op !== "put" && op.op !== "patch" && op.op !== "increment")
668
+ continue;
669
+ const before = measured;
670
+ await prevalidate(index, op);
671
+ // Yield after any compile or check, even when the record was skipped.
672
+ if (measured > before) await yieldToEventLoop();
673
+ }
674
+ return {
675
+ ...access.writeOptions,
676
+ validate: (schema, data) => {
677
+ const known = validators.get(digestOf(schema));
678
+ // The schema changed after the request was checked: never compile under
679
+ // the Site's lock; the caller retries against the new schema.
680
+ if (known === undefined)
681
+ throw new SiteDataError(
682
+ "conflict",
683
+ "The collection's schema changed while this request was being checked; send it again",
684
+ { reason: "schema_changed", retryable: true },
685
+ );
686
+ if (known instanceof SiteDataError) return "the collection's JSON Schema is invalid";
687
+ if (validated.get(data) === known) return null;
688
+ if (validatedMerges.size > 0 && validatedMerges.get(contentDigest(data)) === known)
689
+ return null;
690
+ // Only patch and increment results get here (inserts and puts were
691
+ // checked before the lock). One must fit what the request reserved,
692
+ // with some slack: a merge into a record that grew after admission is
693
+ // sent again, reserving for its real size, instead of being checked
694
+ // under the Site's lock.
695
+ reserveForMerge(data, true);
696
+ // Anything left for inside the write stays on the request's budget; a
697
+ // single-operation request still completes its one record.
698
+ if (operations.length > 1 && measured > 0 && spentMs > budgetMs) throw overBudget();
699
+ return check(known, data);
700
+ },
701
+ };
702
+ }
703
+
704
+ /** The worker-signed session an agent grant acts in, if any. */
705
+ function agentSessionIds(grant: AccessGrant): { sessionId: string; turnId: string | null } | null {
706
+ if (grant.principalKind !== "agent_attempt" && grant.metadata?.delegated !== true) return null;
707
+ const sessionId = grant.metadata?.sessionId;
708
+ if (typeof sessionId !== "string" || sessionId.length === 0) return null;
709
+ const turnId = grant.metadata?.turnId;
710
+ return { sessionId, turnId: typeof turnId === "string" && turnId.length > 0 ? turnId : null };
711
+ }
712
+
713
+ /** A stable principal for an agent session: its own, distinct from any person. */
714
+ function agentPrincipal(grant: AccessGrant): string {
715
+ const sessionId = grant.metadata?.sessionId;
716
+ return typeof sessionId === "string" && sessionId.length > 0
717
+ ? `agent:session:${sessionId}`
718
+ : `agent:${grant.subjectId}`;
719
+ }
720
+
721
+ /**
722
+ * The record owner: the person, or the person an agent or schedule acts for.
723
+ * A person-less agent owns records as its scheduled task when a schedule's
724
+ * run created its session (`schedule:<taskId>`, stable across runs), and as
725
+ * its own session otherwise (`agent:session:<sessionId>`).
726
+ */
727
+ export function siteDataPrincipal(
728
+ grant: AccessGrant,
729
+ subjectId: string | null,
730
+ scheduledTaskId: string | null = null,
731
+ ): string {
732
+ if (subjectId) return `user:${subjectId}`;
733
+ if (grant.principalKind === "agent_attempt" || agentSessionIds(grant)) {
734
+ return scheduledTaskId ? `schedule:${scheduledTaskId}` : agentPrincipal(grant);
735
+ }
736
+ return `service:${grant.subjectId}`;
737
+ }
738
+
739
+ /** The writer recorded on records: agents are distinct from the person they act for. */
740
+ export function siteDataActor(grant: AccessGrant, subjectId: string | null): string {
741
+ if (grant.principalKind === "agent_attempt") return agentPrincipal(grant);
742
+ return siteDataPrincipal(grant, subjectId);
743
+ }
744
+
745
+ /** Personless credentials that may manage data with publish permission. */
746
+ const PERSONLESS_MANAGER_KINDS = new Set(["api_key", "configured_key", "service"]);
747
+
748
+ /**
749
+ * Managers may manage the Site's data, each with `artifacts:publish`: its
750
+ * owner, a workspace admin, an agent acting for one of those people, or a
751
+ * person-less workspace API key, configured key or service credential. A Site
752
+ * without an owner is managed by its creator. An agent without a person (for
753
+ * example a person-less scheduled task) is a viewer, whatever credential
754
+ * started it. Agents whose session came from a Site page are clamped to that
755
+ * Site's viewer rules in resolveSiteDataAccess.
756
+ */
757
+ export function siteDataRole(
758
+ grant: AccessGrant,
759
+ site: Pick<SiteDataSite, "ownerSubjectId" | "createdBySubjectId">,
760
+ subjectId: string | null,
761
+ ): "manager" | "viewer" {
762
+ if (!hasPermission(grant.permissions, "artifacts:publish", grant.permissionMode)) return "viewer";
763
+ if (hasLiteralPermission(grant.permissions, "workspace:admin")) return "manager";
764
+ if (subjectId === null)
765
+ return grant.principalKind !== undefined && PERSONLESS_MANAGER_KINDS.has(grant.principalKind)
766
+ ? "manager"
767
+ : "viewer";
768
+ const owner = site.ownerSubjectId ?? site.createdBySubjectId ?? null;
769
+ return owner !== null && owner === subjectId ? "manager" : "viewer";
770
+ }
771
+
772
+ export type SiteDataSurface = {
773
+ /** The pinned Site id a host attached to a Site-originated request. */
774
+ frameSiteId?: string | null;
775
+ /** Management surfaces: describe, define, delete, reset, import, export. */
776
+ management?: boolean;
777
+ };
778
+
779
+ /**
780
+ * A viewer of a published Site, resolved and authorized by site-runtime (the
781
+ * gateway-signed API): a signed-in person (`user:<subject>`) or an anonymous
782
+ * guest (`guest:<visitor>`). Always a framed viewer of exactly this Site;
783
+ * guests reach only what each collection's guest rule allows.
784
+ */
785
+ export type PublishedSiteDataViewer = {
786
+ kind: "published_viewer";
787
+ accountId: string;
788
+ workspaceId: string;
789
+ site: SiteDataSite;
790
+ principal: string;
791
+ guest: boolean;
792
+ };
793
+
794
+ /** Who is calling Site Data: an access grant, or a published Site viewer. */
795
+ export type SiteDataAuthority = AccessGrant | PublishedSiteDataViewer;
796
+
797
+ function isPublishedViewer(authority: SiteDataAuthority): authority is PublishedSiteDataViewer {
798
+ return (authority as { kind?: unknown }).kind === "published_viewer";
799
+ }
800
+
801
+ function publishedViewerAccess(
802
+ deps: SiteDataDeps,
803
+ viewer: PublishedSiteDataViewer,
804
+ siteId: string,
805
+ surface: SiteDataSurface,
806
+ ): SiteDataAccess {
807
+ if (viewer.site.id !== siteId)
808
+ throw new SiteDataError("forbidden", "A Site can only use its own data");
809
+ if (surface.management)
810
+ throw new SiteDataError("forbidden", "Only the Site's managers can manage its data");
811
+ if (viewer.site.status !== "active")
812
+ throw new SiteDataError("forbidden", "This Site is archived");
813
+ const caller: SiteDataCaller = {
814
+ accountId: viewer.accountId,
815
+ workspaceId: viewer.workspaceId,
816
+ siteId,
817
+ role: "viewer",
818
+ principal: viewer.principal,
819
+ actor: viewer.principal,
820
+ framed: true,
821
+ ...(viewer.guest ? { guest: true } : {}),
822
+ };
823
+ const subjectId = viewer.principal.startsWith("user:")
824
+ ? viewer.principal.slice("user:".length)
825
+ : null;
826
+ const limits = siteDataLimits(deps.settings);
827
+ return {
828
+ site: viewer.site,
829
+ caller,
830
+ subjectId,
831
+ store: siteDataStore(deps.db),
832
+ limits,
833
+ writeOptions: {
834
+ limits,
835
+ validate: validateSiteDataRecord,
836
+ afterWrite: (tx, summary) => meterSiteData(tx, viewer, caller, subjectId, "write", summary),
837
+ },
838
+ };
839
+ }
840
+
841
+ /**
842
+ * Resolve the Site and caller. not_found when the caller cannot see the Site
843
+ * (by audience); forbidden for an archived Site (viewers and Site frames) and
844
+ * for management by anyone but a manager outside the Site frame.
845
+ */
846
+ export async function resolveSiteDataAccess(
847
+ deps: SiteDataDeps,
848
+ authority: SiteDataAuthority,
849
+ siteId: string,
850
+ surface: SiteDataSurface = {},
851
+ ): Promise<SiteDataAccess> {
852
+ if (isPublishedViewer(authority)) return publishedViewerAccess(deps, authority, siteId, surface);
853
+ const grant = authority;
854
+ const scope = { accountId: grant.accountId, workspaceId: grant.workspaceId };
855
+ // An agent whose session (or spawning lineage, root session, or current
856
+ // turn) came from a Site page acts for that page: it gets the same frame
857
+ // clamp as the page. A lineage that cannot be read in full fails closed:
858
+ // viewer reach on the one Site it names, or no Site Data at all.
859
+ const agentIds = agentSessionIds(grant);
860
+ const agentSession = agentIds
861
+ ? await getSiteDataAgentSession(deps.db, scope, agentIds.sessionId, agentIds.turnId)
862
+ : null;
863
+ const siteAgent =
864
+ agentSession !== null &&
865
+ (agentSession.originSiteIds.length > 0 || agentSession.siteTurn || !agentSession.complete);
866
+ if (siteAgent) {
867
+ const [origin, ...others] = agentSession.originSiteIds;
868
+ const knownSite = origin !== undefined && others.length === 0;
869
+ // A known Site origin limits the agent to that Site. Without exactly one
870
+ // known origin (a Site turn, or a lineage that could not be read in full)
871
+ // the agent reaches no Site Data.
872
+ if (!knownSite || origin !== siteId)
873
+ throw new SiteDataError(
874
+ "forbidden",
875
+ agentSession.complete || knownSite
876
+ ? "An agent started from a Site page can only use that Site's data"
877
+ : "This agent's session history could not be confirmed, so it cannot use Site Data",
878
+ );
879
+ if (surface.management)
880
+ throw new SiteDataError(
881
+ "forbidden",
882
+ agentSession.complete
883
+ ? "An agent started from a Site page cannot manage Site Data"
884
+ : "This agent's session history could not be confirmed, so it cannot manage Site Data",
885
+ );
886
+ }
887
+ const framed = siteAgent || (surface.frameSiteId !== undefined && surface.frameSiteId !== null);
888
+ if (surface.frameSiteId != null && surface.frameSiteId !== siteId)
889
+ throw new SiteDataError("forbidden", "A Site can only use its own data");
890
+ const viewer = await siteViewerForGrant(deps.db, grant);
891
+ const site = await getSiteDataSite(deps.db, scope, viewer, siteId);
892
+ if (!site) throw new SiteDataError("not_found", "Site not found");
893
+ // Site HTML is untrusted: whoever views it, a Site frame gets viewer reach.
894
+ // Managers keep full reach only through the Data view, the API and agent tools.
895
+ const manages = siteDataRole(grant, site, viewer.subjectId) === "manager";
896
+ const role = framed ? "viewer" : manages ? "manager" : "viewer";
897
+ if (site.status !== "active" && (framed || role !== "manager"))
898
+ throw new SiteDataError("forbidden", "This Site is archived");
899
+ if (surface.management && (framed || role !== "manager"))
900
+ throw new SiteDataError("forbidden", "Only the Site's managers can manage its data");
901
+ const caller: SiteDataCaller = {
902
+ accountId: grant.accountId,
903
+ workspaceId: grant.workspaceId,
904
+ siteId,
905
+ role,
906
+ principal: siteDataPrincipal(grant, viewer.subjectId, agentSession?.scheduledTaskId ?? null),
907
+ actor: siteDataActor(grant, viewer.subjectId),
908
+ framed,
909
+ };
910
+ const limits = siteDataLimits(deps.settings);
911
+ return {
912
+ site,
913
+ caller,
914
+ subjectId: viewer.subjectId,
915
+ store: siteDataStore(deps.db),
916
+ limits,
917
+ writeOptions: {
918
+ limits,
919
+ validate: validateSiteDataRecord,
920
+ afterWrite: (tx, summary) =>
921
+ meterSiteData(tx, grant, caller, viewer.subjectId, "write", summary),
922
+ },
923
+ };
924
+ }
925
+
926
+ /**
927
+ * Usage metering in `usage_only` mode (docs/credit-boundaries-rollout.md): the
928
+ * workspace pays for viewer-triggered usage; events are recorded in the write
929
+ * transaction and never debit credits until a reviewed rate is enabled. The
930
+ * workspace's Site storage level (Site SQL plus Site Data) is refreshed in
931
+ * its platform usage in the same transaction; any charge for it settles with
932
+ * the next platform usage the API meters (a Site SQL call or a gateway
933
+ * request report).
934
+ */
935
+ export async function meterSiteData(
936
+ tx: Database,
937
+ grant: Pick<AccessGrant, "accountId" | "workspaceId">,
938
+ caller: SiteDataCaller,
939
+ subjectId: string | null,
940
+ kind: "write" | "upload",
941
+ summary: SiteDataWriteSummary,
942
+ ): Promise<void> {
943
+ const usageId = crypto.randomUUID();
944
+ const common = {
945
+ accountId: grant.accountId,
946
+ workspaceId: grant.workspaceId,
947
+ subjectId,
948
+ sourceResourceType: "site",
949
+ sourceResourceId: caller.siteId,
950
+ };
951
+ await recordSitePlatformUsageInTransaction(
952
+ tx,
953
+ { accountId: grant.accountId, workspaceId: grant.workspaceId },
954
+ { refreshStorage: true },
955
+ );
956
+ if (kind === "upload") {
957
+ await recordUsageEvent(tx, {
958
+ ...common,
959
+ eventType: "site_data.file_upload_bytes",
960
+ quantity: summary.bytesWritten,
961
+ unit: "byte",
962
+ idempotencyKey: `site_data.file_upload_bytes:${usageId}`,
963
+ });
964
+ return;
965
+ }
966
+ await recordUsageEvent(tx, {
967
+ ...common,
968
+ eventType: "site_data.write",
969
+ quantity: summary.operations,
970
+ unit: "operation",
971
+ idempotencyKey: `site_data.write:${usageId}`,
972
+ });
973
+ if (summary.bytesWritten > 0)
974
+ await recordUsageEvent(tx, {
975
+ ...common,
976
+ eventType: "site_data.write_bytes",
977
+ quantity: summary.bytesWritten,
978
+ unit: "byte",
979
+ idempotencyKey: `site_data.write_bytes:${usageId}`,
980
+ });
981
+ }
982
+
983
+ // --- files -----------------------------------------------------------------
984
+
985
+ const FILE_TYPES: RegExp[] = [
986
+ /^image\/(png|jpeg|gif|webp|avif)$/u,
987
+ /^application\/pdf$/u,
988
+ /^text\/(plain|csv|markdown)$/u,
989
+ /^application\/json$/u,
990
+ /^audio\/(mpeg|mp4|ogg|wav|webm|aac)$/u,
991
+ /^video\/(mp4|webm|ogg|quicktime)$/u,
992
+ /^application\/zip$/u,
993
+ /^application\/vnd\.openxmlformats-officedocument\.(wordprocessingml\.document|spreadsheetml\.sheet|presentationml\.presentation)$/u,
994
+ /^application\/octet-stream$/u,
995
+ ];
996
+
997
+ const SIGNATURES: Array<[RegExp, (bytes: Uint8Array) => boolean]> = [
998
+ [/^image\/png$/u, (b) => b[0] === 0x89 && b[1] === 0x50 && b[2] === 0x4e && b[3] === 0x47],
999
+ [/^image\/jpeg$/u, (b) => b[0] === 0xff && b[1] === 0xd8 && b[2] === 0xff],
1000
+ [/^image\/gif$/u, (b) => b[0] === 0x47 && b[1] === 0x49 && b[2] === 0x46],
1001
+ [
1002
+ /^image\/webp$/u,
1003
+ (b) => b[0] === 0x52 && b[1] === 0x49 && b[2] === 0x46 && b[3] === 0x46 && b[8] === 0x57,
1004
+ ],
1005
+ [/^application\/pdf$/u, (b) => b[0] === 0x25 && b[1] === 0x50 && b[2] === 0x44 && b[3] === 0x46],
1006
+ ];
1007
+
1008
+ /** Normalize and check a declared upload type; images and PDFs must match their bytes. */
1009
+ export function siteDataFileContentType(
1010
+ declared: string | null | undefined,
1011
+ bytes: Uint8Array,
1012
+ ): string {
1013
+ const essence = (declared || "application/octet-stream").split(";", 1)[0]!.trim().toLowerCase();
1014
+ if (!FILE_TYPES.some((pattern) => pattern.test(essence)))
1015
+ throw new SiteDataError(
1016
+ "validation",
1017
+ "This file type is not supported. Use an image, PDF, text, CSV, JSON, audio, video, Office or zip file.",
1018
+ { contentType: essence },
1019
+ );
1020
+ for (const [pattern, matches] of SIGNATURES)
1021
+ if (pattern.test(essence) && !matches(bytes))
1022
+ throw new SiteDataError("validation", "The file's contents do not match its type", {
1023
+ contentType: essence,
1024
+ });
1025
+ return essence;
1026
+ }
1027
+
1028
+ export function siteDataFileName(name: string | null | undefined): string {
1029
+ const cleaned = (name ?? "")
1030
+ .replace(/[\u0000-\u001f\u007f/\\]/gu, "")
1031
+ .trim()
1032
+ .slice(0, 255);
1033
+ if (!cleaned) throw new SiteDataError("validation", "Give the file a name");
1034
+ return cleaned;
1035
+ }
1036
+
1037
+ export function siteDataObjectKey(workspaceId: string, siteId: string, fileId: string): string {
1038
+ return `workspaces/${workspaceId}/site-data/${siteId}/files/${fileId}`;
1039
+ }
1040
+
1041
+ export function sha256Hex(bytes: Uint8Array): string {
1042
+ return createHash("sha256").update(bytes).digest("hex");
1043
+ }