create-safest-tools 0.2.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 (240) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +71 -0
  3. package/bin/create-safest-tools.mjs +12 -0
  4. package/package.json +34 -0
  5. package/src/cli.mjs +128 -0
  6. package/src/config.mjs +151 -0
  7. package/src/scaffold.mjs +30 -0
  8. package/template/LICENSE +201 -0
  9. package/template/README.md +35 -0
  10. package/template/console/README.md +28 -0
  11. package/template/console/analytics/AnalyticsWorkspace.tsx +55 -0
  12. package/template/console/analytics/controller.ts +133 -0
  13. package/template/console/analytics/events.ts +33 -0
  14. package/template/console/analytics/store.ts +33 -0
  15. package/template/console/analytics/types.ts +38 -0
  16. package/template/console/appeals/AppealWorkspace.tsx +73 -0
  17. package/template/console/appeals/controller.ts +125 -0
  18. package/template/console/appeals/events.ts +33 -0
  19. package/template/console/appeals/store.ts +35 -0
  20. package/template/console/appeals/types.ts +37 -0
  21. package/template/console/assistant/AssistantWorkspace.tsx +133 -0
  22. package/template/console/assistant/types.ts +34 -0
  23. package/template/console/auth/AccountJourney.tsx +69 -0
  24. package/template/console/auth/InvitationAcceptance.tsx +145 -0
  25. package/template/console/auth/OAuthButtons.tsx +78 -0
  26. package/template/console/auth/OperatorLogin.tsx +110 -0
  27. package/template/console/auth/PasswordRecovery.tsx +132 -0
  28. package/template/console/auth/account-route.ts +15 -0
  29. package/template/console/auth/browser-navigation.ts +7 -0
  30. package/template/console/auth/events.ts +15 -0
  31. package/template/console/command/CommandCentre.tsx +119 -0
  32. package/template/console/command/controller.ts +267 -0
  33. package/template/console/command/events.ts +19 -0
  34. package/template/console/command/store.ts +36 -0
  35. package/template/console/command/types.ts +61 -0
  36. package/template/console/components/AnalystIdentity.tsx +25 -0
  37. package/template/console/components/PageHeader.tsx +27 -0
  38. package/template/console/configuration/ConfigurationDialog.tsx +200 -0
  39. package/template/console/configuration/ConfigurationWorkspace.tsx +66 -0
  40. package/template/console/configuration/FormBuilder.tsx +143 -0
  41. package/template/console/configuration/api.ts +40 -0
  42. package/template/console/configuration/events.ts +14 -0
  43. package/template/console/configuration/types.ts +79 -0
  44. package/template/console/lib/format.ts +49 -0
  45. package/template/console/lib/http.ts +96 -0
  46. package/template/console/main.tsx +225 -0
  47. package/template/console/operations/OperationsWorkspace.tsx +126 -0
  48. package/template/console/operations/controller.ts +237 -0
  49. package/template/console/operations/events.ts +37 -0
  50. package/template/console/operations/store.ts +32 -0
  51. package/template/console/operations/types.ts +107 -0
  52. package/template/console/people/PeopleWorkspace.tsx +81 -0
  53. package/template/console/people/types.ts +25 -0
  54. package/template/console/profile/ProfileWorkspace.tsx +136 -0
  55. package/template/console/profile/events.ts +9 -0
  56. package/template/console/profile/types.ts +6 -0
  57. package/template/console/quality/QualityWorkspace.tsx +87 -0
  58. package/template/console/quality/controller.ts +154 -0
  59. package/template/console/quality/events.ts +34 -0
  60. package/template/console/quality/store.ts +36 -0
  61. package/template/console/quality/types.ts +70 -0
  62. package/template/console/queues/QueueEditor.tsx +173 -0
  63. package/template/console/queues/QueueWorkspace.tsx +109 -0
  64. package/template/console/queues/controller.ts +194 -0
  65. package/template/console/queues/events.ts +34 -0
  66. package/template/console/queues/store.ts +37 -0
  67. package/template/console/queues/types.ts +102 -0
  68. package/template/console/registry/RegistryDialog.tsx +198 -0
  69. package/template/console/registry/RegistryWorkspace.tsx +108 -0
  70. package/template/console/registry/events.ts +14 -0
  71. package/template/console/registry/types.ts +52 -0
  72. package/template/console/reports/ReportDrawer.tsx +159 -0
  73. package/template/console/reports/ReportWorkspace.tsx +90 -0
  74. package/template/console/reports/controller.ts +517 -0
  75. package/template/console/reports/events.ts +42 -0
  76. package/template/console/reports/store.ts +39 -0
  77. package/template/console/reports/types.ts +230 -0
  78. package/template/console/settings/BrandEditor.tsx +126 -0
  79. package/template/console/settings/ChannelDialog.tsx +241 -0
  80. package/template/console/settings/SettingsWorkspace.tsx +155 -0
  81. package/template/console/settings/events.ts +19 -0
  82. package/template/console/settings/handoff.ts +22 -0
  83. package/template/console/settings/types.ts +93 -0
  84. package/template/console/shell/WorkspaceShell.tsx +160 -0
  85. package/template/console/shell/controller.ts +182 -0
  86. package/template/console/shell/events.ts +25 -0
  87. package/template/console/shell/navigation.ts +61 -0
  88. package/template/console/shell/store.ts +56 -0
  89. package/template/console/shell/types.ts +86 -0
  90. package/template/console/workflows/CreateWorkflowDialog.tsx +87 -0
  91. package/template/console/workflows/WorkflowCanvas.tsx +42 -0
  92. package/template/console/workflows/WorkflowDialogs.tsx +6 -0
  93. package/template/console/workflows/WorkflowStudio.tsx +331 -0
  94. package/template/console/workflows/WorkflowWorkspace.tsx +82 -0
  95. package/template/console/workflows/events.ts +17 -0
  96. package/template/console/workflows/graph.ts +156 -0
  97. package/template/console/workflows/templates.ts +70 -0
  98. package/template/console/workflows/types.ts +201 -0
  99. package/template/gitignore.template +18 -0
  100. package/template/migrations/0001_reports_foundation.sql +444 -0
  101. package/template/migrations/0002_human_report_loop.sql +65 -0
  102. package/template/migrations/0003_delivery_reliability.sql +18 -0
  103. package/template/migrations/0004_public_intake.sql +14 -0
  104. package/template/migrations/0005_operations_visibility.sql +24 -0
  105. package/template/migrations/0006_ai_governance.sql +198 -0
  106. package/template/migrations/0007_ai_release_gates.sql +6 -0
  107. package/template/migrations/0008_retention_analytics_exports.sql +52 -0
  108. package/template/migrations/0009_retention_derived_copies.sql +14 -0
  109. package/template/migrations/0010_ai_quality_controls.sql +26 -0
  110. package/template/migrations/0011_analyst_presence.sql +28 -0
  111. package/template/migrations/0012_queue_policies.sql +87 -0
  112. package/template/migrations/0013_routing_agents.sql +63 -0
  113. package/template/migrations/0014_webhook_enrichments.sql +122 -0
  114. package/template/migrations/0015_queue_owned_ai.sql +55 -0
  115. package/template/migrations/0016_operator_accounts.sql +67 -0
  116. package/template/migrations/0017_operator_profiles_and_recovery.sql +33 -0
  117. package/template/migrations/0018_platform_configuration.sql +382 -0
  118. package/template/migrations/0019_workflow_authoring_runtime.sql +372 -0
  119. package/template/migrations/0020_tasks_findings_assistant_budgets.sql +427 -0
  120. package/template/migrations/0021_abuse_evidence_operations.sql +324 -0
  121. package/template/migrations/0022_workflow_dispatch_operations.sql +42 -0
  122. package/template/migrations/0023_component_connection_execution.sql +75 -0
  123. package/template/migrations/0024_access_runtime_integrity.sql +72 -0
  124. package/template/migrations/0025_installation_timezone.sql +11 -0
  125. package/template/migrations/0026_ai_and_egress_execution_controls.sql +49 -0
  126. package/template/migrations/0027_prompt_and_ai_registry.sql +37 -0
  127. package/template/migrations/0028_step_attempt_ai_provenance.sql +13 -0
  128. package/template/migrations/0029_evidence_fetch_transport.sql +5 -0
  129. package/template/migrations/0030_evidence_dlq_incidents.sql +45 -0
  130. package/template/migrations/0031_shadow_quality_integrity.sql +7 -0
  131. package/template/migrations/0032_action_delivery_outbox.sql +55 -0
  132. package/template/migrations/0033_configuration_and_assistant_drafts.sql +43 -0
  133. package/template/migrations/0034_installation_integrations.sql +31 -0
  134. package/template/migrations/0035_workspace_governance.sql +21 -0
  135. package/template/migrations/0036_published_routing_baseline.sql +15 -0
  136. package/template/migrations/0037_builtin_phishing_specialist.sql +71 -0
  137. package/template/migrations/0038_remove_deprecated_enrichment_runtime.sql +228 -0
  138. package/template/migrations/0039_secure_reporting_channels.sql +45 -0
  139. package/template/migrations/0040_notification_only_reporting.sql +24 -0
  140. package/template/migrations/0041_better_auth_credentials.sql +17 -0
  141. package/template/package.json +47 -0
  142. package/template/public/_headers +27 -0
  143. package/template/public/app-icon-192.png +0 -0
  144. package/template/public/app-icon-512.png +0 -0
  145. package/template/public/brand-icon.svg +7 -0
  146. package/template/public/brand-tokens.css +80 -0
  147. package/template/public/console/auth-shell.js +19340 -0
  148. package/template/public/customer-brand.js +58 -0
  149. package/template/public/embed/embed.css +139 -0
  150. package/template/public/embed/embed.js +408 -0
  151. package/template/public/embed/index.html +91 -0
  152. package/template/public/favicon.svg +7 -0
  153. package/template/public/fonts/Manrope-Variable.ttf +0 -0
  154. package/template/public/fonts/Newsreader-Italic-Variable.ttf +0 -0
  155. package/template/public/fonts/Newsreader-Variable.ttf +0 -0
  156. package/template/public/index.html +123 -0
  157. package/template/public/logo-primary.svg +7 -0
  158. package/template/public/logo-reversed.svg +7 -0
  159. package/template/public/manifest.webmanifest +21 -0
  160. package/template/public/public-report.js +184 -0
  161. package/template/public/report/index.html +39 -0
  162. package/template/public/report/public-report.css +20 -0
  163. package/template/public/social-card.png +0 -0
  164. package/template/public/styles.css +1521 -0
  165. package/template/public/widget.css +80 -0
  166. package/template/public/widget.js +220 -0
  167. package/template/reports.config.example.json +38 -0
  168. package/template/reports.schema.json +89 -0
  169. package/template/scripts/reports-auth-onboarding.mjs +180 -0
  170. package/template/scripts/reports-backup.mjs +276 -0
  171. package/template/scripts/reports-cloudflare-preflight.mjs +152 -0
  172. package/template/scripts/reports-deploy.mjs +173 -0
  173. package/template/scripts/reports-plan.mjs +226 -0
  174. package/template/scripts/reports-restore.mjs +180 -0
  175. package/template/scripts/reports-secrets.mjs +73 -0
  176. package/template/scripts/reports-uninstall-plan.mjs +32 -0
  177. package/template/src/agent-executor.ts +330 -0
  178. package/template/src/ai-observability.ts +163 -0
  179. package/template/src/ai-registry-validation.ts +244 -0
  180. package/template/src/ai-registry.ts +292 -0
  181. package/template/src/api-cursor.ts +84 -0
  182. package/template/src/assistant.ts +554 -0
  183. package/template/src/audit.ts +39 -0
  184. package/template/src/backup-service.ts +269 -0
  185. package/template/src/budget-control.ts +127 -0
  186. package/template/src/component-executor.ts +845 -0
  187. package/template/src/configuration-registry.ts +550 -0
  188. package/template/src/connection-egress.ts +430 -0
  189. package/template/src/connection-oauth.ts +368 -0
  190. package/template/src/evidence-service.ts +335 -0
  191. package/template/src/human-tasks.ts +316 -0
  192. package/template/src/index.ts +4114 -0
  193. package/template/src/installation-admin.ts +301 -0
  194. package/template/src/intake-abuse.ts +156 -0
  195. package/template/src/platform-registry-validation.ts +367 -0
  196. package/template/src/platform-registry.ts +753 -0
  197. package/template/src/report-admin.ts +342 -0
  198. package/template/src/report-ai-quality.ts +390 -0
  199. package/template/src/report-ai-validation.ts +80 -0
  200. package/template/src/report-ai.ts +857 -0
  201. package/template/src/report-auth.ts +292 -0
  202. package/template/src/report-better-auth.ts +583 -0
  203. package/template/src/report-context-schema.ts +132 -0
  204. package/template/src/report-context.ts +126 -0
  205. package/template/src/report-crypto.ts +142 -0
  206. package/template/src/report-delivery.ts +579 -0
  207. package/template/src/report-form-validation.ts +51 -0
  208. package/template/src/report-governance.ts +510 -0
  209. package/template/src/report-http.ts +90 -0
  210. package/template/src/report-operations.ts +685 -0
  211. package/template/src/report-operator-accounts.ts +810 -0
  212. package/template/src/report-presence.ts +398 -0
  213. package/template/src/report-queue-validation.ts +309 -0
  214. package/template/src/report-queues.ts +478 -0
  215. package/template/src/report-repository.ts +972 -0
  216. package/template/src/report-router-agent.ts +328 -0
  217. package/template/src/report-router-validation.ts +81 -0
  218. package/template/src/report-routing.ts +178 -0
  219. package/template/src/report-turnstile.ts +142 -0
  220. package/template/src/report-types.ts +224 -0
  221. package/template/src/report-validation.ts +279 -0
  222. package/template/src/report-workflow-validation.ts +119 -0
  223. package/template/src/report-workflow.ts +886 -0
  224. package/template/src/shadow-quality.ts +278 -0
  225. package/template/src/workflow-actions.ts +782 -0
  226. package/template/src/workflow-compiler.ts +230 -0
  227. package/template/src/workflow-dynamic-runtime.ts +613 -0
  228. package/template/src/workflow-effects.ts +316 -0
  229. package/template/src/workflow-expressions.ts +191 -0
  230. package/template/src/workflow-platform-types.ts +121 -0
  231. package/template/src/workflow-platform-validation.ts +540 -0
  232. package/template/src/workflow-repository.ts +916 -0
  233. package/template/src/workflow-runs.ts +686 -0
  234. package/template/src/workflow-simulator.ts +211 -0
  235. package/template/src/workspace-branding.ts +128 -0
  236. package/template/src/workspace-governance.ts +279 -0
  237. package/template/tsconfig.console.json +23 -0
  238. package/template/tsconfig.json +22 -0
  239. package/template/vite.console.config.ts +20 -0
  240. package/template/worker-configuration.d.ts +65 -0
@@ -0,0 +1,4114 @@
1
+ import { canonicalJson } from "./audit";
2
+ import { decodeApiCursor, encodeApiCursor } from "./api-cursor";
3
+ import {
4
+ assertBackupTransferAllowed,
5
+ backupBucket,
6
+ finalizeBackup,
7
+ listBackupObjects,
8
+ listBackups,
9
+ parseBackupBucket,
10
+ parseBackupCompletion,
11
+ readBackupObject,
12
+ startBackup,
13
+ } from "./backup-service";
14
+ import {
15
+ assertOperatorQueueAccess,
16
+ assertOperatorReportAccess,
17
+ assertOperatorWorkflowRunAccess,
18
+ projectOperatorFields,
19
+ requireIntegration,
20
+ requireOperator,
21
+ resolveInfrastructureOwnerIdentity,
22
+ appendOperatorAuthenticationCookies,
23
+ resolveOperatorAuthentication,
24
+ resolveOperatorSession,
25
+ } from "./report-auth";
26
+ import {
27
+ callBetterAuth,
28
+ configuredAuthProviders,
29
+ handleBetterAuthRequest,
30
+ inspectInvitation,
31
+ invitationAllowsEmail,
32
+ invitationIdFromRequest,
33
+ revokeBetterAuthSessionsForOperator,
34
+ sendOperatorInvitationEmail,
35
+ } from "./report-better-auth";
36
+ import {
37
+ type AiBinding,
38
+ escalateExpiredAiWaits,
39
+ markAiJobEnqueued,
40
+ markAiJobUnexpectedFailure,
41
+ pendingAiJobs,
42
+ processAiJob,
43
+ RetryableAiError,
44
+ } from "./report-ai";
45
+ import {
46
+ claimAiQualitySample,
47
+ listAiQualitySamples,
48
+ loadAiQualitySample,
49
+ parseQualityReview,
50
+ recordAiQualityReview,
51
+ } from "./report-ai-quality";
52
+ import { sha256, signContextToken, verifyContextToken } from "./report-crypto";
53
+ import { createStoredReportContext, loadStoredReportContext, pruneReportContexts } from "./report-context";
54
+ import { parseContextSchema, routingContextFacts, validateContextFacts } from "./report-context-schema";
55
+ import { pendingDeliveryJobs, processDeliveryJob, RetryableDeliveryError } from "./report-delivery";
56
+ import { ApiError, allowedOrigin, apiErrorResponse, json, methodNotAllowed, readJsonObject, withCors } from "./report-http";
57
+ import {
58
+ cancelHumanTask,
59
+ claimHumanTask,
60
+ listHumanTasks,
61
+ loadHumanTask,
62
+ reassignHumanTask,
63
+ releaseHumanTaskClaim,
64
+ respondToHumanTask,
65
+ } from "./human-tasks";
66
+ import {
67
+ closeAssistantSession,
68
+ createAssistantSession,
69
+ loadAssistantDraft,
70
+ loadAssistantHistory,
71
+ loadAssistantToolCall,
72
+ markAssistantDraftAdopted,
73
+ sendAssistantMessage,
74
+ } from "./assistant";
75
+ import {
76
+ createDeletionRequest,
77
+ createLegalHold,
78
+ listAuditEntries,
79
+ loadAnalytics,
80
+ processDeletionRequests,
81
+ recordExportAudit,
82
+ refreshDailyAggregatesIfStale,
83
+ releaseLegalHold,
84
+ reportsCsv,
85
+ runRetentionIfDue,
86
+ verifyAuditChain,
87
+ } from "./report-governance";
88
+ import {
89
+ createAgent,
90
+ createModelAlias,
91
+ listAgents,
92
+ listBudgets,
93
+ listModelAliases,
94
+ listPrompts,
95
+ publishAgentVersion,
96
+ publishBudget,
97
+ publishModelAliasVersion,
98
+ publishPrompt,
99
+ } from "./ai-registry";
100
+ import {
101
+ parseAgentCreate,
102
+ parseAgentVersion,
103
+ parseBudgetPublication,
104
+ parseModelAliasCreate,
105
+ parseModelAliasVersion,
106
+ parsePromptPublication,
107
+ } from "./ai-registry-validation";
108
+ import { listAgentVersions, listAiRuns, loadAiRun, loadAiUsage } from "./ai-observability";
109
+ import {
110
+ acknowledgeOperationsIncident,
111
+ beginReconciliationRun,
112
+ finishReconciliationRun,
113
+ listDeadLetters,
114
+ listOperationsIncidents,
115
+ loadOperationsIncident,
116
+ loadOperationsSnapshot,
117
+ repairStaleOperationalLeases,
118
+ recordDeadLetter,
119
+ resolveOperationsIncident,
120
+ retryOperationsIncident,
121
+ } from "./report-operations";
122
+ import {
123
+ appendOperatorSessionCookies,
124
+ bootstrapOwner,
125
+ clearOperatorSessionCookies,
126
+ createOperatorInvitation,
127
+ deleteOperatorProfilePicture,
128
+ listOperatorPeople,
129
+ logoutOperator,
130
+ operatorProfilePictureResponse,
131
+ revokeOperatorInvitation,
132
+ revokeOperatorSessions,
133
+ resolveBetterAuthOperator,
134
+ updateOperatorUser,
135
+ updateOperatorProfile,
136
+ uploadOperatorProfilePicture,
137
+ } from "./report-operator-accounts";
138
+ import { createQueue, listQueues, loadQueue, publishQueuePolicy, updateQueue } from "./report-queues";
139
+ import { parseQueueCreate, parseQueuePolicy, parseQueueUpdate } from "./report-queue-validation";
140
+ import { activeRoutingAgent, listRoutingAgents, publishRoutingAgent, validateRoutingAgentOutput } from "./report-router-agent";
141
+ import { parseRoutingAgentConfiguration } from "./report-router-validation";
142
+ import {
143
+ listAnalystPresence,
144
+ parsePresenceHeartbeat,
145
+ pruneStaleAnalystSessions,
146
+ recordAnalystAuthentication,
147
+ touchAnalystSession,
148
+ } from "./report-presence";
149
+ import {
150
+ createReport,
151
+ integrationAllowsOrigin,
152
+ installationAllowsWidgetOrigin,
153
+ type InboxCursor,
154
+ listInboxReports,
155
+ loadContextReportReplay,
156
+ loadIntegration,
157
+ markJobEnqueueFailed,
158
+ markJobEnqueued,
159
+ pendingRoutingJobs,
160
+ processRoutingJob,
161
+ publicForm,
162
+ } from "./report-repository";
163
+ import type { ActionProposalJob, AiJob, ContextClaims, DeliveryJob, EvidenceFetchJob, ReportJob, ReportState, WorkflowDispatchJob } from "./report-types";
164
+ import { enforcePublicRateLimit, turnstileConfig, verifyPublicTurnstile, verifyTurnstile } from "./report-turnstile";
165
+ import { parseContextMintInput, parsePublicReport, parseServerReport, parseWidgetReport } from "./report-validation";
166
+ import {
167
+ addReviewerMessage,
168
+ chainPendingAuditEvents,
169
+ claimAppeal,
170
+ claimReport,
171
+ decideAppeal,
172
+ listAppeals,
173
+ loadAppealDetail,
174
+ loadReportDetail,
175
+ recordHumanDecision,
176
+ releaseAppealClaim,
177
+ releaseReportClaim,
178
+ } from "./report-workflow";
179
+ import {
180
+ closeReport,
181
+ listReportActions,
182
+ listReportFindings,
183
+ loadDecisionDetail,
184
+ loadReportTimeline,
185
+ reassignReport,
186
+ reopenReport,
187
+ } from "./report-admin";
188
+ import { parseAppealDecision, parseDecision, parseReviewerMessage } from "./report-workflow-validation";
189
+ import {
190
+ parseComponentCreate,
191
+ parseComponentVersion,
192
+ parseConnectionCreate,
193
+ parseConnectionVersion,
194
+ } from "./platform-registry-validation";
195
+ import {
196
+ componentDependencies,
197
+ connectionDependencies,
198
+ createComponent,
199
+ createConnection,
200
+ listComponents,
201
+ listConnections,
202
+ loadComponent,
203
+ loadConnection,
204
+ publishComponentVersion,
205
+ publishConnectionVersion,
206
+ setComponentStatus,
207
+ setConnectionStatus,
208
+ testComponentDefinition,
209
+ updateComponentDefinition,
210
+ updateConnectionDefinition,
211
+ validateComponentDefinition,
212
+ validateConnectionDefinition,
213
+ } from "./platform-registry";
214
+ import { ConnectionExecutionError, executeConnectionJson } from "./connection-egress";
215
+ import { completeConnectionOAuth, pruneConnectionOAuthStates, revokeConnectionOAuth, startConnectionOAuth } from "./connection-oauth";
216
+ import { ComponentExecutionError, executeAssistantReadOnlyComponent, executeRoutingReadOnlyComponent } from "./component-executor";
217
+ import { maintainIntakeProtection } from "./intake-abuse";
218
+ import {
219
+ acceptEvidenceResponse,
220
+ createEvidenceSnapshot,
221
+ listReportEvidence,
222
+ pendingEvidenceFetchJobs,
223
+ processEvidenceFetch,
224
+ readEvidenceContent,
225
+ requestEvidenceFetch,
226
+ RetryableEvidenceError,
227
+ } from "./evidence-service";
228
+ import { releaseExpiredBudgetReservations } from "./budget-control";
229
+ import {
230
+ acceptActionResult,
231
+ decideActionProposal,
232
+ loadActionProposalDetail,
233
+ pendingActionProposalJobs,
234
+ processActionProposalJob,
235
+ proposeCompensatingAction,
236
+ reconcileActionProposalEvents,
237
+ retryActionProposal,
238
+ RetryableActionProposalError,
239
+ } from "./workflow-actions";
240
+ import type { JsonObject, JsonValue, WorkflowAuthorityMode } from "./workflow-platform-types";
241
+ import { validateJsonSchemaValue } from "./workflow-expressions";
242
+ import {
243
+ parseWorkflowDefinition,
244
+ parseWorkflowGraph,
245
+ parseWorkflowPublish,
246
+ } from "./workflow-platform-validation";
247
+ import {
248
+ createWorkflowFixture,
249
+ createWorkflowDefinition,
250
+ deleteWorkflowFixture,
251
+ diffWorkflowVersion,
252
+ listWorkflowFixtures,
253
+ listWorkflowDefinitions,
254
+ loadWorkflowTestRun,
255
+ loadWorkflowVersion,
256
+ loadQueueWorkflowPins,
257
+ loadWorkflowDefinition,
258
+ pinQueueWorkflow,
259
+ publishWorkflowDraft,
260
+ replaceWorkflowDraft,
261
+ retireWorkflowVersion,
262
+ runWorkflowFixtureTest,
263
+ updateWorkflowFixture,
264
+ validateWorkflowDraft,
265
+ } from "./workflow-repository";
266
+ import {
267
+ controlWorkflowRun,
268
+ createPinnedWorkflowRun,
269
+ listWorkflowRuns,
270
+ loadWorkflowRun,
271
+ pendingWorkflowDispatchJobs,
272
+ processWorkflowDispatchJob,
273
+ reconcileWorkflowRun,
274
+ RetryableWorkflowDispatchError,
275
+ sendWorkflowEvent,
276
+ } from "./workflow-runs";
277
+ import {
278
+ compareCompletedShadowRuns,
279
+ createShadowQualityTask,
280
+ dismissShadowComparison,
281
+ listShadowComparisons,
282
+ } from "./shadow-quality";
283
+ import {
284
+ createConfigurationDraft,
285
+ listConfigurations,
286
+ listPublishedForms,
287
+ loadConfiguration,
288
+ publishConfiguration,
289
+ renderTemplatePreview,
290
+ type ConfigurationType,
291
+ updateConfigurationDraft,
292
+ validateStoredConfiguration,
293
+ } from "./configuration-registry";
294
+ import {
295
+ createIntegration,
296
+ listIntegrations,
297
+ loadInstallation,
298
+ loadIntegrationAdmin,
299
+ metaVersion,
300
+ revokeIntegrationKeys,
301
+ rotateIntegrationKey,
302
+ updateInstallation,
303
+ updateIntegration,
304
+ verifyFeature,
305
+ } from "./installation-admin";
306
+ import { loadWorkspaceBrand } from "./workspace-branding";
307
+ import {
308
+ createExport,
309
+ createSavedView,
310
+ deleteSavedView,
311
+ downloadExport,
312
+ expireExports,
313
+ listDeletionRequests,
314
+ listExports,
315
+ listLegalHolds,
316
+ listSavedViews,
317
+ loadExport,
318
+ loadWorkflowAnalytics,
319
+ previewRetention,
320
+ retentionOverview,
321
+ searchWorkspace,
322
+ updateSavedView,
323
+ } from "./workspace-governance";
324
+
325
+ export { WorkspacePresence } from "./report-presence";
326
+ export {
327
+ DynamicWorkflow,
328
+ DynamicWorkflowBinding,
329
+ WorkflowRuntimeCapability,
330
+ } from "./workflow-dynamic-runtime";
331
+
332
+ const reportStates = new Set<ReportState>([
333
+ "received", "human_review", "ai_review", "awaiting_reporter", "resolved_by_human",
334
+ "resolved_by_ai", "human_review_requested", "appealed", "closed",
335
+ ]);
336
+ const EXPECTED_DATABASE_MIGRATIONS = 41;
337
+
338
+ function integer(value: string | undefined, fallback: number, minimum: number, maximum: number): number {
339
+ const parsed = Number.parseInt(value ?? "", 10);
340
+ return Number.isFinite(parsed) ? Math.max(minimum, Math.min(maximum, parsed)) : fallback;
341
+ }
342
+
343
+ function publicBaseUrl(env: Env): string {
344
+ try {
345
+ const url = new URL(env.PUBLIC_BASE_URL);
346
+ const local = url.hostname === "localhost" || url.hostname === "127.0.0.1";
347
+ if ((url.protocol !== "https:" && !(local && url.protocol === "http:")) || url.username || url.password
348
+ || url.pathname !== "/" || url.search || url.hash) throw new Error("invalid public origin");
349
+ return url.origin;
350
+ } catch {
351
+ throw new ApiError(503, "public_url_misconfigured", "PUBLIC_BASE_URL is not configured as a valid deployment origin.");
352
+ }
353
+ }
354
+
355
+ function receiptJson(receipt: Awaited<ReturnType<typeof createReport>>["receipt"]): Record<string, unknown> {
356
+ return {
357
+ schema_version: "1",
358
+ report_id: receipt.reportId,
359
+ reference: receipt.reference,
360
+ state: receipt.state,
361
+ received_at: receipt.receivedAt,
362
+ updates_via: "email_or_customer_webhook",
363
+ idempotent_replay: receipt.idempotentReplay,
364
+ };
365
+ }
366
+
367
+ function queueRoutingJob(env: Env, ctx: ExecutionContext, jobId: string, reportId: string): void {
368
+ const job: ReportJob = { version: 1, jobId, type: "route_report", reportId };
369
+ ctx.waitUntil((async () => {
370
+ try {
371
+ await env.REPORT_JOBS.send(job, { contentType: "json" });
372
+ await markJobEnqueued(env.DB, jobId);
373
+ } catch (error) {
374
+ console.error("report routing enqueue failed", { jobId, errorType: error instanceof Error ? error.name : typeof error });
375
+ await markJobEnqueueFailed(env.DB, jobId, "queue_send_failed");
376
+ }
377
+ })());
378
+ }
379
+
380
+ function queueDeliveryJobs(env: Env, ctx: ExecutionContext, jobs: DeliveryJob[]): void {
381
+ if (!jobs.length) return;
382
+ ctx.waitUntil((async () => {
383
+ try {
384
+ await env.DELIVERY_JOBS.sendBatch(jobs.map((job) => ({ body: job, contentType: "json" as const })));
385
+ } catch (error) {
386
+ console.error("outbox delivery enqueue failed; scheduled reconciliation will retry", {
387
+ jobCount: jobs.length,
388
+ errorType: error instanceof Error ? error.name : typeof error,
389
+ });
390
+ }
391
+ })());
392
+ }
393
+
394
+ function publishWorkspaceEvent(
395
+ env: Env,
396
+ ctx: ExecutionContext,
397
+ event: { type: "claim_changed" | "reports_changed"; reportId?: string },
398
+ ): void {
399
+ ctx.waitUntil(env.WORKSPACE_PRESENCE.getByName("default").publish(event).catch((error: unknown) => {
400
+ console.error("workspace presence publish failed", {
401
+ eventType: event.type,
402
+ reportId: event.reportId,
403
+ errorType: error instanceof Error ? error.name : typeof error,
404
+ });
405
+ }));
406
+ }
407
+
408
+ function deliveryOptions(env: Env) {
409
+ return {
410
+ notificationEnabled: Boolean(env.NOTIFICATION_WEBHOOK_URL?.trim() && env.NOTIFICATION_WEBHOOK_SECRET?.trim()),
411
+ };
412
+ }
413
+
414
+ function intakeDeliveryOptions(env: Env) {
415
+ const emailBinding = (env as unknown as { EMAIL?: unknown }).EMAIL;
416
+ return {
417
+ contactEncryptionKey: env.CREDENTIAL_ENCRYPTION_KEY,
418
+ emailEnabled: Boolean(emailBinding && env.EMAIL_FROM_ADDRESS?.trim()),
419
+ notificationEnabled: Boolean(env.NOTIFICATION_WEBHOOK_URL?.trim() && env.NOTIFICATION_WEBHOOK_SECRET?.trim()),
420
+ };
421
+ }
422
+
423
+ function configuredAiBinding(env: Env): AiBinding | undefined {
424
+ if (env.AI_LOCAL_FIXTURE_ENABLED === "true") {
425
+ const origin = new URL(publicBaseUrl(env));
426
+ if (origin.hostname !== "localhost" && origin.hostname !== "127.0.0.1") {
427
+ console.error("local AI fixture ignored outside localhost");
428
+ return undefined;
429
+ }
430
+ return {
431
+ async run() {
432
+ const fixture: unknown = JSON.parse(env.AI_LOCAL_FIXTURE_JSON);
433
+ return fixture;
434
+ },
435
+ };
436
+ }
437
+ return env.AI;
438
+ }
439
+
440
+ async function handleContextToken(request: Request, env: Env): Promise<Response> {
441
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
442
+ const integration = await requireIntegration(request, env);
443
+ if (!integration.scopes.includes("mint_context_tokens")) throw new ApiError(403, "integration_scope_denied", "The integration cannot mint context tokens.");
444
+ if (integration.channelMode !== "contextual") throw new ApiError(403, "integration_mode_denied", "This reporting channel does not accept authenticated context.");
445
+ const input = parseContextMintInput(await readJsonObject(request, 32 * 1024));
446
+ if (!input.reporter.reference) throw new ApiError(400, "authenticated_reporter_required", "A signed-in reporting channel must identify the reporter from the customer backend session.", { field: "reporter.reference" });
447
+ if (!input.origin) throw new ApiError(400, "widget_origin_required", "origin is required when minting widget context.", { field: "origin" });
448
+ if (!integration.allowedOrigins.includes(input.origin)) {
449
+ throw new ApiError(403, "origin_not_allowed", "The widget origin is not allowed for this integration.");
450
+ }
451
+ const formVersionId = integration.formVersionId || input.formVersionId || "general-v1";
452
+ if (integration.formVersionId && input.formVersionId && integration.formVersionId !== input.formVersionId) {
453
+ throw new ApiError(400, "integration_form_mismatch", "This reporting channel is connected to a different published form.");
454
+ }
455
+ if (!await publicForm(env.DB, formVersionId)) throw new ApiError(404, "form_not_found", "The report form is not available.");
456
+ const contextSchema = parseContextSchema(integration.contextSchema, { allowLegacy: true });
457
+ validateContextFacts(input.trustedFacts, contextSchema);
458
+ const issuedAt = Math.floor(Date.now() / 1000);
459
+ const ttl = integer(env.CONTEXT_TOKEN_TTL_SECONDS, 300, 60, 900);
460
+ const expiresAt = new Date((issuedAt + ttl) * 1_000).toISOString();
461
+ const storedContext = await createStoredReportContext(env.DB, { ...input, origin: input.origin }, {
462
+ integrationId: integration.id,
463
+ formVersionId,
464
+ contextSchemaVersion: contextSchema.version,
465
+ routingFacts: routingContextFacts(input.trustedFacts, contextSchema),
466
+ expiresAt,
467
+ });
468
+ const claims: ContextClaims = {
469
+ version: 2,
470
+ iss: publicBaseUrl(env),
471
+ aud: "safest-resolve-reports",
472
+ integrationId: integration.id,
473
+ contextId: storedContext.id,
474
+ formVersionId,
475
+ jti: crypto.randomUUID(),
476
+ iat: issuedAt,
477
+ exp: issuedAt + ttl,
478
+ origin: input.origin,
479
+ };
480
+ const token = await signContextToken(claims, env.CONTEXT_SIGNING_SECRET);
481
+ return json({ schema_version: "2", context_token: token, expires_at: expiresAt }, { status: 201 });
482
+ }
483
+
484
+ async function handleWidgetConfig(request: Request, env: Env): Promise<Response> {
485
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
486
+ const url = new URL(request.url);
487
+ const integrationId = url.searchParams.get("integration_id")?.trim() ?? "";
488
+ const formVersion = url.searchParams.get("form_version")?.trim() || "general-v1";
489
+ if (!integrationId || integrationId.length > 200 || formVersion.length > 100) {
490
+ throw new ApiError(400, "invalid_query", "integration_id and form_version are required and must be valid.");
491
+ }
492
+ const integration = await loadIntegration(env.DB, integrationId);
493
+ if (!integration || integration.enabled !== 1) throw new ApiError(404, "widget_not_found", "The report widget is not available.");
494
+ const origin = request.headers.get("origin");
495
+ if (origin && !integrationAllowsOrigin(integration, origin)) throw new ApiError(403, "origin_not_allowed", "The widget origin is not allowed.");
496
+ const [form, brand] = await Promise.all([publicForm(env.DB, formVersion), loadWorkspaceBrand(env.DB)]);
497
+ if (!form) throw new ApiError(404, "form_not_found", "The report form is not available.");
498
+ if (integration.form_version_id && integration.form_version_id !== formVersion) {
499
+ throw new ApiError(404, "form_not_found", "This reporting channel is connected to a different form.");
500
+ }
501
+ const antiAbuse = turnstileConfig(env);
502
+ return json({
503
+ schema_version: "1",
504
+ integration_id: integrationId,
505
+ mode: integration.channel_mode,
506
+ human_verification: { required: true, provider: "cloudflare_turnstile", site_key: antiAbuse.siteKey },
507
+ form: {
508
+ id: form.id,
509
+ title: form.title,
510
+ description: form.description,
511
+ target_types: form.targetTypes,
512
+ reasons: form.reasons,
513
+ fields: form.fields,
514
+ consent_notice: form.consentNotice,
515
+ },
516
+ brand,
517
+ });
518
+ }
519
+
520
+ async function handleWidgetEmbed(request: Request, env: Env): Promise<Response> {
521
+ if (request.method !== "GET" && request.method !== "HEAD") return methodNotAllowed(["GET", "HEAD"]);
522
+ const url = new URL(request.url);
523
+ const integrationId = url.searchParams.get("integration_id")?.trim() ?? "";
524
+ const parentOrigin = url.searchParams.get("parent_origin")?.trim() ?? "";
525
+ const preview = url.searchParams.get("preview") === "1";
526
+ if (!integrationId || integrationId.length > 200 || !parentOrigin || parentOrigin.length > 2_048) {
527
+ throw new ApiError(400, "embed_configuration_invalid", "The embedded report form is missing its channel or website origin.");
528
+ }
529
+ let normalizedParent: string;
530
+ try {
531
+ const parsed = new URL(parentOrigin);
532
+ if (parsed.origin !== parentOrigin || parsed.username || parsed.password) throw new Error("not an origin");
533
+ normalizedParent = parsed.origin;
534
+ } catch {
535
+ throw new ApiError(400, "embed_origin_invalid", "The embedded report form received an invalid website origin.");
536
+ }
537
+ const integration = await loadIntegration(env.DB, integrationId);
538
+ if (!integration || integration.enabled !== 1) throw new ApiError(404, "widget_not_found", "The report widget is not available.");
539
+ const ownOrigin = publicBaseUrl(env);
540
+ const allowed = preview && normalizedParent === ownOrigin
541
+ || integrationAllowsOrigin(integration, normalizedParent);
542
+ if (!allowed) throw new ApiError(403, "origin_not_allowed", "This website is not allowed to embed the report form.");
543
+ const assetUrl = new URL("/embed/index.html", request.url);
544
+ const asset = await env.ASSETS.fetch(new Request(assetUrl, {
545
+ method: request.method,
546
+ headers: request.headers,
547
+ }));
548
+ const headers = new Headers(asset.headers);
549
+ headers.set("cache-control", "no-store");
550
+ headers.set("content-security-policy", [
551
+ "default-src 'self'",
552
+ "script-src 'self' https://challenges.cloudflare.com",
553
+ "style-src 'self' 'unsafe-inline'",
554
+ "font-src 'self'",
555
+ "img-src 'self' data: https:",
556
+ "connect-src 'self' https://challenges.cloudflare.com",
557
+ "frame-src https://challenges.cloudflare.com",
558
+ `frame-ancestors ${normalizedParent}`,
559
+ "object-src 'none'",
560
+ "base-uri 'none'",
561
+ "form-action 'self'",
562
+ ].join("; "));
563
+ headers.set("referrer-policy", "no-referrer");
564
+ headers.set("permissions-policy", "camera=(), microphone=(), geolocation=(), payment=()");
565
+ headers.set("x-content-type-options", "nosniff");
566
+ headers.delete("x-frame-options");
567
+ return new Response(request.method === "HEAD" ? null : asset.body, {
568
+ status: asset.status,
569
+ statusText: asset.statusText,
570
+ headers,
571
+ });
572
+ }
573
+
574
+ async function handleWidgetReport(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
575
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
576
+ const body = await readJsonObject(request);
577
+ const rawToken = typeof body.context_token === "string" ? body.context_token : "";
578
+ const claims = await verifyContextToken(rawToken, env.CONTEXT_SIGNING_SECRET, publicBaseUrl(env));
579
+ const integration = await loadIntegration(env.DB, claims.integrationId);
580
+ if (!integration || integration.enabled !== 1) throw new ApiError(401, "integration_not_available", "The application integration is not enabled.");
581
+ if (integration.channel_mode !== "contextual") throw new ApiError(403, "integration_mode_denied", "This channel does not accept authenticated widget reports.");
582
+ const origin = request.headers.get("origin");
583
+ const isolatedEmbedOrigin = publicBaseUrl(env);
584
+ if (!origin || !integrationAllowsOrigin(integration, claims.origin)
585
+ || (origin !== isolatedEmbedOrigin && origin !== claims.origin)) {
586
+ throw new ApiError(403, "origin_not_allowed", "The report origin does not match the signed application context.");
587
+ }
588
+ const context = await loadStoredReportContext(env.DB, claims.contextId);
589
+ if (context.integrationId !== claims.integrationId || context.origin !== claims.origin || context.formVersionId !== claims.formVersionId) {
590
+ throw new ApiError(401, "report_context_mismatch", "The trusted report context does not match this submission.");
591
+ }
592
+ const idempotencyKey = typeof body.idempotency_key === "string" ? body.idempotency_key.trim() : "";
593
+ if (idempotencyKey) {
594
+ const replay = await loadContextReportReplay(env.DB, claims.integrationId, claims.jti, idempotencyKey);
595
+ if (replay) return json(receiptJson(replay));
596
+ }
597
+ const remoteIp = request.headers.get("cf-connecting-ip");
598
+ await verifyTurnstile(body.turnstile_token, remoteIp, turnstileConfig(env), {
599
+ action: "widget_report_submit",
600
+ cdata: await sha256(rawToken),
601
+ });
602
+ const result = await createReport(env.DB, parseWidgetReport(body, claims, context), {
603
+ ...intakeDeliveryOptions(env),
604
+ abusePepper: env.RATE_LIMIT_PEPPER,
605
+ networkAddress: remoteIp,
606
+ turnstileOutcome: "verified",
607
+ surgeThresholdPerFiveMinutes: integer(env.PUBLIC_REPORT_SURGE_PER_5_MINUTES, 250, 25, 100_000),
608
+ });
609
+ if (result.route) queueRoutingJob(env, ctx, result.jobId, result.receipt.reportId);
610
+ queueDeliveryJobs(env, ctx, result.deliveryJobs);
611
+ return json(receiptJson(result.receipt), { status: result.created ? 201 : 200 });
612
+ }
613
+
614
+ async function handleAnonymousWidgetReport(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
615
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
616
+ if (request.headers.get("origin") !== publicBaseUrl(env)) {
617
+ throw new ApiError(403, "origin_not_allowed", "Anonymous embedded reports must be submitted from the isolated Resolve form.");
618
+ }
619
+ const body = await readJsonObject(request);
620
+ const integrationId = typeof body.integration_id === "string" ? body.integration_id.trim() : "";
621
+ const integration = await loadIntegration(env.DB, integrationId);
622
+ if (!integration || integration.enabled !== 1 || integration.channel_mode !== "anonymous") {
623
+ throw new ApiError(404, "widget_not_found", "The anonymous reporting channel is not available.");
624
+ }
625
+ if (integration.form_version_id && body.form_version !== integration.form_version_id) {
626
+ throw new ApiError(400, "integration_form_mismatch", "This reporting channel is connected to a different form.");
627
+ }
628
+ const remoteIp = request.headers.get("cf-connecting-ip");
629
+ await enforcePublicRateLimit(
630
+ env.DB,
631
+ remoteIp,
632
+ env.RATE_LIMIT_PEPPER,
633
+ integer(env.PUBLIC_REPORT_LIMIT_PER_10_MINUTES, 5, 1, 100),
634
+ );
635
+ await verifyTurnstile(body.turnstile_token, remoteIp, turnstileConfig(env), { action: "anonymous_report_submit" });
636
+ const reportBody = { ...body };
637
+ delete reportBody.integration_id;
638
+ const result = await createReport(env.DB, parsePublicReport(reportBody, integration.id), {
639
+ ...intakeDeliveryOptions(env),
640
+ abusePepper: env.RATE_LIMIT_PEPPER,
641
+ networkAddress: remoteIp,
642
+ turnstileOutcome: "verified",
643
+ surgeThresholdPerFiveMinutes: integer(env.PUBLIC_REPORT_SURGE_PER_5_MINUTES, 250, 25, 100_000),
644
+ });
645
+ if (result.route) queueRoutingJob(env, ctx, result.jobId, result.receipt.reportId);
646
+ queueDeliveryJobs(env, ctx, result.deliveryJobs);
647
+ return json(receiptJson(result.receipt), { status: result.created ? 201 : 200 });
648
+ }
649
+
650
+ async function handleServerReport(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
651
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
652
+ const integration = await requireIntegration(request, env);
653
+ if (!integration.scopes.includes("submit_reports")) throw new ApiError(403, "integration_scope_denied", "The integration cannot submit reports.");
654
+ const body = await readJsonObject(request);
655
+ const headerIdempotency = request.headers.get("idempotency-key")?.trim();
656
+ if (headerIdempotency && body.idempotency_key !== headerIdempotency) {
657
+ throw new ApiError(400, "idempotency_key_mismatch", "The Idempotency-Key header and body value must match.");
658
+ }
659
+ const result = await createReport(env.DB, parseServerReport(body, integration.id), {
660
+ ...intakeDeliveryOptions(env),
661
+ abusePepper: env.RATE_LIMIT_PEPPER,
662
+ networkAddress: request.headers.get("cf-connecting-ip"),
663
+ turnstileOutcome: null,
664
+ surgeThresholdPerFiveMinutes: integer(env.PUBLIC_REPORT_SURGE_PER_5_MINUTES, 250, 25, 100_000),
665
+ });
666
+ if (result.route) queueRoutingJob(env, ctx, result.jobId, result.receipt.reportId);
667
+ queueDeliveryJobs(env, ctx, result.deliveryJobs);
668
+ return json(receiptJson(result.receipt), { status: result.created ? 201 : 200 });
669
+ }
670
+
671
+ async function handlePublicConfig(request: Request, env: Env): Promise<Response> {
672
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
673
+ const installation = await env.DB.prepare(`
674
+ SELECT anonymous_reporting_enabled FROM installations WHERE id = 'default' LIMIT 1
675
+ `).first<{ anonymous_reporting_enabled: number }>();
676
+ if (!installation || installation.anonymous_reporting_enabled !== 1) {
677
+ throw new ApiError(404, "public_reporting_disabled", "Public reporting is not enabled for this deployment.");
678
+ }
679
+ const config = turnstileConfig(env);
680
+ const formVersion = new URL(request.url).searchParams.get("form_version")?.trim() || "general-v1";
681
+ if (formVersion.length > 100) throw new ApiError(400, "invalid_query", "form_version is invalid.");
682
+ if (formVersion !== "general-v1") {
683
+ const settingsRow = await env.DB.prepare(`SELECT settings_json AS settingsJson FROM installations WHERE id = 'default' LIMIT 1`).first<{ settingsJson: string }>();
684
+ let publicForms: string[] = [];
685
+ try {
686
+ const settings: unknown = JSON.parse(settingsRow?.settingsJson || "{}");
687
+ const configured = settings && typeof settings === "object" && !Array.isArray(settings) ? (settings as Record<string, unknown>).public_form_version_ids : [];
688
+ if (Array.isArray(configured)) publicForms = configured.filter((value): value is string => typeof value === "string");
689
+ } catch { publicForms = []; }
690
+ if (!publicForms.includes(formVersion)) throw new ApiError(404, "form_not_found", "The public report form is not available.");
691
+ }
692
+ const [form, brand] = await Promise.all([publicForm(env.DB, formVersion), loadWorkspaceBrand(env.DB)]);
693
+ if (!form) throw new ApiError(404, "form_not_found", "The public report form is not available.");
694
+ return json({
695
+ schema_version: "1",
696
+ turnstile_site_key: config.siteKey,
697
+ form: {
698
+ id: form.id,
699
+ title: form.title,
700
+ description: form.description,
701
+ target_types: form.targetTypes,
702
+ reasons: form.reasons,
703
+ fields: form.fields,
704
+ consent_notice: form.consentNotice,
705
+ },
706
+ brand,
707
+ });
708
+ }
709
+
710
+ async function handlePublicBrand(request: Request, env: Env): Promise<Response> {
711
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
712
+ return json({ schema_version: "1", brand: await loadWorkspaceBrand(env.DB) }, {
713
+ headers: { "cache-control": "public, max-age=0, must-revalidate" },
714
+ });
715
+ }
716
+
717
+ async function handlePublicReport(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
718
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
719
+ const expectedOrigin = publicBaseUrl(env);
720
+ if (request.headers.get("origin") !== expectedOrigin) {
721
+ throw new ApiError(403, "origin_not_allowed", "Public reports must be submitted from this deployment's hosted form.");
722
+ }
723
+ const body = await readJsonObject(request);
724
+ const remoteIp = request.headers.get("cf-connecting-ip");
725
+ await enforcePublicRateLimit(
726
+ env.DB,
727
+ remoteIp,
728
+ env.RATE_LIMIT_PEPPER,
729
+ integer(env.PUBLIC_REPORT_LIMIT_PER_10_MINUTES, 5, 1, 100),
730
+ );
731
+ await verifyPublicTurnstile(body.turnstile_token, remoteIp, turnstileConfig(env));
732
+ const result = await createReport(env.DB, parsePublicReport(body, env.PUBLIC_INTEGRATION_ID), {
733
+ ...intakeDeliveryOptions(env),
734
+ abusePepper: env.RATE_LIMIT_PEPPER,
735
+ networkAddress: remoteIp,
736
+ turnstileOutcome: "verified",
737
+ surgeThresholdPerFiveMinutes: integer(env.PUBLIC_REPORT_SURGE_PER_5_MINUTES, 250, 25, 100_000),
738
+ });
739
+ if (result.route) queueRoutingJob(env, ctx, result.jobId, result.receipt.reportId);
740
+ queueDeliveryJobs(env, ctx, result.deliveryJobs);
741
+ return json(receiptJson(result.receipt), { status: result.created ? 201 : 200 });
742
+ }
743
+
744
+ async function handleAdminSession(request: Request, env: Env): Promise<Response> {
745
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
746
+ const authentication = await resolveOperatorAuthentication(request, env);
747
+ if (!authentication) throw new ApiError(401, "operator_authentication_required", "Sign in to your Safest workspace to continue.");
748
+ const session = authentication.session;
749
+ const profile = await recordAnalystAuthentication(env.DB, session);
750
+ return appendOperatorAuthenticationCookies(json({
751
+ session: { ...session, profile },
752
+ configuration: {
753
+ allowed_action_codes: env.ALLOWED_ACTION_CODES.split(",").map((value) => value.trim()).filter(Boolean),
754
+ action_delivery_enabled: Boolean(env.ACTION_WEBHOOK_URL?.trim() && env.ACTION_WEBHOOK_SECRET?.trim()),
755
+ notification_delivery_enabled: Boolean(env.NOTIFICATION_WEBHOOK_URL?.trim() && env.NOTIFICATION_WEBHOOK_SECRET?.trim()),
756
+ account_notification_enabled: Boolean(env.OPERATOR_NOTIFICATION_WEBHOOK_URL?.trim() && env.OPERATOR_NOTIFICATION_WEBHOOK_SECRET?.trim()),
757
+ },
758
+ }), request, authentication);
759
+ }
760
+
761
+ async function normalizedBetterAuthError(response: Response, fallback: string): Promise<Response> {
762
+ const body = await response.json().catch(() => ({})) as { code?: unknown; message?: unknown };
763
+ const code = typeof body.code === "string" ? body.code.toLowerCase() : "authentication_failed";
764
+ const message = typeof body.message === "string" && body.message ? body.message : fallback;
765
+ return json({ error: { code, message } }, { status: response.status });
766
+ }
767
+
768
+ function normalizedBetterAuthSuccess(response: Response, body: unknown, status = 200): Response {
769
+ const headers = new Headers(response.headers);
770
+ headers.set("content-type", "application/json; charset=utf-8");
771
+ headers.set("cache-control", "no-store");
772
+ return new Response(JSON.stringify(body), {
773
+ status,
774
+ headers,
775
+ });
776
+ }
777
+
778
+ async function handleAuthProviders(request: Request, env: Env): Promise<Response> {
779
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
780
+ return json({
781
+ schema_version: "1",
782
+ password: true,
783
+ providers: configuredAuthProviders(env),
784
+ }, { headers: { "cache-control": "no-store" } });
785
+ }
786
+
787
+ async function handleOperatorLogin(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
788
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
789
+ const body = await readJsonObject(request, 4 * 1024);
790
+ const email = typeof body.email === "string" ? body.email.trim().toLowerCase() : "";
791
+ const operator = email ? await env.DB.prepare(
792
+ "SELECT status FROM operator_users WHERE email = ?1 LIMIT 1",
793
+ ).bind(email).first<{ status: string }>() : null;
794
+ const invited = !operator && await invitationAllowsEmail(request, env, email);
795
+ if (operator?.status !== "active" && !invited) {
796
+ throw new ApiError(401, "invalid_operator_credentials", "Email or password is incorrect.");
797
+ }
798
+ const better = await callBetterAuth(request, env, ctx, "/sign-in/email", {
799
+ email,
800
+ password: body.password,
801
+ rememberMe: false,
802
+ callbackURL: "/",
803
+ });
804
+ if (better.ok) {
805
+ return normalizedBetterAuthSuccess(better, { schema_version: "1", authenticated: true });
806
+ }
807
+ return normalizedBetterAuthError(better, "Email or password is incorrect.");
808
+ }
809
+
810
+ async function handleOperatorInvitationAccept(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
811
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
812
+ const body = await readJsonObject(request, 8 * 1024);
813
+ const inspectionRequest = new Request(request.url, {
814
+ method: "POST",
815
+ headers: request.headers,
816
+ body: JSON.stringify({ token: body.token }),
817
+ });
818
+ const inspection = await inspectInvitation(inspectionRequest, env);
819
+ if (!inspection.ok) return inspection;
820
+ const details = await inspection.clone().json() as { invitation: { email: string } };
821
+ const admissionCookie = inspection.headers.get("set-cookie")?.split(";", 1)[0] ?? "";
822
+ const requestCookies = request.headers.get("cookie") ?? "";
823
+ const cookie = [requestCookies, admissionCookie].filter(Boolean).join("; ");
824
+ const better = await callBetterAuth(request, env, ctx, "/sign-up/email", {
825
+ name: body.display_name,
826
+ email: details.invitation.email,
827
+ password: body.password,
828
+ callbackURL: "/",
829
+ rememberMe: false,
830
+ }, cookie);
831
+ if (!better.ok) return normalizedBetterAuthError(better, "The invitation could not be accepted.");
832
+ const admittedRequestHeaders = new Headers(request.headers);
833
+ admittedRequestHeaders.set("cookie", cookie);
834
+ const invitationId = await invitationIdFromRequest(new Request(request.url, { headers: admittedRequestHeaders }), env);
835
+ if (!invitationId || !await resolveBetterAuthOperator(env.DB, {
836
+ email: details.invitation.email,
837
+ name: typeof body.display_name === "string" ? body.display_name : details.invitation.email.split("@", 1)[0],
838
+ }, invitationId)) {
839
+ throw new ApiError(409, "invitation_already_used", "This invitation has already been used or the account already exists.");
840
+ }
841
+ const authResult = await better.clone().json().catch(() => ({})) as { token?: unknown };
842
+ const headers = new Headers(better.headers);
843
+ const invitationCookie = inspection.headers.get("set-cookie");
844
+ if (invitationCookie) headers.append("set-cookie", invitationCookie);
845
+ return new Response(JSON.stringify({
846
+ schema_version: "1",
847
+ account_created: true,
848
+ requires_verification: !authResult.token,
849
+ }), { status: 201, headers });
850
+ }
851
+
852
+ async function handleOperatorPasswordResetRequest(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
853
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
854
+ const body = await readJsonObject(request, 4 * 1024);
855
+ const email = typeof body.email === "string" ? body.email.trim().toLowerCase() : "";
856
+ const better = await callBetterAuth(request, env, ctx, "/request-password-reset", {
857
+ email,
858
+ redirectTo: "/",
859
+ });
860
+ if (!better.ok) {
861
+ console.warn("Password reset request was not accepted by Better Auth", better.status);
862
+ better.body?.cancel();
863
+ }
864
+ return json({
865
+ schema_version: "1",
866
+ requested: true,
867
+ message: "If this account can be reset, password reset instructions are on the way.",
868
+ }, { status: 202 });
869
+ }
870
+
871
+ async function handleOperatorPasswordResetComplete(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
872
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
873
+ const body = await readJsonObject(request, 8 * 1024);
874
+ const better = await callBetterAuth(request, env, ctx, "/reset-password", {
875
+ token: body.token,
876
+ newPassword: body.password,
877
+ });
878
+ if (!better.ok) return normalizedBetterAuthError(better, "This password reset link is invalid or expired.");
879
+ return json({ schema_version: "1", reset: true });
880
+ }
881
+
882
+ async function handleOperatorProfile(request: Request, env: Env): Promise<Response> {
883
+ const session = await requireOperator(request, env, "readReports");
884
+ if (request.method === "GET") return json({ schema_version: "1", profile: session });
885
+ if (request.method === "PATCH") {
886
+ const profile = await updateOperatorProfile(env.DB, session, await readJsonObject(request, 4 * 1024));
887
+ return json({ schema_version: "1", profile });
888
+ }
889
+ return methodNotAllowed(["GET", "PATCH"]);
890
+ }
891
+
892
+ async function handleOperatorProfilePicture(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
893
+ const session = await requireOperator(request, env, "readReports");
894
+ if (request.method === "PUT") {
895
+ const result = await uploadOperatorProfilePicture(env.DB, env.PROFILE_MEDIA, session, request);
896
+ if (result.oldObjectKey) ctx.waitUntil(env.PROFILE_MEDIA.delete(result.oldObjectKey));
897
+ return json({ schema_version: "1", profile: result.session });
898
+ }
899
+ if (request.method === "DELETE") {
900
+ const result = await deleteOperatorProfilePicture(env.DB, session);
901
+ if (result.oldObjectKey) ctx.waitUntil(env.PROFILE_MEDIA.delete(result.oldObjectKey));
902
+ return json({ schema_version: "1", profile: result.session });
903
+ }
904
+ return methodNotAllowed(["PUT", "DELETE"]);
905
+ }
906
+
907
+ async function handleOperatorProfilePictureRead(
908
+ request: Request,
909
+ env: Env,
910
+ userId: string,
911
+ ): Promise<Response> {
912
+ if (request.method !== "GET" && request.method !== "HEAD") return methodNotAllowed(["GET", "HEAD"]);
913
+ await requireOperator(request, env, "readReports");
914
+ return operatorProfilePictureResponse(env.DB, env.PROFILE_MEDIA, userId, request.method === "HEAD");
915
+ }
916
+
917
+ async function handleOperatorLogout(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
918
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
919
+ await logoutOperator(request, env.DB);
920
+ const better = await callBetterAuth(request, env, ctx, "/sign-out", {});
921
+ const response = normalizedBetterAuthSuccess(better, { schema_version: "1", signed_out: true });
922
+ return clearOperatorSessionCookies(response, request);
923
+ }
924
+
925
+ async function handleInfrastructureBootstrap(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
926
+ if (request.method !== "GET" && request.method !== "POST") return methodNotAllowed(["GET", "POST"]);
927
+ const identity = await resolveInfrastructureOwnerIdentity(request, env, ctx);
928
+ if (!identity) throw new ApiError(401, "infrastructure_authentication_required", "Cloudflare Access authentication is required for infrastructure owner setup.");
929
+ const issued = await bootstrapOwner(env.DB, identity);
930
+ if (request.method === "POST") {
931
+ return appendOperatorSessionCookies(json({ schema_version: "1", bootstrapped: true }, { status: 201 }), issued, request);
932
+ }
933
+ return appendOperatorSessionCookies(new Response(null, {
934
+ status: 303,
935
+ headers: { location: "/#reports", "cache-control": "no-store" },
936
+ }), issued, request);
937
+ }
938
+
939
+ async function handleAdminPeople(request: Request, env: Env): Promise<Response> {
940
+ const session = await requireOperator(request, env, "managePeople");
941
+ if (request.method === "GET") {
942
+ return json({ schema_version: "1", ...(await listOperatorPeople(env.DB)) });
943
+ }
944
+ if (request.method === "POST") {
945
+ const invitation = await createOperatorInvitation(
946
+ env.DB,
947
+ session,
948
+ await readJsonObject(request, 4 * 1024),
949
+ publicBaseUrl(env),
950
+ );
951
+ try {
952
+ await sendOperatorInvitationEmail(env, invitation);
953
+ } catch (error) {
954
+ await revokeOperatorInvitation(env.DB, session, invitation.id);
955
+ console.error("Operator invitation delivery failed", error instanceof Error ? error.message : typeof error);
956
+ throw new ApiError(
957
+ 502,
958
+ "invitation_delivery_failed",
959
+ "The invitation email could not be delivered, so no active invitation was left behind. Check Email Sending and try again.",
960
+ );
961
+ }
962
+ return json({ schema_version: "1", invitation }, { status: 201 });
963
+ }
964
+ return methodNotAllowed(["GET", "POST"]);
965
+ }
966
+
967
+ async function handleAdminInvitation(
968
+ request: Request,
969
+ env: Env,
970
+ invitationId: string,
971
+ ): Promise<Response> {
972
+ if (request.method !== "DELETE") return methodNotAllowed(["DELETE"]);
973
+ const session = await requireOperator(request, env, "managePeople");
974
+ await revokeOperatorInvitation(env.DB, session, invitationId);
975
+ return new Response(null, { status: 204 });
976
+ }
977
+
978
+ async function handleAdminOperatorPasswordReset(
979
+ request: Request,
980
+ env: Env,
981
+ userId: string,
982
+ ctx: ExecutionContext,
983
+ ): Promise<Response> {
984
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
985
+ const session = await requireOperator(request, env, "managePeople");
986
+ const target = await env.DB.prepare(`
987
+ SELECT operator_users.email AS email, operator_users.roles_json AS rolesJson,
988
+ EXISTS(
989
+ SELECT 1
990
+ FROM auth_user JOIN auth_account ON auth_account.userId = auth_user.id
991
+ WHERE auth_user.email = operator_users.email COLLATE NOCASE
992
+ AND auth_account.providerId = 'credential'
993
+ AND auth_account.password IS NOT NULL
994
+ ) AS passwordAuth
995
+ FROM operator_users WHERE operator_users.id = ?1 LIMIT 1
996
+ `).bind(userId).first<{ email: string; rolesJson: string; passwordAuth: number }>();
997
+ if (!target) throw new ApiError(404, "operator_not_found", "The Safest account was not found.");
998
+ const targetRoles = JSON.parse(target.rolesJson) as unknown;
999
+ const privileged = Array.isArray(targetRoles) && targetRoles.some((role) => role === "owner" || role === "administrator");
1000
+ if (privileged && !session.roles.includes("owner")) {
1001
+ throw new ApiError(403, "owner_required", "Only the infrastructure owner can reset an owner or administrator account.");
1002
+ }
1003
+ if (!target.passwordAuth) {
1004
+ throw new ApiError(
1005
+ 409,
1006
+ "password_authentication_unavailable",
1007
+ "This account uses OAuth or Cloudflare Access and does not have a Safest password to reset.",
1008
+ );
1009
+ }
1010
+ const better = await callBetterAuth(request, env, ctx, "/request-password-reset", {
1011
+ email: target.email,
1012
+ redirectTo: "/",
1013
+ });
1014
+ if (!better.ok) return normalizedBetterAuthError(better, "The password reset email could not be sent.");
1015
+ return json({
1016
+ schema_version: "1",
1017
+ password_reset: { delivery: "email", email: target.email },
1018
+ }, { status: 202 });
1019
+ }
1020
+
1021
+ async function handleAdminOperator(
1022
+ request: Request,
1023
+ env: Env,
1024
+ userId: string,
1025
+ action?: "revoke-sessions",
1026
+ ): Promise<Response> {
1027
+ const session = await requireOperator(request, env, "managePeople");
1028
+ if (action === "revoke-sessions") {
1029
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
1030
+ const revoked = await revokeOperatorSessions(env.DB, session, userId)
1031
+ + await revokeBetterAuthSessionsForOperator(env, userId);
1032
+ return json({ schema_version: "1", user_id: userId, revoked_sessions: revoked });
1033
+ }
1034
+ if (request.method !== "PATCH") return methodNotAllowed(["PATCH"]);
1035
+ const operator = await updateOperatorUser(env.DB, session, userId, await readJsonObject(request, 32 * 1024));
1036
+ if (operator.status === "suspended") await revokeBetterAuthSessionsForOperator(env, userId);
1037
+ return json({ schema_version: "1", operator });
1038
+ }
1039
+
1040
+ async function handleAdminPresence(request: Request, env: Env): Promise<Response> {
1041
+ const session = await requireOperator(request, env, "readReports");
1042
+ const profile = await recordAnalystAuthentication(env.DB, session);
1043
+ if (request.method === "GET") {
1044
+ return json({ schema_version: "1", profile, analysts: await listAnalystPresence(env.DB, 45, session.queueScope ?? null) });
1045
+ }
1046
+ if (request.method === "POST") {
1047
+ const heartbeat = parsePresenceHeartbeat(await readJsonObject(request, 4 * 1024));
1048
+ const result = await touchAnalystSession(env.DB, {
1049
+ ...heartbeat,
1050
+ actorId: session.actor.id,
1051
+ allowedQueueIds: session.queueScope ?? null,
1052
+ claimTtlMinutes: integer(env.CLAIM_TTL_MINUTES, 15, 1, 1_440),
1053
+ });
1054
+ return json({
1055
+ schema_version: "1",
1056
+ profile,
1057
+ claim_renewed: result.claimRenewed,
1058
+ analysts: await listAnalystPresence(env.DB, 45, session.queueScope ?? null),
1059
+ });
1060
+ }
1061
+ return methodNotAllowed(["GET", "POST"]);
1062
+ }
1063
+
1064
+ async function handleAdminPresenceSocket(request: Request, env: Env): Promise<Response> {
1065
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
1066
+ if (request.headers.get("upgrade")?.toLowerCase() !== "websocket") {
1067
+ throw new ApiError(426, "websocket_upgrade_required", "A WebSocket upgrade is required.");
1068
+ }
1069
+ const session = await requireOperator(request, env, "readReports");
1070
+ await recordAnalystAuthentication(env.DB, session);
1071
+ const sessionId = new URL(request.url).searchParams.get("session_id") ?? "";
1072
+ parsePresenceHeartbeat({ session_id: sessionId, activity: "online", active_report_id: null });
1073
+ const headers = new Headers(request.headers);
1074
+ headers.set("x-safest-actor-id", session.actor.id);
1075
+ headers.set("x-safest-session-id", sessionId);
1076
+ headers.set("x-safest-queue-scope", canonicalJson(session.queueScope ?? null));
1077
+ return env.WORKSPACE_PRESENCE.getByName("default").fetch(new Request(request, { headers }));
1078
+ }
1079
+
1080
+ async function handleAdminReports(request: Request, env: Env): Promise<Response> {
1081
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
1082
+ const session = await requireOperator(request, env, "readReports");
1083
+ const url = new URL(request.url);
1084
+ const queueId = url.searchParams.get("queue_id")?.trim();
1085
+ const rawState = url.searchParams.get("state")?.trim();
1086
+ if (queueId && queueId.length > 100) throw new ApiError(400, "invalid_query", "queue_id is invalid.");
1087
+ if (rawState && !reportStates.has(rawState as ReportState)) throw new ApiError(400, "invalid_query", "state is invalid.");
1088
+ const cursorScope = `reports:${session.actor.id}:${queueId ?? "*"}:${rawState ?? "*"}`;
1089
+ const cursor = await decodeApiCursor<InboxCursor>(env.CONTEXT_SIGNING_SECRET, cursorScope, url.searchParams.get("cursor"));
1090
+ if (cursor && (!Number.isInteger(cursor.priority) || cursor.priority < 0 || cursor.priority > 100
1091
+ || typeof cursor.receivedAt !== "string" || cursor.receivedAt.length > 50
1092
+ || typeof cursor.id !== "string" || cursor.id.length > 100)) {
1093
+ throw new ApiError(400, "cursor_invalid", "cursor is invalid or does not belong to this query.");
1094
+ }
1095
+ const page = await listInboxReports(env.DB, {
1096
+ ...(queueId ? { queueId } : {}),
1097
+ allowedQueueIds: session.queueScope ?? null,
1098
+ ...(rawState ? { state: rawState as ReportState } : {}),
1099
+ cursor,
1100
+ limit: integer(url.searchParams.get("limit") ?? undefined, 50, 1, 100),
1101
+ });
1102
+ return json({
1103
+ schema_version: "1",
1104
+ reports: projectOperatorFields(session, "reports", page.reports),
1105
+ next_cursor: page.nextPosition
1106
+ ? await encodeApiCursor(env.CONTEXT_SIGNING_SECRET, cursorScope, page.nextPosition)
1107
+ : null,
1108
+ });
1109
+ }
1110
+
1111
+ async function handleAdminQueues(request: Request, env: Env): Promise<Response> {
1112
+ if (request.method === "GET") {
1113
+ const session = await requireOperator(request, env, "readReports");
1114
+ const includeArchived = session.permissions.manageQueues
1115
+ && new URL(request.url).searchParams.get("include_archived") === "true";
1116
+ const queues = await listQueues(env.DB, includeArchived);
1117
+ const visibleQueues = session.permissions.manageQueues || session.queueScope === null
1118
+ ? queues
1119
+ : queues.filter((queue) => session.queueScope?.includes(String(queue.id)));
1120
+ return json({ schema_version: "1", queues: visibleQueues });
1121
+ }
1122
+ if (request.method === "POST") {
1123
+ const session = await requireOperator(request, env, "manageQueues");
1124
+ const result = await createQueue(
1125
+ env.DB,
1126
+ parseQueueCreate(await readJsonObject(request, 64 * 1024)),
1127
+ session,
1128
+ mutationKey(request),
1129
+ );
1130
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
1131
+ }
1132
+ return methodNotAllowed(["GET", "POST"]);
1133
+ }
1134
+
1135
+ async function handleAdminRoutingAgent(request: Request, env: Env): Promise<Response> {
1136
+ if (request.method === "GET") {
1137
+ await requireOperator(request, env, "manageAi");
1138
+ return json({ schema_version: "1", configurations: await listRoutingAgents(env.DB) });
1139
+ }
1140
+ if (request.method === "POST") {
1141
+ const session = await requireOperator(request, env, "manageAi");
1142
+ const result = await publishRoutingAgent(
1143
+ env.DB,
1144
+ parseRoutingAgentConfiguration(await readJsonObject(request, 32 * 1024)),
1145
+ session,
1146
+ mutationKey(request),
1147
+ );
1148
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
1149
+ }
1150
+ return methodNotAllowed(["GET", "POST"]);
1151
+ }
1152
+
1153
+ async function handleAdminRoutingAgentTest(request: Request, env: Env): Promise<Response> {
1154
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
1155
+ await requireOperator(request, env, "manageAi");
1156
+ const agent = await activeRoutingAgent(env.DB);
1157
+ if (!agent) throw new ApiError(404, "routing_agent_not_found", "No published routing-agent version exists.");
1158
+ const body = await readJsonObject(request, 32 * 1024);
1159
+ if (!body.simulated_output || typeof body.simulated_output !== "object" || Array.isArray(body.simulated_output)) {
1160
+ throw new ApiError(400, "routing_agent_fixture_invalid", "simulated_output must be a routing-agent output object.");
1161
+ }
1162
+ const candidateIds = agent.candidateQueueIds;
1163
+ const validation = validateRoutingAgentOutput(body.simulated_output, new Set(candidateIds), agent.confidenceThreshold);
1164
+ const accepted = agent.enabled && agent.rolloutMode === "active" && !validation.needsHuman && validation.errors.length === 0;
1165
+ return json({
1166
+ schema_version: "1",
1167
+ test: {
1168
+ routing_agent_version_id: agent.id,
1169
+ candidate_queue_ids: candidateIds,
1170
+ validation,
1171
+ would_accept: accepted,
1172
+ authoritative: false,
1173
+ },
1174
+ }, { status: validation.errors.length ? 422 : 200 });
1175
+ }
1176
+
1177
+ async function handleAdminQueue(
1178
+ request: Request,
1179
+ env: Env,
1180
+ queueId: string,
1181
+ action?: "versions" | "policy-versions" | "pause" | "resume" | "health",
1182
+ ): Promise<Response> {
1183
+ if (action === "versions" || action === "policy-versions") {
1184
+ if (request.method === "GET") {
1185
+ await requireOperator(request, env, "manageQueues");
1186
+ const queue = await loadQueue(env.DB, queueId);
1187
+ if (!queue) throw new ApiError(404, "queue_not_found", "The queue does not exist.");
1188
+ return json({ schema_version: "1", versions: queue.versions });
1189
+ }
1190
+ if (request.method === "POST") {
1191
+ const session = await requireOperator(request, env, "manageQueues");
1192
+ const result = await publishQueuePolicy(
1193
+ env.DB,
1194
+ queueId,
1195
+ parseQueuePolicy(await readJsonObject(request, 64 * 1024)),
1196
+ session,
1197
+ mutationKey(request),
1198
+ );
1199
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
1200
+ }
1201
+ return methodNotAllowed(["GET", "POST"]);
1202
+ }
1203
+ if (action === "pause" || action === "resume") {
1204
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
1205
+ const session = await requireOperator(request, env, "manageQueues");
1206
+ if (action === "resume") {
1207
+ const ready = await env.DB.prepare(`
1208
+ SELECT q.id FROM queue_definitions q JOIN queue_versions v ON v.id = q.active_version_id
1209
+ WHERE q.id = ?1 AND q.status != 'archived' AND v.published_at IS NOT NULL AND v.retired_at IS NULL
1210
+ LIMIT 1
1211
+ `).bind(queueId).first();
1212
+ if (!ready) throw new ApiError(409, "queue_resume_readiness_failed", "The queue needs a current published policy before it can resume.");
1213
+ }
1214
+ const result = await updateQueue(env.DB, queueId, { status: action === "pause" ? "paused" : "active" }, session, mutationKey(request));
1215
+ return json({ schema_version: "1", ...result });
1216
+ }
1217
+ if (action === "health") {
1218
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
1219
+ const session = await requireOperator(request, env, "readOperations");
1220
+ assertOperatorQueueAccess(session, queueId);
1221
+ const queue = await loadQueue(env.DB, queueId);
1222
+ if (!queue) throw new ApiError(404, "queue_not_found", "The queue does not exist.");
1223
+ const [reports, runs, tasks, actions, circuits, budgets] = await Promise.all([
1224
+ env.DB.prepare(`SELECT COUNT(*) AS total, SUM(CASE WHEN state NOT IN ('resolved_by_human','resolved_by_ai','closed') THEN 1 ELSE 0 END) AS open, SUM(CASE WHEN due_at IS NOT NULL AND due_at < ?2 AND state NOT IN ('resolved_by_human','resolved_by_ai','closed') THEN 1 ELSE 0 END) AS overdue FROM reports WHERE queue_id = ?1`).bind(queueId, new Date().toISOString()).first(),
1225
+ env.DB.prepare(`SELECT COUNT(*) AS total, SUM(CASE WHEN state IN ('failed','repair_required') THEN 1 ELSE 0 END) AS unhealthy, SUM(CASE WHEN state IN ('created','dispatch_pending','queued','running','waiting','paused') THEN 1 ELSE 0 END) AS active FROM workflow_runs WHERE queue_id = ?1`).bind(queueId).first(),
1226
+ env.DB.prepare(`SELECT COUNT(*) AS open FROM human_tasks WHERE queue_id = ?1 AND state IN ('open','claimed')`).bind(queueId).first(),
1227
+ env.DB.prepare(`SELECT COUNT(*) AS pending FROM action_proposals WHERE report_id IN (SELECT id FROM reports WHERE queue_id = ?1) AND state IN ('proposed','approval_required','approved','queued','applying','failed','compensation_required')`).bind(queueId).first(),
1228
+ env.DB.prepare(`SELECT COUNT(*) AS open FROM connection_health WHERE state IN ('open_circuit','degraded')`).first(),
1229
+ env.DB.prepare(`SELECT COUNT(*) AS exhausted FROM budget_usage u JOIN budget_policies p ON p.id = u.policy_id WHERE p.scope_type = 'queue' AND p.scope_id = ?1 AND u.consumed_value + u.reserved_value >= p.limit_value`).bind(queueId).first(),
1230
+ ]);
1231
+ return json({ schema_version: "1", queue, health: { reports, workflowRuns: runs, tasks, actions, connectionCircuits: circuits, budgets } });
1232
+ }
1233
+ if (request.method === "GET") {
1234
+ const session = await requireOperator(request, env, "manageQueues");
1235
+ assertOperatorQueueAccess(session, queueId);
1236
+ const queue = await loadQueue(env.DB, queueId);
1237
+ if (!queue) throw new ApiError(404, "queue_not_found", "The queue does not exist.");
1238
+ return json({ schema_version: "1", queue });
1239
+ }
1240
+ if (request.method === "PATCH") {
1241
+ const session = await requireOperator(request, env, "manageQueues");
1242
+ const result = await updateQueue(
1243
+ env.DB,
1244
+ queueId,
1245
+ parseQueueUpdate(await readJsonObject(request, 16 * 1024)),
1246
+ session,
1247
+ mutationKey(request),
1248
+ );
1249
+ return json({ schema_version: "1", ...result });
1250
+ }
1251
+ return methodNotAllowed(["GET", "PATCH"]);
1252
+ }
1253
+
1254
+ function parseWorkflowAuthorityMode(value: unknown): WorkflowAuthorityMode {
1255
+ if (value === "off" || value === "shadow" || value === "assist" || value === "bounded_auto") return value;
1256
+ throw new ApiError(400, "invalid_workflow_authority", "authority_mode must be off, shadow, assist, or bounded_auto.");
1257
+ }
1258
+
1259
+ async function handleAdminConnections(request: Request, env: Env): Promise<Response> {
1260
+ if (request.method === "GET") {
1261
+ const session = await requireOperator(request, env, "manageConnections");
1262
+ const includeArchived = session.permissions.manageConnections
1263
+ && new URL(request.url).searchParams.get("include_archived") === "true";
1264
+ return json({ schema_version: "1", connections: await listConnections(env.DB, includeArchived) });
1265
+ }
1266
+ if (request.method === "POST") {
1267
+ const session = await requireOperator(request, env, "publishConnections");
1268
+ const result = await createConnection(
1269
+ env.DB,
1270
+ parseConnectionCreate(await readJsonObject(request, 128 * 1024)),
1271
+ session,
1272
+ mutationKey(request),
1273
+ );
1274
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
1275
+ }
1276
+ return methodNotAllowed(["GET", "POST"]);
1277
+ }
1278
+
1279
+ async function handleAdminConnection(
1280
+ request: Request,
1281
+ env: Env,
1282
+ connectionId: string,
1283
+ action?: "versions" | "validate" | "test" | "pause" | "resume" | "dependencies",
1284
+ ): Promise<Response> {
1285
+ if (action === "versions" && request.method === "POST") {
1286
+ const session = await requireOperator(request, env, "publishConnections");
1287
+ const result = await publishConnectionVersion(
1288
+ env.DB,
1289
+ connectionId,
1290
+ parseConnectionVersion(await readJsonObject(request, 64 * 1024)),
1291
+ session,
1292
+ mutationKey(request),
1293
+ );
1294
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
1295
+ }
1296
+ if (action === "validate") {
1297
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
1298
+ await requireOperator(request, env, "manageConnections");
1299
+ const validation = await validateConnectionDefinition(env.DB, connectionId);
1300
+ return json({ schema_version: "1", validation }, { status: validation.valid === true ? 200 : 422 });
1301
+ }
1302
+ if (action === "dependencies") {
1303
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
1304
+ await requireOperator(request, env, "manageConnections");
1305
+ return json({ schema_version: "1", dependencies: await connectionDependencies(env.DB, connectionId) });
1306
+ }
1307
+ if (action === "pause") {
1308
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
1309
+ const session = await requireOperator(request, env, "manageConnections");
1310
+ const result = await setConnectionStatus(env.DB, connectionId, "paused", session, mutationKey(request));
1311
+ return json({ schema_version: "1", ...result });
1312
+ }
1313
+ if (action === "test" || action === "resume") {
1314
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
1315
+ const session = await requireOperator(request, env, "manageConnections");
1316
+ const body = await readJsonObject(request, 128 * 1024);
1317
+ if (typeof body.path !== "string" || !body.path.startsWith("/") || body.path.length > 2_048) {
1318
+ throw new ApiError(400, "connection_test_path_required", "A safe absolute path is required for the connection probe.");
1319
+ }
1320
+ const method = typeof body.method === "string" ? body.method.toUpperCase() : "POST";
1321
+ const connection = await loadConnection(env.DB, connectionId);
1322
+ if (!connection) throw new ApiError(404, "connection_not_found", "The connection does not exist.");
1323
+ const definition = connection.definition as Record<string, unknown>;
1324
+ const connectionVersionId = typeof definition.active_version_id === "string" ? definition.active_version_id : null;
1325
+ if (!connectionVersionId) throw new ApiError(409, "connection_version_missing", "The connection has no active version.");
1326
+ let probe;
1327
+ try {
1328
+ probe = await executeConnectionJson(env, {
1329
+ connectionVersionId,
1330
+ operationId: `connection-probe:${connectionId}:${mutationKey(request)}`,
1331
+ attemptNumber: 1,
1332
+ method,
1333
+ path: body.path,
1334
+ body: body.body ?? {},
1335
+ allowPaused: true,
1336
+ headers: { "x-safest-purpose": "connection-health-probe" },
1337
+ });
1338
+ } catch (error) {
1339
+ if (error instanceof ConnectionExecutionError) {
1340
+ const status = ["connection_rate_limited", "connection_concurrency_exhausted"].includes(error.code) ? 429 : error.retryable ? 503 : 422;
1341
+ throw new ApiError(status, error.code, "The connection probe failed safely.", { retryable: error.retryable, retry_after_seconds: error.delaySeconds });
1342
+ }
1343
+ throw error;
1344
+ }
1345
+ const resumed = action === "resume"
1346
+ ? await setConnectionStatus(env.DB, connectionId, "active", session, mutationKey(request)) : null;
1347
+ return json({
1348
+ schema_version: "1",
1349
+ probe: { status: probe.status, data: probe.data, response_digest: probe.responseDigest, attempt_id: probe.connectionAttemptId },
1350
+ ...(resumed ? { connection: resumed } : {}),
1351
+ });
1352
+ }
1353
+ if (!action && request.method === "PATCH") {
1354
+ const session = await requireOperator(request, env, "manageConnections");
1355
+ const result = await updateConnectionDefinition(env.DB, connectionId, await readJsonObject(request, 16 * 1024), session, mutationKey(request));
1356
+ return json({ schema_version: "1", ...result });
1357
+ }
1358
+ if (request.method !== "GET") return methodNotAllowed(action === "versions" ? ["GET", "POST"] : action ? ["GET"] : ["GET", "PATCH"]);
1359
+ await requireOperator(request, env, "manageConnections");
1360
+ const connection = await loadConnection(env.DB, connectionId);
1361
+ if (!connection) throw new ApiError(404, "connection_not_found", "The connection does not exist.");
1362
+ if (action === "versions") return json({ schema_version: "1", versions: connection.versions });
1363
+ return json({ schema_version: "1", connection });
1364
+ }
1365
+
1366
+ async function handleAdminConnectionOAuthCallback(request: Request, env: Env): Promise<Response> {
1367
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
1368
+ const session = await requireOperator(request, env, "manageConnections");
1369
+ const url = new URL(request.url);
1370
+ const providerError = url.searchParams.get("error");
1371
+ if (providerError) {
1372
+ throw new ApiError(400, "oauth_authorization_denied", "The OAuth provider did not authorize the connection.", {
1373
+ provider_error: providerError.slice(0, 200),
1374
+ });
1375
+ }
1376
+ const state = url.searchParams.get("state") ?? "";
1377
+ const code = url.searchParams.get("code") ?? "";
1378
+ const result = await completeConnectionOAuth(env, state, code, session);
1379
+ return json({ schema_version: "1", ...result });
1380
+ }
1381
+
1382
+ async function handleAdminConnectionOAuth(
1383
+ request: Request,
1384
+ env: Env,
1385
+ connectionId: string,
1386
+ action: "start" | "revoke",
1387
+ ): Promise<Response> {
1388
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
1389
+ const session = await requireOperator(request, env, "manageConnections");
1390
+ const result = action === "start"
1391
+ ? await startConnectionOAuth(env, connectionId, session)
1392
+ : await revokeConnectionOAuth(env, connectionId, session);
1393
+ return json({ schema_version: "1", ...result });
1394
+ }
1395
+
1396
+ async function handleAdminComponents(request: Request, env: Env): Promise<Response> {
1397
+ if (request.method === "GET") {
1398
+ const session = await requireOperator(request, env, "manageComponents");
1399
+ const includeArchived = session.permissions.manageComponents
1400
+ && new URL(request.url).searchParams.get("include_archived") === "true";
1401
+ return json({ schema_version: "1", components: await listComponents(env.DB, includeArchived) });
1402
+ }
1403
+ if (request.method === "POST") {
1404
+ const session = await requireOperator(request, env, "publishComponents");
1405
+ const result = await createComponent(
1406
+ env.DB,
1407
+ parseComponentCreate(await readJsonObject(request, 256 * 1024)),
1408
+ session,
1409
+ mutationKey(request),
1410
+ );
1411
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
1412
+ }
1413
+ return methodNotAllowed(["GET", "POST"]);
1414
+ }
1415
+
1416
+ async function handleAdminComponent(
1417
+ request: Request,
1418
+ env: Env,
1419
+ componentId: string,
1420
+ action?: "versions" | "validate" | "test" | "pause" | "resume" | "dependencies",
1421
+ ): Promise<Response> {
1422
+ if (action === "versions" && request.method === "POST") {
1423
+ const session = await requireOperator(request, env, "publishComponents");
1424
+ const definition = await loadComponent(env.DB, componentId);
1425
+ if (!definition) throw new ApiError(404, "component_not_found", "The component does not exist.");
1426
+ const effectClass = (definition.definition as Record<string, unknown>).effect_class;
1427
+ const parsedEffect = effectClass === "side_effecting" ? "side_effecting" : "read_only";
1428
+ const result = await publishComponentVersion(
1429
+ env.DB,
1430
+ componentId,
1431
+ parseComponentVersion(await readJsonObject(request, 256 * 1024), parsedEffect),
1432
+ session,
1433
+ mutationKey(request),
1434
+ );
1435
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
1436
+ }
1437
+ if (action === "validate") {
1438
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
1439
+ await requireOperator(request, env, "manageComponents");
1440
+ const validation = await validateComponentDefinition(env.DB, componentId);
1441
+ return json({ schema_version: "1", validation }, { status: validation.valid === true ? 200 : 422 });
1442
+ }
1443
+ if (action === "test") {
1444
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
1445
+ const session = await requireOperator(request, env, "manageComponents");
1446
+ const result = await testComponentDefinition(env.DB, componentId, session);
1447
+ return json({ schema_version: "1", ...result }, { status: result.passed ? 200 : 422 });
1448
+ }
1449
+ if (action === "dependencies") {
1450
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
1451
+ await requireOperator(request, env, "manageComponents");
1452
+ return json({ schema_version: "1", dependencies: await componentDependencies(env.DB, componentId) });
1453
+ }
1454
+ if (action === "pause" || action === "resume") {
1455
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
1456
+ const session = await requireOperator(request, env, "manageComponents");
1457
+ if (action === "resume") {
1458
+ const validation = await validateComponentDefinition(env.DB, componentId);
1459
+ if (validation.valid !== true) throw new ApiError(409, "component_validation_failed", "The component cannot resume until validation passes.", { validation });
1460
+ }
1461
+ const result = await setComponentStatus(env.DB, componentId, action === "pause" ? "paused" : "active", session, mutationKey(request));
1462
+ return json({ schema_version: "1", ...result });
1463
+ }
1464
+ if (!action && request.method === "PATCH") {
1465
+ const session = await requireOperator(request, env, "manageComponents");
1466
+ const result = await updateComponentDefinition(env.DB, componentId, await readJsonObject(request, 16 * 1024), session, mutationKey(request));
1467
+ return json({ schema_version: "1", ...result });
1468
+ }
1469
+ if (request.method !== "GET") return methodNotAllowed(action === "versions" ? ["GET", "POST"] : action ? ["GET"] : ["GET", "PATCH"]);
1470
+ await requireOperator(request, env, "manageComponents");
1471
+ const component = await loadComponent(env.DB, componentId);
1472
+ if (!component) throw new ApiError(404, "component_not_found", "The component does not exist.");
1473
+ if (action === "versions") return json({ schema_version: "1", versions: component.versions });
1474
+ return json({ schema_version: "1", component });
1475
+ }
1476
+
1477
+ async function handleAdminWorkflows(request: Request, env: Env): Promise<Response> {
1478
+ if (request.method === "GET") {
1479
+ const session = await requireOperator(request, env, "manageWorkflows");
1480
+ const includeArchived = session.permissions.manageWorkflows
1481
+ && new URL(request.url).searchParams.get("include_archived") === "true";
1482
+ return json({ schema_version: "1", workflows: await listWorkflowDefinitions(env.DB, includeArchived) });
1483
+ }
1484
+ if (request.method === "POST") {
1485
+ const session = await requireOperator(request, env, "manageWorkflows");
1486
+ const body = await readJsonObject(request, 512 * 1024);
1487
+ const definition = parseWorkflowDefinition({
1488
+ key: body.key,
1489
+ name: body.name,
1490
+ description: body.description,
1491
+ purpose: body.purpose,
1492
+ });
1493
+ const result = await createWorkflowDefinition(
1494
+ env.DB,
1495
+ definition,
1496
+ parseWorkflowGraph(body.graph),
1497
+ session,
1498
+ mutationKey(request),
1499
+ );
1500
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
1501
+ }
1502
+ return methodNotAllowed(["GET", "POST"]);
1503
+ }
1504
+
1505
+ async function handleAdminWorkflow(
1506
+ request: Request,
1507
+ env: Env,
1508
+ ctx: ExecutionContext,
1509
+ workflowId: string,
1510
+ action?: "draft" | "validate" | "publish" | "versions" | "fixtures" | "test" | "test-runs" | "shadow-runs",
1511
+ ): Promise<Response> {
1512
+ if (!action) {
1513
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
1514
+ await requireOperator(request, env, "manageWorkflows");
1515
+ const workflow = await loadWorkflowDefinition(env.DB, workflowId);
1516
+ if (!workflow) throw new ApiError(404, "workflow_not_found", "The workflow does not exist.");
1517
+ return json({ schema_version: "1", workflow });
1518
+ }
1519
+ if (action === "draft") {
1520
+ if (request.method === "GET") {
1521
+ await requireOperator(request, env, "manageWorkflows");
1522
+ const workflow = await loadWorkflowDefinition(env.DB, workflowId);
1523
+ if (!workflow) throw new ApiError(404, "workflow_not_found", "The workflow does not exist.");
1524
+ const draft = workflow.draft as Record<string, unknown> | null;
1525
+ const revision = Number(draft?.revision);
1526
+ return json({ schema_version: "1", draft }, {
1527
+ ...(Number.isInteger(revision) && revision > 0 ? { headers: { etag: `W/\"${revision}\"` } } : {}),
1528
+ });
1529
+ }
1530
+ if (request.method === "PUT" || request.method === "PATCH") {
1531
+ const session = await requireOperator(request, env, "manageWorkflows");
1532
+ const body = await readJsonObject(request, 512 * 1024);
1533
+ const headerRevision = request.headers.get("if-match")?.replace(/^W\/|"/gu, "");
1534
+ const expectedRevision = body.expected_revision ?? (headerRevision ? Number(headerRevision) : undefined);
1535
+ if (!Number.isInteger(expectedRevision) || Number(expectedRevision) < 1) {
1536
+ throw new ApiError(400, "invalid_workflow_revision", "expected_revision must be a positive integer.");
1537
+ }
1538
+ const result = await replaceWorkflowDraft(
1539
+ env.DB,
1540
+ workflowId,
1541
+ parseWorkflowGraph(body.graph),
1542
+ Number(expectedRevision),
1543
+ session,
1544
+ );
1545
+ return json({ schema_version: "1", ...result });
1546
+ }
1547
+ return methodNotAllowed(["GET", "PUT", "PATCH"]);
1548
+ }
1549
+ if (action === "validate") {
1550
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
1551
+ await requireOperator(request, env, "manageWorkflows");
1552
+ const body = await readJsonObject(request, 8 * 1024);
1553
+ const result = await validateWorkflowDraft(env.DB, workflowId, parseWorkflowAuthorityMode(body.authority_mode ?? "assist"));
1554
+ return json({ schema_version: "1", ...result }, { status: result.validation.valid ? 200 : 422 });
1555
+ }
1556
+ if (action === "publish") {
1557
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
1558
+ const session = await requireOperator(request, env, "publishWorkflows");
1559
+ const result = await publishWorkflowDraft(
1560
+ env.DB,
1561
+ env.WORKFLOW_ARTIFACTS,
1562
+ workflowId,
1563
+ parseWorkflowPublish(await readJsonObject(request, 64 * 1024)),
1564
+ session,
1565
+ mutationKey(request),
1566
+ "2026-08-29",
1567
+ );
1568
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
1569
+ }
1570
+ if (action === "fixtures") {
1571
+ if (request.method === "GET") {
1572
+ await requireOperator(request, env, "manageWorkflows");
1573
+ return json({ schema_version: "1", fixtures: await listWorkflowFixtures(env.DB, workflowId) });
1574
+ }
1575
+ if (request.method === "POST") {
1576
+ const session = await requireOperator(request, env, "manageWorkflows");
1577
+ const body = await readJsonObject(request, 512 * 1024);
1578
+ if (typeof body.name !== "string") throw new ApiError(400, "workflow_fixture_invalid", "name is required.");
1579
+ const objectField = (value: unknown, field: string): JsonObject => {
1580
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new ApiError(400, "workflow_fixture_invalid", `${field} must be an object.`);
1581
+ return value as JsonObject;
1582
+ };
1583
+ const result = await createWorkflowFixture(env.DB, workflowId, {
1584
+ name: body.name,
1585
+ input: objectField(body.input ?? {}, "input"),
1586
+ componentResults: objectField(body.component_results ?? {}, "component_results"),
1587
+ humanResponses: objectField(body.human_responses ?? {}, "human_responses"),
1588
+ expected: objectField(body.expected ?? {}, "expected"),
1589
+ failureInjection: objectField(body.failure_injection ?? {}, "failure_injection"),
1590
+ }, session, mutationKey(request));
1591
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
1592
+ }
1593
+ return methodNotAllowed(["GET", "POST"]);
1594
+ }
1595
+ if (action === "test" || action === "test-runs") {
1596
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
1597
+ const session = await requireOperator(request, env, "manageWorkflows");
1598
+ const body = await readJsonObject(request, 16 * 1024);
1599
+ if (typeof body.fixture_id !== "string" || !body.fixture_id.trim()) throw new ApiError(400, "workflow_fixture_required", "fixture_id is required.");
1600
+ const result = await runWorkflowFixtureTest(env.DB, workflowId, body.fixture_id.trim(), session);
1601
+ return json({ schema_version: "1", ...result }, { status: result.status === "passed" ? 200 : 422 });
1602
+ }
1603
+ if (action === "shadow-runs") {
1604
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
1605
+ const session = await requireOperator(request, env, "runShadow");
1606
+ const body = await readJsonObject(request, 16 * 1024);
1607
+ const reportId = boundedBodyString(body.report_id, "report_id", 100);
1608
+ await assertOperatorReportAccess(env.DB, session, reportId);
1609
+ const selected = await env.DB.prepare(`
1610
+ SELECT w.id FROM reports r JOIN queue_workflow_pins p
1611
+ ON p.queue_id = r.queue_id AND p.mode = 'shadow' AND p.deactivated_at IS NULL
1612
+ JOIN workflow_versions v ON v.id = p.workflow_version_id
1613
+ JOIN workflow_definitions w ON w.id = v.workflow_id
1614
+ WHERE r.id = ?1 AND w.id = ?2 LIMIT 1
1615
+ `).bind(reportId, workflowId).first();
1616
+ if (!selected) throw new ApiError(409, "workflow_shadow_pin_mismatch", "This workflow is not the report queue's active shadow pin.");
1617
+ const result = await createPinnedWorkflowRun(env.DB, reportId, "shadow", session, mutationKey(request));
1618
+ ctx.waitUntil(env.CONTROL_JOBS.send(result.job, { contentType: "json" }).catch((error: unknown) => {
1619
+ console.error("shadow workflow dispatch enqueue failed; reconciliation will retry", { runId: result.runId, errorType: error instanceof Error ? error.name : typeof error });
1620
+ }));
1621
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 202 });
1622
+ }
1623
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
1624
+ await requireOperator(request, env, "manageWorkflows");
1625
+ const workflow = await loadWorkflowDefinition(env.DB, workflowId);
1626
+ if (!workflow) throw new ApiError(404, "workflow_not_found", "The workflow does not exist.");
1627
+ return json({ schema_version: "1", versions: workflow.versions });
1628
+ }
1629
+
1630
+ async function handleAdminWorkflowFixture(
1631
+ request: Request,
1632
+ env: Env,
1633
+ workflowId: string,
1634
+ fixtureId: string,
1635
+ ): Promise<Response> {
1636
+ const session = await requireOperator(request, env, "manageWorkflows");
1637
+ if (request.method === "DELETE") {
1638
+ const result = await deleteWorkflowFixture(env.DB, workflowId, fixtureId, session, mutationKey(request));
1639
+ return json({ schema_version: "1", ...result });
1640
+ }
1641
+ if (request.method !== "PATCH") return methodNotAllowed(["PATCH", "DELETE"]);
1642
+ const fixtures = await listWorkflowFixtures(env.DB, workflowId);
1643
+ const current = fixtures.find((fixture) => fixture.id === fixtureId);
1644
+ if (!current) throw new ApiError(404, "workflow_fixture_not_found", "The workflow fixture does not exist.");
1645
+ const body = await readJsonObject(request, 512 * 1024);
1646
+ const objectField = (value: unknown, field: string): JsonObject => {
1647
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new ApiError(400, "workflow_fixture_invalid", `${field} must be an object.`);
1648
+ return value as JsonObject;
1649
+ };
1650
+ const name = body.name ?? current.name;
1651
+ if (typeof name !== "string") throw new ApiError(400, "workflow_fixture_invalid", "name must be a string.");
1652
+ const result = await updateWorkflowFixture(env.DB, workflowId, fixtureId, {
1653
+ name,
1654
+ input: objectField(body.input ?? current.input, "input"),
1655
+ componentResults: objectField(body.component_results ?? current.componentResults, "component_results"),
1656
+ humanResponses: objectField(body.human_responses ?? current.humanResponses, "human_responses"),
1657
+ expected: objectField(body.expected ?? current.expected, "expected"),
1658
+ failureInjection: objectField(body.failure_injection ?? current.failureInjection, "failure_injection"),
1659
+ }, session, mutationKey(request));
1660
+ return json({ schema_version: "1", ...result });
1661
+ }
1662
+
1663
+ async function handleAdminWorkflowTestRun(
1664
+ request: Request,
1665
+ env: Env,
1666
+ workflowId: string,
1667
+ testRunId: string,
1668
+ ): Promise<Response> {
1669
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
1670
+ await requireOperator(request, env, "manageWorkflows");
1671
+ const testRun = await loadWorkflowTestRun(env.DB, workflowId, testRunId);
1672
+ if (!testRun) throw new ApiError(404, "workflow_test_run_not_found", "The workflow test run does not exist.");
1673
+ return json({ schema_version: "1", test_run: testRun });
1674
+ }
1675
+
1676
+ async function handleAdminWorkflowVersion(
1677
+ request: Request,
1678
+ env: Env,
1679
+ workflowId: string,
1680
+ versionId: string,
1681
+ action?: "diff" | "retire",
1682
+ ): Promise<Response> {
1683
+ if (!action) {
1684
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
1685
+ await requireOperator(request, env, "manageWorkflows");
1686
+ const version = await loadWorkflowVersion(env.DB, workflowId, versionId);
1687
+ if (!version) throw new ApiError(404, "workflow_version_not_found", "The workflow version does not exist.");
1688
+ return json({ schema_version: "1", version });
1689
+ }
1690
+ if (action === "diff") {
1691
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
1692
+ await requireOperator(request, env, "manageWorkflows");
1693
+ const against = new URL(request.url).searchParams.get("against") ?? undefined;
1694
+ return json({ schema_version: "1", diff: await diffWorkflowVersion(env.DB, workflowId, versionId, against) });
1695
+ }
1696
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
1697
+ const session = await requireOperator(request, env, "publishWorkflows");
1698
+ const result = await retireWorkflowVersion(env.DB, workflowId, versionId, session, mutationKey(request));
1699
+ return json({ schema_version: "1", ...result });
1700
+ }
1701
+
1702
+ async function handleAdminQueueWorkflowPin(request: Request, env: Env, queueId: string): Promise<Response> {
1703
+ if (request.method === "GET") {
1704
+ const session = await requireOperator(request, env, "manageWorkflowPins");
1705
+ assertOperatorQueueAccess(session, queueId);
1706
+ return json({ schema_version: "1", pins: await loadQueueWorkflowPins(env.DB, queueId) });
1707
+ }
1708
+ if (request.method === "PUT") {
1709
+ const session = await requireOperator(request, env, "manageWorkflowPins");
1710
+ const body = await readJsonObject(request, 16 * 1024);
1711
+ if (typeof body.workflow_version_id !== "string" || !body.workflow_version_id.trim()) {
1712
+ throw new ApiError(400, "workflow_version_required", "workflow_version_id is required.");
1713
+ }
1714
+ if (body.mode !== "shadow" && body.mode !== "primary") {
1715
+ throw new ApiError(400, "workflow_pin_mode_invalid", "mode must be shadow or primary.");
1716
+ }
1717
+ if (body.rollback_of_pin_id !== undefined && typeof body.rollback_of_pin_id !== "string") {
1718
+ throw new ApiError(400, "workflow_pin_invalid", "rollback_of_pin_id must be a string when supplied.");
1719
+ }
1720
+ const result = await pinQueueWorkflow(env.DB, queueId, {
1721
+ workflowVersionId: body.workflow_version_id.trim(),
1722
+ mode: body.mode,
1723
+ ...(typeof body.rollback_of_pin_id === "string" ? { rollbackOfPinId: body.rollback_of_pin_id } : {}),
1724
+ }, session, mutationKey(request));
1725
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
1726
+ }
1727
+ return methodNotAllowed(["GET", "PUT"]);
1728
+ }
1729
+
1730
+ async function handleAdminReportWorkflowRuns(
1731
+ request: Request,
1732
+ env: Env,
1733
+ ctx: ExecutionContext,
1734
+ reportId: string,
1735
+ ): Promise<Response> {
1736
+ if (request.method === "GET") {
1737
+ const session = await requireOperator(request, env, "readRuns");
1738
+ await assertOperatorReportAccess(env.DB, session, reportId);
1739
+ return json({ schema_version: "1", runs: await listWorkflowRuns(env.DB, { reportId, limit: 100 }) });
1740
+ }
1741
+ if (request.method === "POST") {
1742
+ const body = await readJsonObject(request, 8 * 1024);
1743
+ if (body.mode !== "shadow" && body.mode !== "primary") {
1744
+ throw new ApiError(400, "workflow_run_mode_invalid", "mode must be shadow or primary.");
1745
+ }
1746
+ const session = await requireOperator(request, env, body.mode === "shadow" ? "runShadow" : "operateRuns");
1747
+ await assertOperatorReportAccess(env.DB, session, reportId);
1748
+ const result = await createPinnedWorkflowRun(env.DB, reportId, body.mode, session, mutationKey(request));
1749
+ ctx.waitUntil(env.CONTROL_JOBS.send(result.job, { contentType: "json" }).catch((error: unknown) => {
1750
+ console.error("workflow dispatch enqueue failed; reconciliation will retry", {
1751
+ runId: result.runId,
1752
+ errorType: error instanceof Error ? error.name : typeof error,
1753
+ });
1754
+ }));
1755
+ return json({
1756
+ schema_version: "1",
1757
+ run_id: result.runId,
1758
+ workflow_version_id: result.workflowVersionId,
1759
+ idempotent_replay: result.idempotentReplay,
1760
+ }, { status: result.idempotentReplay ? 200 : 202 });
1761
+ }
1762
+ return methodNotAllowed(["GET", "POST"]);
1763
+ }
1764
+
1765
+ async function handleAdminWorkflowRuns(request: Request, env: Env): Promise<Response> {
1766
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
1767
+ const session = await requireOperator(request, env, "readRuns");
1768
+ const url = new URL(request.url);
1769
+ const state = url.searchParams.get("state")?.trim() || undefined;
1770
+ if (state && ![
1771
+ "created", "dispatch_pending", "queued", "running", "waiting", "paused",
1772
+ "succeeded", "failed", "terminated", "superseded", "repair_required",
1773
+ ].includes(state)) throw new ApiError(400, "workflow_run_state_invalid", "state is invalid.");
1774
+ return json({
1775
+ schema_version: "1",
1776
+ runs: await listWorkflowRuns(env.DB, {
1777
+ ...(state ? { state } : {}),
1778
+ allowedQueueIds: session.queueScope ?? null,
1779
+ limit: integer(url.searchParams.get("limit") ?? undefined, 50, 1, 100),
1780
+ }),
1781
+ });
1782
+ }
1783
+
1784
+ async function handleAdminAssistantSessions(request: Request, env: Env): Promise<Response> {
1785
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
1786
+ const session = await requireOperator(request, env, "useAssistant");
1787
+ const result = await createAssistantSession(env.DB, session, mutationKey(request));
1788
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
1789
+ }
1790
+
1791
+ async function handleAdminPrompts(request: Request, env: Env): Promise<Response> {
1792
+ if (request.method === "GET") {
1793
+ await requireOperator(request, env, "readAi");
1794
+ return json({ schema_version: "1", prompts: await listPrompts(env.DB) });
1795
+ }
1796
+ if (request.method === "POST") {
1797
+ const session = await requireOperator(request, env, "manageAi");
1798
+ const result = await publishPrompt(
1799
+ env.DB,
1800
+ parsePromptPublication(await readJsonObject(request, 64 * 1024)),
1801
+ session,
1802
+ mutationKey(request),
1803
+ );
1804
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
1805
+ }
1806
+ return methodNotAllowed(["GET", "POST"]);
1807
+ }
1808
+
1809
+ async function handleAdminModelAliases(request: Request, env: Env): Promise<Response> {
1810
+ if (request.method === "GET") {
1811
+ await requireOperator(request, env, "readAi");
1812
+ return json({ schema_version: "1", model_aliases: await listModelAliases(env.DB) });
1813
+ }
1814
+ if (request.method === "POST") {
1815
+ const session = await requireOperator(request, env, "manageAi");
1816
+ const result = await createModelAlias(
1817
+ env.DB,
1818
+ parseModelAliasCreate(await readJsonObject(request, 64 * 1024)),
1819
+ session,
1820
+ mutationKey(request),
1821
+ );
1822
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
1823
+ }
1824
+ return methodNotAllowed(["GET", "POST"]);
1825
+ }
1826
+
1827
+ async function handleAdminModelAliasVersion(request: Request, env: Env, aliasId: string): Promise<Response> {
1828
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
1829
+ const session = await requireOperator(request, env, "manageAi");
1830
+ const result = await publishModelAliasVersion(
1831
+ env.DB,
1832
+ aliasId,
1833
+ parseModelAliasVersion(await readJsonObject(request, 64 * 1024)),
1834
+ session,
1835
+ mutationKey(request),
1836
+ );
1837
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
1838
+ }
1839
+
1840
+ async function handleAdminAgents(request: Request, env: Env): Promise<Response> {
1841
+ if (request.method === "GET") {
1842
+ await requireOperator(request, env, "readAi");
1843
+ return json({ schema_version: "1", agents: await listAgents(env.DB) });
1844
+ }
1845
+ if (request.method === "POST") {
1846
+ const session = await requireOperator(request, env, "manageAi");
1847
+ const result = await createAgent(
1848
+ env.DB,
1849
+ parseAgentCreate(await readJsonObject(request, 128 * 1024)),
1850
+ session,
1851
+ mutationKey(request),
1852
+ );
1853
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
1854
+ }
1855
+ return methodNotAllowed(["GET", "POST"]);
1856
+ }
1857
+
1858
+ async function handleAdminAgentVersion(request: Request, env: Env, agentId: string): Promise<Response> {
1859
+ if (request.method === "GET") {
1860
+ await requireOperator(request, env, "manageAi");
1861
+ const versions = await listAgentVersions(env.DB, agentId);
1862
+ if (!versions.length && !await env.DB.prepare(`SELECT id FROM agent_definitions WHERE id = ?1`).bind(agentId).first()) {
1863
+ throw new ApiError(404, "agent_not_found", "The agent does not exist.");
1864
+ }
1865
+ return json({ schema_version: "1", versions });
1866
+ }
1867
+ if (request.method === "POST") {
1868
+ const session = await requireOperator(request, env, "manageAi");
1869
+ const result = await publishAgentVersion(
1870
+ env.DB,
1871
+ agentId,
1872
+ parseAgentVersion(await readJsonObject(request, 128 * 1024)),
1873
+ session,
1874
+ mutationKey(request),
1875
+ );
1876
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
1877
+ }
1878
+ return methodNotAllowed(["GET", "POST"]);
1879
+ }
1880
+
1881
+ async function handleAdminAgentTest(request: Request, env: Env, agentId: string): Promise<Response> {
1882
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
1883
+ await requireOperator(request, env, "manageAi");
1884
+ const body = await readJsonObject(request, 256 * 1024);
1885
+ if (!body.input || typeof body.input !== "object" || Array.isArray(body.input)) {
1886
+ throw new ApiError(400, "agent_test_input_invalid", "input must be an object.");
1887
+ }
1888
+ const active = await env.DB.prepare(`
1889
+ SELECT v.id, v.input_schema_json AS inputSchemaJson, v.tool_manifest_json AS toolManifestJson,
1890
+ v.maximum_turns AS maximumTurns, v.model_alias_version_id AS modelAliasVersionId,
1891
+ v.prompt_version_id AS promptVersionId
1892
+ FROM agent_definitions a JOIN agent_versions v ON v.id = a.active_version_id
1893
+ WHERE a.id = ?1 AND a.status = 'active' AND v.retired_at IS NULL LIMIT 1
1894
+ `).bind(agentId).first<Record<string, unknown>>();
1895
+ if (!active) throw new ApiError(404, "agent_not_found", "The active agent version does not exist.");
1896
+ let schema: JsonObject = {};
1897
+ let tools: unknown = [];
1898
+ try {
1899
+ schema = JSON.parse(String(active.inputSchemaJson)) as JsonObject;
1900
+ tools = JSON.parse(String(active.toolManifestJson));
1901
+ } catch {
1902
+ throw new ApiError(409, "agent_version_invalid", "The active agent version contains invalid stored configuration.");
1903
+ }
1904
+ const errors = validateJsonSchemaValue(body.input as JsonValue, schema);
1905
+ const result = {
1906
+ mode: "schema_and_authority_dry_run",
1907
+ status: errors.length ? "failed" : "passed",
1908
+ agentVersionId: active.id,
1909
+ modelAliasVersionId: active.modelAliasVersionId,
1910
+ promptVersionId: active.promptVersionId,
1911
+ maximumTurns: active.maximumTurns,
1912
+ allowedTools: tools,
1913
+ validationErrors: errors,
1914
+ externalCallsExecuted: false,
1915
+ };
1916
+ return json({ schema_version: "1", test: result }, { status: errors.length ? 422 : 200 });
1917
+ }
1918
+
1919
+ async function handleAdminAiRuns(request: Request, env: Env): Promise<Response> {
1920
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
1921
+ const session = await requireOperator(request, env, "readAi");
1922
+ const url = new URL(request.url);
1923
+ const status = url.searchParams.get("status")?.trim() || undefined;
1924
+ if (status && !["running", "succeeded", "invalid", "failed", "escalated", "timed_out"].includes(status)) {
1925
+ throw new ApiError(400, "ai_run_status_invalid", "status is invalid.");
1926
+ }
1927
+ return json({
1928
+ schema_version: "1",
1929
+ runs: await listAiRuns(env.DB, {
1930
+ allowedQueueIds: session.queueScope ?? null,
1931
+ ...(status ? { status } : {}),
1932
+ limit: integer(url.searchParams.get("limit") ?? undefined, 50, 1, 100),
1933
+ }),
1934
+ });
1935
+ }
1936
+
1937
+ async function handleAdminAiRun(request: Request, env: Env, runId: string): Promise<Response> {
1938
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
1939
+ const session = await requireOperator(request, env, "readAi");
1940
+ const run = await loadAiRun(env.DB, runId);
1941
+ if (!run) throw new ApiError(404, "ai_run_not_found", "The AI run does not exist.");
1942
+ await assertOperatorReportAccess(env.DB, session, String(run.reportId));
1943
+ return json({ schema_version: "1", run: projectOperatorFields(session, "ai_run", run) });
1944
+ }
1945
+
1946
+ async function handleAdminAiUsage(request: Request, env: Env): Promise<Response> {
1947
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
1948
+ await requireOperator(request, env, "readAi");
1949
+ const days = integer(new URL(request.url).searchParams.get("days") ?? undefined, 30, 1, 366);
1950
+ return json({ schema_version: "1", usage: await loadAiUsage(env.DB, days) });
1951
+ }
1952
+
1953
+ async function handleAdminBudgets(request: Request, env: Env): Promise<Response> {
1954
+ if (request.method === "GET") {
1955
+ await requireOperator(request, env, "readOperations");
1956
+ return json({ schema_version: "1", budgets: await listBudgets(env.DB) });
1957
+ }
1958
+ if (request.method === "POST") {
1959
+ const session = await requireOperator(request, env, "manageBudgets");
1960
+ const result = await publishBudget(
1961
+ env.DB,
1962
+ parseBudgetPublication(await readJsonObject(request, 32 * 1024)),
1963
+ session,
1964
+ mutationKey(request),
1965
+ );
1966
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
1967
+ }
1968
+ return methodNotAllowed(["GET", "POST"]);
1969
+ }
1970
+
1971
+ async function handleAdminConfigurations(
1972
+ request: Request,
1973
+ env: Env,
1974
+ type: ConfigurationType,
1975
+ ): Promise<Response> {
1976
+ if (request.method === "GET") {
1977
+ await requireOperator(request, env, "manageConfiguration");
1978
+ return json({ schema_version: "1", configurations: await listConfigurations(env.DB, type) });
1979
+ }
1980
+ if (request.method === "POST") {
1981
+ const session = await requireOperator(request, env, "manageConfiguration");
1982
+ const body = await readJsonObject(request, 512 * 1024);
1983
+ const key = type === "routing_rules" ? "default" : typeof body.key === "string" ? body.key : "";
1984
+ const result = await createConfigurationDraft(env.DB, type, key, body.content, session, mutationKey(request));
1985
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
1986
+ }
1987
+ return methodNotAllowed(["GET", "POST"]);
1988
+ }
1989
+
1990
+ async function handleAdminConfiguration(
1991
+ request: Request,
1992
+ env: Env,
1993
+ type: ConfigurationType,
1994
+ key: string,
1995
+ action: "draft" | "validate" | "publish" | "versions" | "render",
1996
+ ): Promise<Response> {
1997
+ if (action === "draft") {
1998
+ if (request.method === "GET") {
1999
+ await requireOperator(request, env, "manageConfiguration");
2000
+ const configuration = await loadConfiguration(env.DB, type, key);
2001
+ if (!configuration) throw new ApiError(404, "configuration_not_found", "The configuration does not exist.");
2002
+ const draft = configuration.draft as Record<string, unknown> | null | undefined;
2003
+ const revision = Number(draft?.revision);
2004
+ return json({ schema_version: "1", ...configuration }, {
2005
+ ...(Number.isInteger(revision) && revision > 0 ? { headers: { etag: `W/\"${revision}\"` } } : {}),
2006
+ });
2007
+ }
2008
+ if (request.method === "PATCH" || request.method === "PUT") {
2009
+ const session = await requireOperator(request, env, "manageConfiguration");
2010
+ const body = await readJsonObject(request, 512 * 1024);
2011
+ const headerRevision = request.headers.get("if-match")?.replace(/^W\/|"/gu, "");
2012
+ const expectedRevision = body.expected_revision ?? (headerRevision ? Number(headerRevision) : undefined);
2013
+ if (!Number.isInteger(expectedRevision) || Number(expectedRevision) < 1) throw new ApiError(400, "configuration_revision_invalid", "If-Match or expected_revision must contain a positive integer.");
2014
+ const result = await updateConfigurationDraft(env.DB, type, key, body.content, Number(expectedRevision), session);
2015
+ return json({ schema_version: "1", ...result });
2016
+ }
2017
+ return methodNotAllowed(["GET", "PATCH", "PUT"]);
2018
+ }
2019
+ if (action === "validate") {
2020
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2021
+ await requireOperator(request, env, "manageConfiguration");
2022
+ const validation = await validateStoredConfiguration(env.DB, type, key);
2023
+ return json({ schema_version: "1", validation }, { status: validation.valid ? 200 : 422 });
2024
+ }
2025
+ if (action === "publish") {
2026
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2027
+ const session = await requireOperator(request, env, "manageConfiguration");
2028
+ const body = await readJsonObject(request, 16 * 1024);
2029
+ if (!Number.isInteger(body.expected_revision) || Number(body.expected_revision) < 1) throw new ApiError(400, "configuration_revision_invalid", "expected_revision must be a positive integer.");
2030
+ const result = await publishConfiguration(env.DB, type, key, Number(body.expected_revision), session, mutationKey(request));
2031
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
2032
+ }
2033
+ if (action === "render") {
2034
+ if (type !== "template" || request.method !== "POST") return methodNotAllowed(["POST"]);
2035
+ await requireOperator(request, env, "manageConfiguration");
2036
+ const configuration = await loadConfiguration(env.DB, type, key);
2037
+ const draft = configuration?.draft as Record<string, unknown> | null | undefined;
2038
+ if (!draft) throw new ApiError(404, "template_draft_not_found", "The template draft does not exist.");
2039
+ const body = await readJsonObject(request, 64 * 1024);
2040
+ return json({ schema_version: "1", preview: renderTemplatePreview(draft.content, body.variables ?? {}) });
2041
+ }
2042
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2043
+ await requireOperator(request, env, "manageConfiguration");
2044
+ const configuration = await loadConfiguration(env.DB, type, key);
2045
+ if (!configuration) throw new ApiError(404, "configuration_not_found", "The configuration does not exist.");
2046
+ return json({ schema_version: "1", versions: configuration.versions });
2047
+ }
2048
+
2049
+ async function handleAdminAssistantSession(
2050
+ request: Request,
2051
+ env: Env,
2052
+ sessionId: string,
2053
+ messagesRoute: boolean,
2054
+ ): Promise<Response> {
2055
+ const session = await requireOperator(request, env, "useAssistant");
2056
+ if (messagesRoute) {
2057
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2058
+ const body = await readJsonObject(request, 16 * 1024);
2059
+ if (typeof body.body !== "string") throw new ApiError(400, "assistant_message_invalid", "body is required.");
2060
+ if (body.report_reference !== undefined && typeof body.report_reference !== "string") throw new ApiError(400, "assistant_report_reference_invalid", "report_reference must be a string.");
2061
+ if (body.component_version_id !== undefined && typeof body.component_version_id !== "string") {
2062
+ throw new ApiError(400, "assistant_component_version_invalid", "component_version_id must be a string.");
2063
+ }
2064
+ if (body.component_input !== undefined && (!body.component_input || typeof body.component_input !== "object" || Array.isArray(body.component_input))) {
2065
+ throw new ApiError(400, "assistant_component_input_invalid", "component_input must be an object.");
2066
+ }
2067
+ if ((body.component_version_id === undefined) !== (body.component_input === undefined)) {
2068
+ throw new ApiError(400, "assistant_enrichment_request_incomplete", "component_version_id and component_input must be supplied together.");
2069
+ }
2070
+ const hasDraft = body.draft_type !== undefined || body.draft_content !== undefined || body.draft_title !== undefined || body.draft_target_id !== undefined;
2071
+ if (hasDraft) {
2072
+ if (!["workflow", "form", "policy", "template", "routing_rules"].includes(String(body.draft_type))
2073
+ || typeof body.draft_title !== "string"
2074
+ || !body.draft_content || typeof body.draft_content !== "object" || Array.isArray(body.draft_content)
2075
+ || (body.draft_target_id !== undefined && typeof body.draft_target_id !== "string")) {
2076
+ throw new ApiError(400, "assistant_draft_request_invalid", "A valid draft_type, draft_title, draft_content, and optional draft_target_id are required.");
2077
+ }
2078
+ if (body.component_version_id !== undefined) throw new ApiError(400, "assistant_tool_request_ambiguous", "Request either an enrichment or a configuration draft, not both.");
2079
+ }
2080
+ const result = await sendAssistantMessage(env, sessionId, {
2081
+ body: body.body,
2082
+ ...(typeof body.report_reference === "string" ? { reportReference: body.report_reference } : {}),
2083
+ ...(typeof body.component_version_id === "string" ? {
2084
+ componentVersionId: body.component_version_id,
2085
+ componentInput: body.component_input as JsonObject,
2086
+ } : {}),
2087
+ ...(hasDraft ? {
2088
+ draft: {
2089
+ type: body.draft_type as "workflow" | "form" | "policy" | "template" | "routing_rules",
2090
+ title: body.draft_title as string,
2091
+ content: body.draft_content as JsonObject,
2092
+ ...(typeof body.draft_target_id === "string" ? { targetId: body.draft_target_id } : {}),
2093
+ },
2094
+ } : {}),
2095
+ }, session, mutationKey(request));
2096
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
2097
+ }
2098
+ if (request.method === "GET") return json({ schema_version: "1", ...(await loadAssistantHistory(env.DB, sessionId, session)) });
2099
+ if (request.method === "DELETE") {
2100
+ await closeAssistantSession(env.DB, sessionId, session);
2101
+ return new Response(null, { status: 204 });
2102
+ }
2103
+ return methodNotAllowed(["GET", "DELETE"]);
2104
+ }
2105
+
2106
+ async function handleAdminAssistantSocket(request: Request, env: Env, sessionId: string): Promise<Response> {
2107
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2108
+ if (request.headers.get("upgrade")?.toLowerCase() !== "websocket") {
2109
+ throw new ApiError(426, "websocket_upgrade_required", "A WebSocket upgrade is required.");
2110
+ }
2111
+ const session = await requireOperator(request, env, "useAssistant");
2112
+ const history = await loadAssistantHistory(env.DB, sessionId, session);
2113
+ const pair = new WebSocketPair();
2114
+ const [client, server] = Object.values(pair);
2115
+ server.accept();
2116
+ server.send(canonicalJson({ type: "assistant.session_snapshot", schema_version: "1", ...history }));
2117
+ server.addEventListener("message", () => {
2118
+ server.send(canonicalJson({
2119
+ type: "assistant.command_required",
2120
+ message: "Submit assistant messages through the authenticated messages endpoint; this socket streams session state only.",
2121
+ }));
2122
+ });
2123
+ return new Response(null, { status: 101, webSocket: client });
2124
+ }
2125
+
2126
+ async function handleAdminAssistantDraftAdopt(
2127
+ request: Request,
2128
+ env: Env,
2129
+ draftId: string,
2130
+ ): Promise<Response> {
2131
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2132
+ const baseSession = await requireOperator(request, env, "useAssistant");
2133
+ const draft = await loadAssistantDraft(env.DB, draftId, baseSession);
2134
+ if (draft.status === "adopted") {
2135
+ return json({ schema_version: "1", draft_id: draftId, adopted_target_id: draft.adoptedTargetId, idempotentReplay: true });
2136
+ }
2137
+ const draftType = String(draft.draftType);
2138
+ const permission = draftType === "workflow" ? "manageWorkflows"
2139
+ : draftType === "form" ? "manageForms"
2140
+ : draftType === "policy" ? "managePolicies"
2141
+ : draftType === "template" ? "manageTemplates" : "manageRouting";
2142
+ const session = await requireOperator(request, env, permission);
2143
+ const content = draft.content as JsonObject;
2144
+ const targetId = draftType === "routing_rules" ? "default" : typeof draft.targetId === "string" ? draft.targetId : "";
2145
+ if (!targetId) throw new ApiError(409, "assistant_draft_target_missing", "This draft has no target editor.");
2146
+ let adoptedTargetId: string;
2147
+ if (draftType === "workflow") {
2148
+ const current = await env.DB.prepare(`SELECT revision FROM workflow_drafts WHERE workflow_id = ?1 LIMIT 1`).bind(targetId).first<{ revision: number }>();
2149
+ if (!current) throw new ApiError(404, "workflow_not_found", "The target workflow does not exist.");
2150
+ const result = await replaceWorkflowDraft(env.DB, targetId, parseWorkflowGraph(content), current.revision, session);
2151
+ adoptedTargetId = result.draftId;
2152
+ } else {
2153
+ const type = draftType as ConfigurationType;
2154
+ const existing = await loadConfiguration(env.DB, type, targetId);
2155
+ const existingDraft = existing?.draft as Record<string, unknown> | null | undefined;
2156
+ if (existingDraft) {
2157
+ const result = await updateConfigurationDraft(env.DB, type, targetId, content, Number(existingDraft.revision), session);
2158
+ adoptedTargetId = result.draftId;
2159
+ } else {
2160
+ const result = await createConfigurationDraft(env.DB, type, targetId, content, session, `assistant-adopt:${mutationKey(request)}`);
2161
+ adoptedTargetId = result.draftId;
2162
+ }
2163
+ }
2164
+ await markAssistantDraftAdopted(env.DB, draftId, adoptedTargetId, session, mutationKey(request));
2165
+ return json({ schema_version: "1", draft_id: draftId, adopted_target_id: adoptedTargetId, idempotentReplay: false }, { status: 201 });
2166
+ }
2167
+
2168
+ async function handleAdminAssistantToolCall(request: Request, env: Env, toolCallId: string): Promise<Response> {
2169
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2170
+ const session = await requireOperator(request, env, "useAssistant");
2171
+ return json({ schema_version: "1", tool_call: await loadAssistantToolCall(env.DB, toolCallId, session) });
2172
+ }
2173
+
2174
+ async function handleAdminHumanTasks(request: Request, env: Env): Promise<Response> {
2175
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2176
+ const session = await requireOperator(request, env, "reviewTasks");
2177
+ const url = new URL(request.url);
2178
+ const state = url.searchParams.get("state")?.trim() || undefined;
2179
+ if (state && !["open", "claimed", "completed", "expired", "cancelled"].includes(state)) {
2180
+ throw new ApiError(400, "human_task_state_invalid", "state is invalid.");
2181
+ }
2182
+ const queueId = url.searchParams.get("queue_id")?.trim() || undefined;
2183
+ if (queueId && queueId.length > 100) throw new ApiError(400, "human_task_queue_invalid", "queue_id is invalid.");
2184
+ if (queueId) assertOperatorQueueAccess(session, queueId);
2185
+ return json({
2186
+ schema_version: "1",
2187
+ tasks: await listHumanTasks(env.DB, {
2188
+ ...(state ? { state } : {}),
2189
+ ...(queueId ? { queueId } : {}),
2190
+ ...(url.searchParams.get("mine") === "true" ? { operatorId: session.actor.id } : {}),
2191
+ allowedQueueIds: session.queueScope ?? null,
2192
+ limit: integer(url.searchParams.get("limit") ?? undefined, 50, 1, 100),
2193
+ }),
2194
+ });
2195
+ }
2196
+
2197
+ async function handleAdminReportEvidence(
2198
+ request: Request,
2199
+ env: Env,
2200
+ ctx: ExecutionContext,
2201
+ reportId: string,
2202
+ action?: "fetch" | "snapshots",
2203
+ ): Promise<Response> {
2204
+ if (!action) {
2205
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2206
+ const session = await requireOperator(request, env, "readEvidence");
2207
+ await assertOperatorReportAccess(env.DB, session, reportId);
2208
+ return json({ schema_version: "1", evidence: await listReportEvidence(env.DB, reportId) });
2209
+ }
2210
+ if (action === "fetch") {
2211
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2212
+ const session = await requireOperator(request, env, "readEvidence");
2213
+ await assertOperatorReportAccess(env.DB, session, reportId);
2214
+ const result = await requestEvidenceFetch(
2215
+ env.DB,
2216
+ reportId,
2217
+ await readJsonObject(request, 64 * 1024),
2218
+ session,
2219
+ mutationKey(request),
2220
+ );
2221
+ ctx.waitUntil(env.REPORT_JOBS.send(result.job, { contentType: "json" }).catch((error: unknown) => {
2222
+ console.error("evidence fetch enqueue failed; scheduled reconciliation will retry", {
2223
+ requestId: result.requestId,
2224
+ errorType: error instanceof Error ? error.name : typeof error,
2225
+ });
2226
+ }));
2227
+ return json({ schema_version: "1", request_id: result.requestId, idempotent_replay: result.idempotentReplay }, { status: result.idempotentReplay ? 200 : 202 });
2228
+ }
2229
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2230
+ const session = await requireOperator(request, env, "manageEvidence");
2231
+ await assertOperatorReportAccess(env.DB, session, reportId);
2232
+ const result = await createEvidenceSnapshot(
2233
+ env,
2234
+ reportId,
2235
+ await readJsonObject(request, 1_100_000),
2236
+ session,
2237
+ mutationKey(request),
2238
+ );
2239
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
2240
+ }
2241
+
2242
+ async function handleAdminEvidenceContent(request: Request, env: Env, snapshotId: string): Promise<Response> {
2243
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2244
+ const session = await requireOperator(request, env, "readSensitiveEvidence");
2245
+ const snapshot = await env.DB.prepare(`SELECT report_id AS reportId FROM evidence_snapshots WHERE id = ?1 AND deleted_at IS NULL LIMIT 1`)
2246
+ .bind(snapshotId).first<{ reportId: string }>();
2247
+ if (!snapshot) throw new ApiError(404, "evidence_snapshot_not_found", "The evidence snapshot is unavailable.");
2248
+ await assertOperatorReportAccess(env.DB, session, snapshot.reportId);
2249
+ const requestId = request.headers.get("cf-ray")?.slice(0, 200) || crypto.randomUUID();
2250
+ return readEvidenceContent(env, snapshotId, session, requestId);
2251
+ }
2252
+
2253
+ async function handleEvidenceResponse(request: Request, env: Env): Promise<Response> {
2254
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2255
+ const integration = await requireIntegration(request, env);
2256
+ if (!integration.scopes.includes("evidence_callback")) throw new ApiError(403, "integration_scope_denied", "This integration cannot submit evidence callbacks.");
2257
+ const result = await acceptEvidenceResponse(env, await readJsonObject(request, 1_100_000), integration.id);
2258
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 202 });
2259
+ }
2260
+
2261
+ async function handleActionResult(request: Request, env: Env): Promise<Response> {
2262
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2263
+ const integration = await requireIntegration(request, env);
2264
+ if (!integration.scopes.includes("action_callback")) throw new ApiError(403, "integration_scope_denied", "This integration cannot submit action callbacks.");
2265
+ const result = await acceptActionResult(env, await readJsonObject(request, 128 * 1024), integration.id);
2266
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 202 });
2267
+ }
2268
+
2269
+ async function handleAdminHumanTask(
2270
+ request: Request,
2271
+ env: Env,
2272
+ taskId: string,
2273
+ action?: "claim" | "respond" | "response" | "reassign" | "cancel",
2274
+ ): Promise<Response> {
2275
+ if (!action) {
2276
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2277
+ const session = await requireOperator(request, env, "reviewTasks");
2278
+ const task = await loadHumanTask(env.DB, taskId);
2279
+ if (!task) throw new ApiError(404, "human_task_not_found", "The task does not exist.");
2280
+ assertOperatorQueueAccess(session, typeof task.queueId === "string" ? task.queueId : null);
2281
+ return json({ schema_version: "1", task: projectOperatorFields(session, "task", task) });
2282
+ }
2283
+ if (action === "claim") {
2284
+ const session = await requireOperator(request, env, "reviewTasks");
2285
+ const task = await loadHumanTask(env.DB, taskId);
2286
+ if (!task) throw new ApiError(404, "human_task_not_found", "The task does not exist.");
2287
+ assertOperatorQueueAccess(session, typeof task.queueId === "string" ? task.queueId : null);
2288
+ if (request.method === "POST") {
2289
+ const claim = await claimHumanTask(env.DB, taskId, session, integer(env.CLAIM_TTL_MINUTES, 15, 1, 1_440));
2290
+ return json({ schema_version: "1", claim }, { status: 201 });
2291
+ }
2292
+ if (request.method === "DELETE") {
2293
+ await releaseHumanTaskClaim(env.DB, taskId, session);
2294
+ return new Response(null, { status: 204 });
2295
+ }
2296
+ return methodNotAllowed(["POST", "DELETE"]);
2297
+ }
2298
+ if (action === "reassign") {
2299
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2300
+ const session = await requireOperator(request, env, "assignTasks");
2301
+ const body = await readJsonObject(request, 16 * 1024);
2302
+ const queueId = boundedBodyString(body.queue_id, "queue_id", 100);
2303
+ const reason = boundedBodyString(body.reason, "reason", 2_000);
2304
+ const task = await loadHumanTask(env.DB, taskId);
2305
+ if (!task) throw new ApiError(404, "human_task_not_found", "The task does not exist.");
2306
+ assertOperatorQueueAccess(session, typeof task.queueId === "string" ? task.queueId : null);
2307
+ assertOperatorQueueAccess(session, queueId);
2308
+ const result = await reassignHumanTask(env.DB, taskId, queueId, reason, session, mutationKey(request));
2309
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
2310
+ }
2311
+ if (action === "cancel") {
2312
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2313
+ const session = await requireOperator(request, env, "operateRuns");
2314
+ const task = await loadHumanTask(env.DB, taskId);
2315
+ if (!task) throw new ApiError(404, "human_task_not_found", "The task does not exist.");
2316
+ assertOperatorQueueAccess(session, typeof task.queueId === "string" ? task.queueId : null);
2317
+ const body = await readJsonObject(request, 16 * 1024);
2318
+ const result = await cancelHumanTask(
2319
+ env.DB, taskId, boundedBodyString(body.reason, "reason", 2_000), session, mutationKey(request),
2320
+ );
2321
+ const event = await sendWorkflowEvent(env, result.runId, {
2322
+ eventType: `task.${taskId}.cancelled`,
2323
+ eventKey: `task-cancel:${taskId}:${mutationKey(request)}`,
2324
+ sourceType: "operator",
2325
+ sourceId: session.actor.id,
2326
+ payload: { task_id: taskId, reason: body.reason },
2327
+ });
2328
+ return json({ schema_version: "1", ...result, event }, { status: result.idempotentReplay ? 200 : 202 });
2329
+ }
2330
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2331
+ const session = await requireOperator(request, env, "reviewTasks");
2332
+ const task = await loadHumanTask(env.DB, taskId);
2333
+ if (!task) throw new ApiError(404, "human_task_not_found", "The task does not exist.");
2334
+ assertOperatorQueueAccess(session, typeof task.queueId === "string" ? task.queueId : null);
2335
+ const body = await readJsonObject(request, 256 * 1024);
2336
+ const result = await respondToHumanTask(env, taskId, body.response, session, mutationKey(request));
2337
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 202 });
2338
+ }
2339
+
2340
+ async function handleAdminWorkflowRun(
2341
+ request: Request,
2342
+ env: Env,
2343
+ runId: string,
2344
+ action?: "pause" | "resume" | "terminate" | "events" | "reconcile",
2345
+ ): Promise<Response> {
2346
+ if (!action) {
2347
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2348
+ const session = await requireOperator(request, env, "readRuns");
2349
+ await assertOperatorWorkflowRunAccess(env.DB, session, runId);
2350
+ const run = await loadWorkflowRun(env.DB, runId);
2351
+ if (!run) throw new ApiError(404, "workflow_run_not_found", "The workflow run does not exist.");
2352
+ return json({ schema_version: "1", ...run });
2353
+ }
2354
+ if (action === "events") {
2355
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2356
+ const session = await requireOperator(request, env, "operateRuns");
2357
+ await assertOperatorWorkflowRunAccess(env.DB, session, runId);
2358
+ const body = await readJsonObject(request, 256 * 1024);
2359
+ if (typeof body.event_type !== "string" || typeof body.event_key !== "string") {
2360
+ throw new ApiError(400, "workflow_event_invalid", "event_type and event_key are required.");
2361
+ }
2362
+ const result = await sendWorkflowEvent(env, runId, {
2363
+ eventType: body.event_type,
2364
+ eventKey: body.event_key,
2365
+ sourceType: "operator",
2366
+ sourceId: session.actor.id,
2367
+ payload: body.payload ?? {},
2368
+ });
2369
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 202 });
2370
+ }
2371
+ if (action === "reconcile") {
2372
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2373
+ const session = await requireOperator(request, env, "operateRuns");
2374
+ await assertOperatorWorkflowRunAccess(env.DB, session, runId);
2375
+ const result = await reconcileWorkflowRun(env, runId, session, mutationKey(request));
2376
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 202 });
2377
+ }
2378
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2379
+ const session = await requireOperator(request, env, "operateRuns");
2380
+ await assertOperatorWorkflowRunAccess(env.DB, session, runId);
2381
+ await controlWorkflowRun(env, runId, action);
2382
+ return json({ schema_version: "1", run_id: runId, state: action === "pause" ? "paused" : action === "resume" ? "running" : "terminated" });
2383
+ }
2384
+
2385
+ async function handleAdminActionProposal(
2386
+ request: Request,
2387
+ env: Env,
2388
+ proposalId: string,
2389
+ action?: "approve" | "reject" | "retry" | "compensate",
2390
+ ): Promise<Response> {
2391
+ const reference = await env.DB.prepare(`SELECT report_id AS reportId FROM action_proposals WHERE id = ?1 LIMIT 1`)
2392
+ .bind(proposalId).first<{ reportId: string }>();
2393
+ if (!reference) throw new ApiError(404, "action_proposal_not_found", "The action proposal does not exist.");
2394
+ if (!action) {
2395
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2396
+ const session = await requireOperator(request, env, "readReports");
2397
+ await assertOperatorReportAccess(env.DB, session, reference.reportId);
2398
+ const detail = await loadActionProposalDetail(env.DB, proposalId);
2399
+ return json({ schema_version: "1", ...detail });
2400
+ }
2401
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2402
+ const session = await requireOperator(request, env, action === "retry" ? "operateActions" : "approveActions");
2403
+ await assertOperatorReportAccess(env.DB, session, reference.reportId);
2404
+ const body = await readJsonObject(request, 32 * 1024);
2405
+ if (action === "compensate") {
2406
+ const result = await proposeCompensatingAction(env, proposalId, body, session, mutationKey(request));
2407
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
2408
+ }
2409
+ const payloadDigest = typeof body.payload_digest === "string" ? body.payload_digest : "";
2410
+ if (!/^sha256:[a-f0-9]{64}$/u.test(payloadDigest)) throw new ApiError(400, "action_payload_digest_invalid", "payload_digest must be an exact SHA-256 digest.");
2411
+ if (action === "retry") {
2412
+ const result = await retryActionProposal(env, proposalId, payloadDigest, session, mutationKey(request));
2413
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 202 });
2414
+ }
2415
+ const result = await decideActionProposal(env, proposalId, {
2416
+ decision: action === "approve" ? "approved" : "rejected",
2417
+ payloadDigest,
2418
+ ...(typeof body.reason === "string" ? { reason: body.reason } : {}),
2419
+ }, session, mutationKey(request));
2420
+ return json({ schema_version: "1", ...result });
2421
+ }
2422
+
2423
+ async function handleAdminOperations(request: Request, env: Env): Promise<Response> {
2424
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2425
+ await requireOperator(request, env, "readOperations");
2426
+ return json({ schema_version: "1", operations: await loadOperationsSnapshot(env.DB) });
2427
+ }
2428
+
2429
+ async function handleAdminOperationsIncidents(request: Request, env: Env): Promise<Response> {
2430
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2431
+ await requireOperator(request, env, "readOperations");
2432
+ const url = new URL(request.url);
2433
+ const state = url.searchParams.get("state");
2434
+ const severity = url.searchParams.get("severity");
2435
+ if (state && !["open", "acknowledged", "mitigated", "resolved"].includes(state)) {
2436
+ throw new ApiError(400, "operations_incident_state_invalid", "The incident state filter is invalid.");
2437
+ }
2438
+ if (severity && !["info", "low", "medium", "high", "critical"].includes(severity)) {
2439
+ throw new ApiError(400, "operations_incident_severity_invalid", "The incident severity filter is invalid.");
2440
+ }
2441
+ return json({
2442
+ schema_version: "1",
2443
+ incidents: await listOperationsIncidents(env.DB, {
2444
+ state, severity, limit: integer(url.searchParams.get("limit") ?? undefined, 50, 1, 100),
2445
+ }),
2446
+ });
2447
+ }
2448
+
2449
+ async function handleAdminOperationsIncident(
2450
+ request: Request,
2451
+ env: Env,
2452
+ incidentId: string,
2453
+ action?: "acknowledge" | "resolve" | "retry",
2454
+ ): Promise<Response> {
2455
+ if (!action) {
2456
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2457
+ await requireOperator(request, env, "readOperations");
2458
+ const incident = await loadOperationsIncident(env.DB, incidentId);
2459
+ if (!incident) throw new ApiError(404, "operations_incident_not_found", "The operations incident does not exist.");
2460
+ return json({ schema_version: "1", ...incident });
2461
+ }
2462
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2463
+ const session = await requireOperator(request, env, "operateSystem");
2464
+ if (action === "acknowledge") {
2465
+ return json({ schema_version: "1", ...(await acknowledgeOperationsIncident(env.DB, incidentId, session, mutationKey(request))) });
2466
+ }
2467
+ if (action === "resolve") {
2468
+ const body = await readJsonObject(request, 16 * 1024);
2469
+ const result = await resolveOperationsIncident(env.DB, incidentId, {
2470
+ code: typeof body.resolution_code === "string" ? body.resolution_code : "",
2471
+ summary: typeof body.summary === "string" ? body.summary : "",
2472
+ }, session, mutationKey(request));
2473
+ return json({ schema_version: "1", ...result });
2474
+ }
2475
+ const result = await retryOperationsIncident(env.DB, incidentId, session, mutationKey(request));
2476
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 202 });
2477
+ }
2478
+
2479
+ async function handleAdminDeadLetters(request: Request, env: Env): Promise<Response> {
2480
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2481
+ await requireOperator(request, env, "readOperations");
2482
+ const url = new URL(request.url);
2483
+ return json({
2484
+ schema_version: "1",
2485
+ dead_letters: await listDeadLetters(env.DB, integer(url.searchParams.get("limit") ?? undefined, 100, 1, 200)),
2486
+ });
2487
+ }
2488
+
2489
+ async function handleAdminAiQualitySamples(request: Request, env: Env): Promise<Response> {
2490
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2491
+ const session = await requireOperator(request, env, "reviewQuality");
2492
+ const url = new URL(request.url);
2493
+ const stateValue = url.searchParams.get("state");
2494
+ if (stateValue && !["pending", "claimed", "complete"].includes(stateValue)) {
2495
+ throw new ApiError(400, "invalid_quality_sample_state", "The quality sample state filter is invalid.");
2496
+ }
2497
+ const limit = integer(url.searchParams.get("limit") ?? undefined, 50, 1, 100);
2498
+ return json({
2499
+ schema_version: "1",
2500
+ samples: await listAiQualitySamples(env.DB, stateValue as "pending" | "claimed" | "complete" | null, limit, session.queueScope ?? null),
2501
+ });
2502
+ }
2503
+
2504
+ async function handleAdminShadowComparisons(request: Request, env: Env): Promise<Response> {
2505
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2506
+ const session = await requireOperator(request, env, "reviewQuality");
2507
+ const url = new URL(request.url);
2508
+ const status = url.searchParams.get("status");
2509
+ if (status && !["pending", "quality_task_created", "reviewed", "dismissed"].includes(status)) {
2510
+ throw new ApiError(400, "shadow_comparison_status_invalid", "The shadow comparison status filter is invalid.");
2511
+ }
2512
+ return json({
2513
+ schema_version: "1",
2514
+ comparisons: await listShadowComparisons(env.DB, {
2515
+ status,
2516
+ limit: integer(url.searchParams.get("limit") ?? undefined, 50, 1, 100),
2517
+ allowedQueueIds: session.queueScope ?? null,
2518
+ }),
2519
+ });
2520
+ }
2521
+
2522
+ async function handleAdminShadowComparison(
2523
+ request: Request,
2524
+ env: Env,
2525
+ comparisonId: string,
2526
+ action: "dismiss" | "create-task",
2527
+ ): Promise<Response> {
2528
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2529
+ const session = await requireOperator(request, env, "reviewQuality");
2530
+ const comparison = await env.DB.prepare(`SELECT report_id AS reportId FROM shadow_comparisons WHERE id = ?1 LIMIT 1`)
2531
+ .bind(comparisonId).first<{ reportId: string }>();
2532
+ if (!comparison) throw new ApiError(404, "shadow_comparison_not_found", "The shadow comparison does not exist.");
2533
+ await assertOperatorReportAccess(env.DB, session, comparison.reportId);
2534
+ if (action === "create-task") {
2535
+ const result = await createShadowQualityTask(env.DB, comparisonId, session, mutationKey(request));
2536
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
2537
+ }
2538
+ const body = await readJsonObject(request, 16 * 1024);
2539
+ const result = await dismissShadowComparison(
2540
+ env.DB,
2541
+ comparisonId,
2542
+ typeof body.reason === "string" ? body.reason : "",
2543
+ session,
2544
+ mutationKey(request),
2545
+ );
2546
+ return json({ schema_version: "1", ...result });
2547
+ }
2548
+
2549
+ async function handleAdminAiQualitySample(
2550
+ request: Request,
2551
+ env: Env,
2552
+ sampleId: string,
2553
+ ): Promise<Response> {
2554
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2555
+ const session = await requireOperator(request, env, "reviewQuality");
2556
+ const sample = await loadAiQualitySample(env.DB, sampleId);
2557
+ if (!sample) throw new ApiError(404, "quality_sample_not_found", "The quality sample does not exist.");
2558
+ await assertOperatorReportAccess(env.DB, session, String(sample.reportId));
2559
+ const report = await loadReportDetail(env.DB, String(sample.reportId));
2560
+ if (!report) throw new ApiError(404, "report_not_found", "The sampled report does not exist.");
2561
+ return json({ schema_version: "1", sample: projectOperatorFields(session, "quality_sample", sample), report: projectOperatorFields(session, "report", report) });
2562
+ }
2563
+
2564
+ async function handleAdminAiQualityClaim(
2565
+ request: Request,
2566
+ env: Env,
2567
+ sampleId: string,
2568
+ ): Promise<Response> {
2569
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2570
+ const session = await requireOperator(request, env, "reviewQuality");
2571
+ const sample = await loadAiQualitySample(env.DB, sampleId);
2572
+ if (!sample) throw new ApiError(404, "quality_sample_not_found", "The quality sample does not exist.");
2573
+ await assertOperatorReportAccess(env.DB, session, String(sample.reportId));
2574
+ return json({
2575
+ schema_version: "1",
2576
+ claim: await claimAiQualitySample(env.DB, sampleId, session, integer(env.CLAIM_TTL_MINUTES, 15, 1, 1_440)),
2577
+ }, { status: 201 });
2578
+ }
2579
+
2580
+ async function handleAdminAiQualityReview(
2581
+ request: Request,
2582
+ env: Env,
2583
+ sampleId: string,
2584
+ ): Promise<Response> {
2585
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2586
+ const session = await requireOperator(request, env, "reviewQuality");
2587
+ const sample = await loadAiQualitySample(env.DB, sampleId);
2588
+ if (!sample) throw new ApiError(404, "quality_sample_not_found", "The quality sample does not exist.");
2589
+ await assertOperatorReportAccess(env.DB, session, String(sample.reportId));
2590
+ const result = await recordAiQualityReview(
2591
+ env.DB,
2592
+ sampleId,
2593
+ session,
2594
+ parseQualityReview(await readJsonObject(request, 16 * 1024), request.headers.get("idempotency-key")),
2595
+ );
2596
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
2597
+ }
2598
+
2599
+ async function handleAdminAnalytics(request: Request, env: Env): Promise<Response> {
2600
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2601
+ await requireOperator(request, env, "readAnalytics");
2602
+ const days = integer(new URL(request.url).searchParams.get("days") ?? undefined, 30, 1, 366);
2603
+ await refreshDailyAggregatesIfStale(env.DB);
2604
+ return json({ schema_version: "1", analytics: await loadAnalytics(env.DB, days) });
2605
+ }
2606
+
2607
+ async function handleAdminAnalyticsAi(request: Request, env: Env): Promise<Response> {
2608
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2609
+ await requireOperator(request, env, "readAi");
2610
+ const days = integer(new URL(request.url).searchParams.get("days") ?? undefined, 30, 1, 366);
2611
+ return json({ schema_version: "1", analytics: await loadAiUsage(env.DB, days) });
2612
+ }
2613
+
2614
+ async function handleAdminAnalyticsWorkflows(request: Request, env: Env): Promise<Response> {
2615
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2616
+ await requireOperator(request, env, "readAnalytics");
2617
+ const days = integer(new URL(request.url).searchParams.get("days") ?? undefined, 30, 1, 366);
2618
+ return json({ schema_version: "1", analytics: await loadWorkflowAnalytics(env.DB, days) });
2619
+ }
2620
+
2621
+ async function handleAdminSearch(request: Request, env: Env): Promise<Response> {
2622
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2623
+ const session = await requireOperator(request, env, "searchWorkspace");
2624
+ const url = new URL(request.url);
2625
+ return json({
2626
+ schema_version: "1",
2627
+ results: await searchWorkspace(
2628
+ env.DB,
2629
+ url.searchParams.get("q") ?? "",
2630
+ session.queueScope ?? null,
2631
+ integer(url.searchParams.get("limit") ?? undefined, 30, 1, 100),
2632
+ ),
2633
+ });
2634
+ }
2635
+
2636
+ async function handleAdminSavedViews(request: Request, env: Env): Promise<Response> {
2637
+ const session = await requireOperator(request, env, "searchWorkspace");
2638
+ if (request.method === "GET") return json({ schema_version: "1", saved_views: await listSavedViews(env.DB, session.actor.id) });
2639
+ if (request.method === "POST") {
2640
+ const savedView = await createSavedView(env.DB, session.actor.id, await readJsonObject(request, 64 * 1024));
2641
+ return json({ schema_version: "1", saved_view: savedView }, { status: 201 });
2642
+ }
2643
+ return methodNotAllowed(["GET", "POST"]);
2644
+ }
2645
+
2646
+ async function handleAdminSavedView(request: Request, env: Env, viewId: string): Promise<Response> {
2647
+ const session = await requireOperator(request, env, "searchWorkspace");
2648
+ if (request.method === "PATCH") {
2649
+ const savedView = await updateSavedView(env.DB, session.actor.id, viewId, await readJsonObject(request, 64 * 1024));
2650
+ return json({ schema_version: "1", saved_view: savedView });
2651
+ }
2652
+ if (request.method === "DELETE") {
2653
+ await deleteSavedView(env.DB, session.actor.id, viewId);
2654
+ return new Response(null, { status: 204 });
2655
+ }
2656
+ return methodNotAllowed(["PATCH", "DELETE"]);
2657
+ }
2658
+
2659
+ async function handleAdminReportsExport(request: Request, env: Env): Promise<Response> {
2660
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2661
+ const session = await requireOperator(request, env, "exportData");
2662
+ const days = integer(new URL(request.url).searchParams.get("days") ?? undefined, 30, 1, 366);
2663
+ const body = await reportsCsv(env.DB, days, session.queueScope ?? null);
2664
+ await recordExportAudit(env.DB, session.actor.id, days);
2665
+ return new Response(body, {
2666
+ headers: {
2667
+ "content-type": "text/csv; charset=utf-8",
2668
+ "content-disposition": `attachment; filename="safest-resolve-${nowDateForFile()}.csv"`,
2669
+ "cache-control": "no-store",
2670
+ },
2671
+ });
2672
+ }
2673
+
2674
+ async function handleAdminExports(request: Request, env: Env): Promise<Response> {
2675
+ if (request.method === "GET") {
2676
+ const session = await requireOperator(request, env, "exportData");
2677
+ return json({ schema_version: "1", exports: await listExports(env.DB, session.actor.id, integer(new URL(request.url).searchParams.get("limit") ?? undefined, 50, 1, 100)) });
2678
+ }
2679
+ if (request.method === "POST") {
2680
+ const session = await requireOperator(request, env, "exportData");
2681
+ const result = await createExport(env, await readJsonObject(request, 64 * 1024), session, mutationKey(request));
2682
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
2683
+ }
2684
+ return methodNotAllowed(["GET", "POST"]);
2685
+ }
2686
+
2687
+ async function handleAdminExport(request: Request, env: Env, exportId: string): Promise<Response> {
2688
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2689
+ const session = await requireOperator(request, env, "exportData");
2690
+ if (new URL(request.url).searchParams.get("download") === "1") return downloadExport(env, exportId, session.actor.id);
2691
+ const exportJob = await loadExport(env.DB, exportId, session.actor.id);
2692
+ if (!exportJob) throw new ApiError(404, "export_not_found", "The export does not exist.");
2693
+ return json({
2694
+ schema_version: "1",
2695
+ export: {
2696
+ ...exportJob,
2697
+ objectKey: undefined,
2698
+ downloadUrl: exportJob.state === "ready" ? `/v1/admin/exports/${encodeURIComponent(exportId)}?download=1` : null,
2699
+ },
2700
+ });
2701
+ }
2702
+
2703
+ function nowDateForFile(): string {
2704
+ return new Date().toISOString().slice(0, 10);
2705
+ }
2706
+
2707
+ async function handleAdminAudit(request: Request, env: Env): Promise<Response> {
2708
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2709
+ const session = await requireOperator(request, env, "viewAudit");
2710
+ const url = new URL(request.url);
2711
+ if (url.searchParams.has("after")) throw new ApiError(400, "invalid_query", "Use the opaque cursor parameter for audit pagination.");
2712
+ const cursorScope = `audit:${session.actor.id}`;
2713
+ const cursor = await decodeApiCursor<{ sequence: number }>(env.CONTEXT_SIGNING_SECRET, cursorScope, url.searchParams.get("cursor"));
2714
+ if (cursor && (!Number.isInteger(cursor.sequence) || cursor.sequence < 0 || cursor.sequence > Number.MAX_SAFE_INTEGER)) {
2715
+ throw new ApiError(400, "cursor_invalid", "cursor is invalid or does not belong to this query.");
2716
+ }
2717
+ const limit = integer(url.searchParams.get("limit") ?? undefined, 100, 1, 500);
2718
+ const page = await listAuditEntries(env.DB, cursor?.sequence ?? 0, limit);
2719
+ return json({
2720
+ schema_version: "1",
2721
+ entries: page.entries,
2722
+ next_cursor: page.nextSequence === null
2723
+ ? null
2724
+ : await encodeApiCursor(env.CONTEXT_SIGNING_SECRET, cursorScope, { sequence: page.nextSequence }),
2725
+ });
2726
+ }
2727
+
2728
+ async function handleAdminAuditVerify(request: Request, env: Env): Promise<Response> {
2729
+ if (request.method !== "GET" && request.method !== "POST") return methodNotAllowed(["GET", "POST"]);
2730
+ await requireOperator(request, env, "viewAudit");
2731
+ return json({ schema_version: "1", verification: await verifyAuditChain(env.DB) });
2732
+ }
2733
+
2734
+ function mutationKey(request: Request): string {
2735
+ const key = request.headers.get("idempotency-key")?.trim() ?? "";
2736
+ if (key.length < 8 || key.length > 200) throw new ApiError(400, "idempotency_key_required", "A stable Idempotency-Key header containing 8 to 200 characters is required.");
2737
+ return key;
2738
+ }
2739
+
2740
+ function boundedBodyString(value: unknown, name: string, maximum: number): string {
2741
+ if (typeof value !== "string" || value.trim().length < 3 || value.trim().length > maximum) {
2742
+ throw new ApiError(400, "invalid_request", `${name} must contain 3 to ${maximum} characters.`);
2743
+ }
2744
+ return value.trim();
2745
+ }
2746
+
2747
+ async function handleAdminLegalHolds(request: Request, env: Env): Promise<Response> {
2748
+ if (request.method === "GET") {
2749
+ await requireOperator(request, env, "manageGovernance");
2750
+ return json({ schema_version: "1", legal_holds: await listLegalHolds(env.DB, integer(new URL(request.url).searchParams.get("limit") ?? undefined, 100, 1, 200)) });
2751
+ }
2752
+ if (request.method !== "POST") return methodNotAllowed(["GET", "POST"]);
2753
+ const session = await requireOperator(request, env, "manageGovernance");
2754
+ const body = await readJsonObject(request, 8 * 1024);
2755
+ if (Object.keys(body).some((key) => !["report_id", "reason"].includes(key))) throw new ApiError(400, "invalid_request", "The legal-hold request contains unsupported fields.");
2756
+ const reportId = boundedBodyString(body.report_id, "report_id", 100);
2757
+ const reason = boundedBodyString(body.reason, "reason", 2_000);
2758
+ const result = await createLegalHold(env.DB, reportId, reason, session.actor.id, mutationKey(request));
2759
+ return json({ schema_version: "1", legal_hold_id: result.id, idempotent_replay: result.idempotentReplay }, { status: result.idempotentReplay ? 200 : 201 });
2760
+ }
2761
+
2762
+ async function handleAdminLegalHoldRelease(request: Request, env: Env, holdId: string): Promise<Response> {
2763
+ if (request.method !== "DELETE") return methodNotAllowed(["DELETE"]);
2764
+ const session = await requireOperator(request, env, "manageGovernance");
2765
+ await releaseLegalHold(env.DB, holdId, session.actor.id, mutationKey(request));
2766
+ return new Response(null, { status: 204 });
2767
+ }
2768
+
2769
+ async function handleAdminDeletionRequests(request: Request, env: Env): Promise<Response> {
2770
+ if (request.method === "GET") {
2771
+ await requireOperator(request, env, "manageGovernance");
2772
+ return json({ schema_version: "1", deletion_requests: await listDeletionRequests(env.DB, integer(new URL(request.url).searchParams.get("limit") ?? undefined, 100, 1, 200)) });
2773
+ }
2774
+ if (request.method !== "POST") return methodNotAllowed(["GET", "POST"]);
2775
+ const session = await requireOperator(request, env, "manageGovernance");
2776
+ const body = await readJsonObject(request, 8 * 1024);
2777
+ if (Object.keys(body).some((key) => !["report_id", "scope", "reason"].includes(key))) throw new ApiError(400, "invalid_request", "The deletion request contains unsupported fields.");
2778
+ const scope = body.scope;
2779
+ if (scope !== "participant_data" && scope !== "all_permitted_data") throw new ApiError(400, "invalid_request", "scope is invalid.");
2780
+ const result = await createDeletionRequest(env.DB, {
2781
+ reportId: boundedBodyString(body.report_id, "report_id", 100),
2782
+ scope,
2783
+ reason: boundedBodyString(body.reason, "reason", 2_000),
2784
+ }, session.actor.id, mutationKey(request));
2785
+ return json({ schema_version: "1", deletion_request_id: result.id, state: result.state, idempotent_replay: result.idempotentReplay }, {
2786
+ status: result.idempotentReplay ? 200 : 201,
2787
+ });
2788
+ }
2789
+
2790
+ async function handleAdminRetention(request: Request, env: Env, preview: boolean): Promise<Response> {
2791
+ const messageDays = integer(env.MESSAGE_RETENTION_DAYS, 90, 1, 3_650);
2792
+ const reportDays = integer(env.REPORT_RETENTION_DAYS, 365, 30, 3_650);
2793
+ if (preview) {
2794
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2795
+ await requireOperator(request, env, "manageGovernance");
2796
+ return json({ schema_version: "1", preview: await previewRetention(env.DB, messageDays, reportDays) });
2797
+ }
2798
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2799
+ await requireOperator(request, env, "manageGovernance");
2800
+ return json({ schema_version: "1", retention: await retentionOverview(env.DB, messageDays, reportDays) });
2801
+ }
2802
+
2803
+ async function handleAdminReportDetail(request: Request, env: Env, reportId: string): Promise<Response> {
2804
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2805
+ const session = await requireOperator(request, env, "readReports");
2806
+ await assertOperatorReportAccess(env.DB, session, reportId);
2807
+ const report = await loadReportDetail(env.DB, reportId);
2808
+ if (!report) throw new ApiError(404, "report_not_found", "The report does not exist.");
2809
+ return json({ schema_version: "1", report: projectOperatorFields(session, "report", report) });
2810
+ }
2811
+
2812
+ async function handleAdminReportClaim(request: Request, env: Env, ctx: ExecutionContext, reportId: string): Promise<Response> {
2813
+ const session = await requireOperator(request, env, "claimReports");
2814
+ await assertOperatorReportAccess(env.DB, session, reportId);
2815
+ if (request.method === "POST") {
2816
+ const claim = await claimReport(env.DB, reportId, session, integer(env.CLAIM_TTL_MINUTES, 15, 1, 1_440));
2817
+ publishWorkspaceEvent(env, ctx, { type: "claim_changed", reportId });
2818
+ return json({ schema_version: "1", claim }, { status: 201 });
2819
+ }
2820
+ if (request.method === "DELETE") {
2821
+ await releaseReportClaim(env.DB, reportId, session);
2822
+ publishWorkspaceEvent(env, ctx, { type: "claim_changed", reportId });
2823
+ return new Response(null, { status: 204 });
2824
+ }
2825
+ return methodNotAllowed(["POST", "DELETE"]);
2826
+ }
2827
+
2828
+ async function handleAdminReportMessage(
2829
+ request: Request,
2830
+ env: Env,
2831
+ ctx: ExecutionContext,
2832
+ reportId: string,
2833
+ ): Promise<Response> {
2834
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2835
+ const session = await requireOperator(request, env, "messageReporters");
2836
+ await assertOperatorReportAccess(env.DB, session, reportId);
2837
+ const result = await addReviewerMessage(
2838
+ env.DB,
2839
+ reportId,
2840
+ session,
2841
+ parseReviewerMessage(await readJsonObject(request, 16 * 1024), request.headers.get("idempotency-key")),
2842
+ deliveryOptions(env),
2843
+ );
2844
+ queueDeliveryJobs(env, ctx, result.jobs);
2845
+ publishWorkspaceEvent(env, ctx, { type: "reports_changed", reportId });
2846
+ return json({ schema_version: "1", message_id: result.messageId, idempotent_replay: result.idempotentReplay }, {
2847
+ status: result.idempotentReplay ? 200 : 201,
2848
+ });
2849
+ }
2850
+
2851
+ async function handleAdminReportDecision(
2852
+ request: Request,
2853
+ env: Env,
2854
+ ctx: ExecutionContext,
2855
+ reportId: string,
2856
+ ): Promise<Response> {
2857
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2858
+ const session = await requireOperator(request, env, "decideReports");
2859
+ await assertOperatorReportAccess(env.DB, session, reportId);
2860
+ const allowedActionCodes = new Set(env.ALLOWED_ACTION_CODES.split(",").map((value) => value.trim()).filter(Boolean));
2861
+ const result = await recordHumanDecision(
2862
+ env.DB,
2863
+ reportId,
2864
+ session,
2865
+ parseDecision(await readJsonObject(request, 32 * 1024), request.headers.get("idempotency-key")),
2866
+ {
2867
+ ...deliveryOptions(env),
2868
+ allowedActionCodes,
2869
+ actionEnabled: Boolean(env.ACTION_WEBHOOK_URL?.trim() && env.ACTION_WEBHOOK_SECRET?.trim()),
2870
+ },
2871
+ );
2872
+ queueDeliveryJobs(env, ctx, result.jobs);
2873
+ publishWorkspaceEvent(env, ctx, { type: "reports_changed", reportId });
2874
+ return json({ schema_version: "1", decision_id: result.decisionId, idempotent_replay: result.idempotentReplay }, {
2875
+ status: result.idempotentReplay ? 200 : 201,
2876
+ });
2877
+ }
2878
+
2879
+ async function terminateActivePrimaryRun(env: Env, reportId: string): Promise<string | null> {
2880
+ const active = await env.DB.prepare(`
2881
+ SELECT id FROM workflow_runs WHERE report_id = ?1 AND run_kind = 'primary'
2882
+ AND state IN ('created','dispatch_pending','queued','running','waiting','paused','repair_required')
2883
+ ORDER BY created_at DESC, id DESC LIMIT 1
2884
+ `).bind(reportId).first<{ id: string }>();
2885
+ if (!active) return null;
2886
+ await controlWorkflowRun(env, active.id, "terminate");
2887
+ return active.id;
2888
+ }
2889
+
2890
+ async function startAssignedPrimaryRun(
2891
+ env: Env,
2892
+ ctx: ExecutionContext,
2893
+ reportId: string,
2894
+ session: Awaited<ReturnType<typeof requireAnyOperator>>,
2895
+ idempotencyKey: string,
2896
+ ): Promise<{ runId: string; workflowVersionId: string; idempotentReplay: boolean } | null> {
2897
+ const pin = await env.DB.prepare(`
2898
+ SELECT p.id FROM reports r JOIN queue_workflow_pins p
2899
+ ON p.queue_id = r.queue_id AND p.mode = 'primary' AND p.deactivated_at IS NULL
2900
+ JOIN workflow_versions v ON v.id = p.workflow_version_id AND v.retired_at IS NULL
2901
+ JOIN workflow_artifacts a ON a.id = v.artifact_id AND a.status = 'ready'
2902
+ WHERE r.id = ?1 LIMIT 1
2903
+ `).bind(reportId).first<{ id: string }>();
2904
+ if (!pin) return null;
2905
+ const result = await createPinnedWorkflowRun(env.DB, reportId, "primary", session, idempotencyKey);
2906
+ ctx.waitUntil(env.CONTROL_JOBS.send(result.job, { contentType: "json" }).catch((error: unknown) => {
2907
+ console.error("assigned workflow dispatch enqueue failed; reconciliation will retry", {
2908
+ runId: result.runId,
2909
+ errorType: error instanceof Error ? error.name : typeof error,
2910
+ });
2911
+ }));
2912
+ return { runId: result.runId, workflowVersionId: result.workflowVersionId, idempotentReplay: result.idempotentReplay };
2913
+ }
2914
+
2915
+ async function handleAdminReportReassign(
2916
+ request: Request,
2917
+ env: Env,
2918
+ ctx: ExecutionContext,
2919
+ reportId: string,
2920
+ ): Promise<Response> {
2921
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2922
+ const session = await requireOperator(request, env, "reassignReports");
2923
+ await assertOperatorReportAccess(env.DB, session, reportId);
2924
+ const body = await readJsonObject(request, 16 * 1024);
2925
+ const queueId = boundedBodyString(body.queue_id, "queue_id", 100);
2926
+ const reason = boundedBodyString(body.reason, "reason", 2_000);
2927
+ assertOperatorQueueAccess(session, queueId);
2928
+ const idempotencyKey = mutationKey(request);
2929
+ await terminateActivePrimaryRun(env, reportId);
2930
+ const assignment = await reassignReport(env.DB, reportId, queueId, reason, session, idempotencyKey);
2931
+ const workflowRun = await startAssignedPrimaryRun(env, ctx, reportId, session, `report-reassign-run:${idempotencyKey}`);
2932
+ publishWorkspaceEvent(env, ctx, { type: "reports_changed", reportId });
2933
+ return json({ schema_version: "1", assignment, workflow_run: workflowRun }, { status: assignment.idempotentReplay ? 200 : 202 });
2934
+ }
2935
+
2936
+ async function handleAdminReportEnrichment(
2937
+ request: Request,
2938
+ env: Env,
2939
+ reportId: string,
2940
+ componentKey: string,
2941
+ ): Promise<Response> {
2942
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
2943
+ const session = await requireOperator(request, env, "runEnrichments");
2944
+ await assertOperatorReportAccess(env.DB, session, reportId);
2945
+ const body = await readJsonObject(request, 256 * 1024);
2946
+ const componentInput = body.input;
2947
+ if (!componentInput || typeof componentInput !== "object" || Array.isArray(componentInput)) {
2948
+ throw new ApiError(400, "component_input_invalid", "input must be an object.");
2949
+ }
2950
+ if (body.component_version_id !== undefined && typeof body.component_version_id !== "string") {
2951
+ throw new ApiError(400, "component_version_invalid", "component_version_id must be a string.");
2952
+ }
2953
+ const version = await env.DB.prepare(`
2954
+ SELECT v.id FROM component_definitions d JOIN component_versions v
2955
+ ON v.definition_id = d.id
2956
+ WHERE d.component_key = ?1 AND d.status = 'active' AND d.effect_class = 'read_only'
2957
+ AND v.id = COALESCE(?2, d.active_version_id)
2958
+ AND v.published_at IS NOT NULL AND v.retired_at IS NULL
2959
+ LIMIT 1
2960
+ `).bind(componentKey, typeof body.component_version_id === "string" ? body.component_version_id : null).first<{ id: string }>();
2961
+ if (!version) throw new ApiError(409, "component_version_unavailable", "The requested published read-only component is unavailable.");
2962
+ try {
2963
+ const result = await executeAssistantReadOnlyComponent(env, {
2964
+ reportId,
2965
+ componentVersionId: version.id,
2966
+ componentInput: componentInput as JsonObject,
2967
+ operationId: mutationKey(request),
2968
+ actorId: session.actor.id,
2969
+ source: "operator",
2970
+ });
2971
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
2972
+ } catch (error) {
2973
+ if (error instanceof ComponentExecutionError) {
2974
+ throw new ApiError(error.retryable ? 503 : 422, error.code, "The enrichment could not be completed safely.", {
2975
+ retryable: error.retryable,
2976
+ retry_after_seconds: error.delaySeconds,
2977
+ });
2978
+ }
2979
+ throw error;
2980
+ }
2981
+ }
2982
+
2983
+ async function handleAdminReportCollection(
2984
+ request: Request,
2985
+ env: Env,
2986
+ reportId: string,
2987
+ collection: "timeline" | "findings" | "actions",
2988
+ ): Promise<Response> {
2989
+ const permission = collection === "findings" ? "readEvidence" : "readReports";
2990
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
2991
+ const session = await requireOperator(request, env, permission);
2992
+ await assertOperatorReportAccess(env.DB, session, reportId);
2993
+ if (collection === "findings") return json({ schema_version: "1", findings: await listReportFindings(env.DB, reportId) });
2994
+ if (collection === "actions") return json({ schema_version: "1", actions: await listReportActions(env.DB, reportId) });
2995
+ const url = new URL(request.url);
2996
+ const timeline = await loadReportTimeline(env.DB, reportId, {
2997
+ cursor: url.searchParams.get("cursor"),
2998
+ cursorSecret: env.CONTEXT_SIGNING_SECRET,
2999
+ limit: integer(url.searchParams.get("limit") ?? undefined, 50, 1, 100),
3000
+ });
3001
+ return json({ schema_version: "1", events: timeline.events, next_cursor: timeline.nextCursor });
3002
+ }
3003
+
3004
+ async function handleAdminReportCloseOrReopen(
3005
+ request: Request,
3006
+ env: Env,
3007
+ ctx: ExecutionContext,
3008
+ reportId: string,
3009
+ action: "close" | "reopen",
3010
+ ): Promise<Response> {
3011
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
3012
+ const session = await requireOperator(request, env, "decideReports");
3013
+ await assertOperatorReportAccess(env.DB, session, reportId);
3014
+ const body = await readJsonObject(request, 16 * 1024);
3015
+ const reason = boundedBodyString(body.reason, "reason", 2_000);
3016
+ const idempotencyKey = mutationKey(request);
3017
+ if (action === "close") {
3018
+ await terminateActivePrimaryRun(env, reportId);
3019
+ const result = await closeReport(env.DB, reportId, reason, session, idempotencyKey);
3020
+ publishWorkspaceEvent(env, ctx, { type: "reports_changed", reportId });
3021
+ return json({ schema_version: "1", ...result });
3022
+ }
3023
+ const queueId = boundedBodyString(body.queue_id, "queue_id", 100);
3024
+ assertOperatorQueueAccess(session, queueId);
3025
+ const result = await reopenReport(env.DB, reportId, queueId, reason, session, idempotencyKey);
3026
+ const workflowRun = await startAssignedPrimaryRun(env, ctx, reportId, session, `report-reopen-run:${idempotencyKey}`);
3027
+ publishWorkspaceEvent(env, ctx, { type: "reports_changed", reportId });
3028
+ return json({ schema_version: "1", report: result, workflow_run: workflowRun }, { status: result.idempotentReplay ? 200 : 202 });
3029
+ }
3030
+
3031
+ async function handleAdminDecision(request: Request, env: Env, decisionId: string): Promise<Response> {
3032
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
3033
+ const session = await requireOperator(request, env, "readReports");
3034
+ const decision = await loadDecisionDetail(env.DB, decisionId);
3035
+ if (!decision) throw new ApiError(404, "decision_not_found", "The decision does not exist.");
3036
+ await assertOperatorReportAccess(env.DB, session, String(decision.reportId));
3037
+ return json({ schema_version: "1", decision: projectOperatorFields(session, "decision", decision) });
3038
+ }
3039
+
3040
+ async function handleAdminAppeals(request: Request, env: Env): Promise<Response> {
3041
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
3042
+ const session = await requireOperator(request, env, "reviewAppeals");
3043
+ const limit = integer(new URL(request.url).searchParams.get("limit") ?? undefined, 50, 1, 100);
3044
+ return json({ schema_version: "1", appeals: await listAppeals(env.DB, limit, session.queueScope ?? null) });
3045
+ }
3046
+
3047
+ async function handleAdminAppealDetail(request: Request, env: Env, appealId: string): Promise<Response> {
3048
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
3049
+ const session = await requireOperator(request, env, "reviewAppeals");
3050
+ const appeal = await loadAppealDetail(env.DB, appealId);
3051
+ if (!appeal) throw new ApiError(404, "appeal_not_found", "The appeal does not exist.");
3052
+ await assertOperatorReportAccess(env.DB, session, String(appeal.reportId));
3053
+ return json({ schema_version: "1", appeal: projectOperatorFields(session, "appeal", appeal) });
3054
+ }
3055
+
3056
+ async function handleAdminAppealClaim(
3057
+ request: Request,
3058
+ env: Env,
3059
+ appealId: string,
3060
+ ): Promise<Response> {
3061
+ const session = await requireOperator(request, env, "reviewAppeals");
3062
+ const appeal = await loadAppealDetail(env.DB, appealId);
3063
+ if (!appeal) throw new ApiError(404, "appeal_not_found", "The appeal does not exist.");
3064
+ await assertOperatorReportAccess(env.DB, session, String(appeal.reportId));
3065
+ if (request.method === "POST") {
3066
+ const claim = await claimAppeal(env.DB, appealId, session, integer(env.CLAIM_TTL_MINUTES, 15, 1, 1_440));
3067
+ return json({ schema_version: "1", claim }, { status: 201 });
3068
+ }
3069
+ if (request.method === "DELETE") {
3070
+ await releaseAppealClaim(env.DB, appealId, session);
3071
+ return new Response(null, { status: 204 });
3072
+ }
3073
+ return methodNotAllowed(["POST", "DELETE"]);
3074
+ }
3075
+
3076
+ async function handleAdminAppealDecision(
3077
+ request: Request,
3078
+ env: Env,
3079
+ ctx: ExecutionContext,
3080
+ appealId: string,
3081
+ ): Promise<Response> {
3082
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
3083
+ const session = await requireOperator(request, env, "decideAppeals");
3084
+ const appeal = await loadAppealDetail(env.DB, appealId);
3085
+ if (!appeal) throw new ApiError(404, "appeal_not_found", "The appeal does not exist.");
3086
+ await assertOperatorReportAccess(env.DB, session, String(appeal.reportId));
3087
+ const allowedActionCodes = new Set(env.ALLOWED_ACTION_CODES.split(",").map((value) => value.trim()).filter(Boolean));
3088
+ const result = await decideAppeal(
3089
+ env.DB,
3090
+ appealId,
3091
+ session,
3092
+ parseAppealDecision(await readJsonObject(request, 32 * 1024), request.headers.get("idempotency-key")),
3093
+ {
3094
+ ...deliveryOptions(env),
3095
+ allowedActionCodes,
3096
+ actionEnabled: Boolean(env.ACTION_WEBHOOK_URL?.trim() && env.ACTION_WEBHOOK_SECRET?.trim()),
3097
+ },
3098
+ );
3099
+ queueDeliveryJobs(env, ctx, result.jobs);
3100
+ return json({
3101
+ schema_version: "1",
3102
+ appeal_decision_id: result.appealDecisionId,
3103
+ idempotent_replay: result.idempotentReplay,
3104
+ }, { status: result.idempotentReplay ? 200 : 201 });
3105
+ }
3106
+
3107
+ async function handleHealth(request: Request, env: Env): Promise<Response> {
3108
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
3109
+ void env;
3110
+ return json({ status: "ok", service: "safest-resolve-reports", version: "1" });
3111
+ }
3112
+
3113
+ async function handleReady(request: Request, env: Env): Promise<Response> {
3114
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
3115
+ await requireAnyOperator(request, env);
3116
+ try {
3117
+ const [migrations, features] = await Promise.all([
3118
+ env.DB.prepare(`SELECT COUNT(*) AS count FROM d1_migrations`).first<{ count: number }>(),
3119
+ env.DB.prepare(`SELECT feature_key AS featureKey, state FROM installation_features WHERE installation_id = 'default'`).all<{ featureKey: string; state: string }>(),
3120
+ env.PROFILE_MEDIA.list({ limit: 1 }),
3121
+ env.WORKFLOW_ARTIFACTS.list({ limit: 1 }),
3122
+ env.EVIDENCE_OBJECTS.list({ limit: 1 }),
3123
+ env.EXPORTS.list({ limit: 1 }),
3124
+ ]);
3125
+ const migrationCount = Number(migrations?.count ?? 0);
3126
+ const featureStates = new Map(features.results.map((feature) => [feature.featureKey, feature.state]));
3127
+ const requiredFeatures = ["database", "workflow_runtime", "workers_ai", "object_storage", "queues"];
3128
+ const unavailable = requiredFeatures.filter((feature) => featureStates.get(feature) !== "ready");
3129
+ const bindingsReady = Boolean(env.WORKFLOWS && env.LOADER && env.CONTROL_JOBS && env.REPORT_JOBS && env.DELIVERY_JOBS && env.AI);
3130
+ const ready = migrationCount >= EXPECTED_DATABASE_MIGRATIONS && unavailable.length === 0 && bindingsReady;
3131
+ return json({
3132
+ status: ready ? "ready" : "not_ready",
3133
+ service: "safest-resolve-reports",
3134
+ schema_migrations: migrationCount,
3135
+ checks: { database: true, private_object_storage: true, runtime_bindings: bindingsReady, unavailable_features: unavailable },
3136
+ }, { status: ready ? 200 : 503 });
3137
+ } catch {
3138
+ return json({ status: "not_ready", service: "safest-resolve-reports", checks: { dependency_probe: false } }, { status: 503 });
3139
+ }
3140
+ }
3141
+
3142
+ async function requireAnyOperator(request: Request, env: Env) {
3143
+ const session = await resolveOperatorSession(request, env);
3144
+ if (!session) throw new ApiError(401, "operator_authentication_required", "Sign in to your Safest workspace to continue.");
3145
+ return session;
3146
+ }
3147
+
3148
+ async function requireDeploymentOwner(request: Request, env: Env) {
3149
+ const session = await resolveOperatorSession(request, env);
3150
+ if (!session) throw new ApiError(401, "infrastructure_authentication_required", "The deployment owner key is required.");
3151
+ if (session.actor.type !== "admin_key" || session.authMethod !== "admin_key") {
3152
+ throw new ApiError(403, "infrastructure_authentication_required", "This operation requires the deployment owner key, not an interactive operator session.");
3153
+ }
3154
+ return session;
3155
+ }
3156
+
3157
+ async function handleAdminBackups(request: Request, env: Env): Promise<Response> {
3158
+ if (request.method === "GET") {
3159
+ const session = await requireOperator(request, env, "manageGovernance");
3160
+ const url = new URL(request.url);
3161
+ const limit = integer(url.searchParams.get("limit") ?? undefined, 100, 1, 200);
3162
+ const cursorScope = `backup-list:${session.actor.type}:${session.actor.id}`;
3163
+ const cursor = await decodeApiCursor<{ createdAt: string; id: string }>(
3164
+ env.CONTEXT_SIGNING_SECRET,
3165
+ cursorScope,
3166
+ url.searchParams.get("cursor"),
3167
+ );
3168
+ if (cursor && (typeof cursor.createdAt !== "string" || !Number.isFinite(Date.parse(cursor.createdAt))
3169
+ || typeof cursor.id !== "string" || cursor.id.length !== 36)) {
3170
+ throw new ApiError(400, "cursor_invalid", "cursor is invalid or does not belong to this query.");
3171
+ }
3172
+ const page = await listBackups(env.DB, limit, cursor ?? undefined);
3173
+ return json({
3174
+ schema_version: "1",
3175
+ backups: page.backups,
3176
+ next_cursor: page.nextCursor
3177
+ ? await encodeApiCursor(env.CONTEXT_SIGNING_SECRET, cursorScope, page.nextCursor)
3178
+ : null,
3179
+ });
3180
+ }
3181
+ if (request.method !== "POST") return methodNotAllowed(["GET", "POST"]);
3182
+ const session = await requireDeploymentOwner(request, env);
3183
+ const result = await startBackup(
3184
+ env.DB,
3185
+ session,
3186
+ await readJsonObject(request, 8 * 1024),
3187
+ mutationKey(request),
3188
+ );
3189
+ return json({ schema_version: "1", ...result }, { status: result.idempotentReplay ? 200 : 201 });
3190
+ }
3191
+
3192
+ async function handleAdminBackup(
3193
+ request: Request,
3194
+ env: Env,
3195
+ backupId: string,
3196
+ action?: "r2" | "r2/object",
3197
+ ): Promise<Response> {
3198
+ if (!action) {
3199
+ if (request.method !== "PATCH") return methodNotAllowed(["PATCH"]);
3200
+ const session = await requireDeploymentOwner(request, env);
3201
+ const result = await finalizeBackup(
3202
+ env.DB,
3203
+ session,
3204
+ backupId,
3205
+ parseBackupCompletion(await readJsonObject(request, 16 * 1024)),
3206
+ mutationKey(request),
3207
+ );
3208
+ return json({ schema_version: "1", ...result });
3209
+ }
3210
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
3211
+ await requireDeploymentOwner(request, env);
3212
+ const normalizedBackupId = await assertBackupTransferAllowed(env.DB, backupId);
3213
+ const url = new URL(request.url);
3214
+ const bucketName = parseBackupBucket(url.searchParams.get("bucket"));
3215
+ const bucket = backupBucket(env, bucketName);
3216
+ if (action === "r2/object") {
3217
+ const object = await readBackupObject(bucket, url.searchParams.get("key"), url.searchParams.get("etag"));
3218
+ return new Response(object.body, {
3219
+ status: 200,
3220
+ headers: {
3221
+ "cache-control": "no-store",
3222
+ "content-disposition": "attachment; filename=\"r2-object\"",
3223
+ "content-length": String(object.size),
3224
+ "content-type": "application/octet-stream",
3225
+ etag: object.httpEtag,
3226
+ "x-content-type-options": "nosniff",
3227
+ },
3228
+ });
3229
+ }
3230
+ const cursorScope = `backup-r2:${normalizedBackupId}:${bucketName}`;
3231
+ const cursor = await decodeApiCursor<{ r2Cursor: string }>(
3232
+ env.CONTEXT_SIGNING_SECRET,
3233
+ cursorScope,
3234
+ url.searchParams.get("cursor"),
3235
+ );
3236
+ if (cursor && (typeof cursor.r2Cursor !== "string" || cursor.r2Cursor.length > 4_096)) {
3237
+ throw new ApiError(400, "cursor_invalid", "cursor is invalid or does not belong to this query.");
3238
+ }
3239
+ const page = await listBackupObjects(
3240
+ bucket,
3241
+ cursor?.r2Cursor,
3242
+ integer(url.searchParams.get("limit") ?? undefined, 500, 1, 1_000),
3243
+ );
3244
+ return json({
3245
+ schema_version: "1",
3246
+ bucket: bucketName,
3247
+ objects: page.objects,
3248
+ next_cursor: page.nextCursor
3249
+ ? await encodeApiCursor(env.CONTEXT_SIGNING_SECRET, cursorScope, { r2Cursor: page.nextCursor })
3250
+ : null,
3251
+ });
3252
+ }
3253
+
3254
+ async function handleMetaCapabilities(request: Request, env: Env): Promise<Response> {
3255
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
3256
+ await requireAnyOperator(request, env);
3257
+ const installation = await loadInstallation(env.DB);
3258
+ return json({ schema_version: "1", capabilities: installation.features });
3259
+ }
3260
+
3261
+ async function handleMetaVersion(request: Request, env: Env): Promise<Response> {
3262
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
3263
+ await requireAnyOperator(request, env);
3264
+ return json({ schema_version: "1", version: await metaVersion(env.DB) });
3265
+ }
3266
+
3267
+ async function handleAdminInstallation(request: Request, env: Env): Promise<Response> {
3268
+ if (request.method === "GET") {
3269
+ await requireOperator(request, env, "manageInstallation");
3270
+ return json({ schema_version: "1", installation: await loadInstallation(env.DB) });
3271
+ }
3272
+ if (request.method === "PATCH") {
3273
+ const session = await requireOperator(request, env, "manageInstallation");
3274
+ const result = await updateInstallation(env.DB, await readJsonObject(request, 64 * 1024), session, mutationKey(request));
3275
+ return json({ schema_version: "1", installation: result });
3276
+ }
3277
+ return methodNotAllowed(["GET", "PATCH"]);
3278
+ }
3279
+
3280
+ async function handleAdminBrand(request: Request, env: Env): Promise<Response> {
3281
+ if (request.method === "GET") {
3282
+ await requireOperator(request, env, "manageConfiguration");
3283
+ return json({ schema_version: "1", brand: await loadWorkspaceBrand(env.DB) });
3284
+ }
3285
+ if (request.method === "PATCH") {
3286
+ const session = await requireOperator(request, env, "manageConfiguration");
3287
+ const body = await readJsonObject(request, 32 * 1024);
3288
+ if (Object.keys(body).some((key) => key !== "brand") || !body.brand || typeof body.brand !== "object" || Array.isArray(body.brand)) {
3289
+ throw new ApiError(400, "brand_update_invalid", "Provide the visual branding values to save.");
3290
+ }
3291
+ const installation = await loadInstallation(env.DB);
3292
+ const settings = installation.settings && typeof installation.settings === "object" && !Array.isArray(installation.settings)
3293
+ ? { ...(installation.settings as Record<string, unknown>), brand: body.brand }
3294
+ : { brand: body.brand };
3295
+ await updateInstallation(env.DB, { settings }, session, mutationKey(request));
3296
+ return json({ schema_version: "1", brand: await loadWorkspaceBrand(env.DB) });
3297
+ }
3298
+ return methodNotAllowed(["GET", "PATCH"]);
3299
+ }
3300
+
3301
+ async function handleAdminReportingChannels(request: Request, env: Env): Promise<Response> {
3302
+ if (request.method === "GET") {
3303
+ await requireOperator(request, env, "manageIntegrations");
3304
+ const [installation, publishedForms] = await Promise.all([loadInstallation(env.DB), listPublishedForms(env.DB)]);
3305
+ const settings = installation.settings && typeof installation.settings === "object" && !Array.isArray(installation.settings)
3306
+ ? installation.settings as Record<string, unknown>
3307
+ : {};
3308
+ const publicFormIds = Array.isArray(settings.public_form_version_ids)
3309
+ ? settings.public_form_version_ids.filter((value): value is string => typeof value === "string")
3310
+ : [];
3311
+ let antiAbuseReady = true;
3312
+ try { turnstileConfig(env); } catch { antiAbuseReady = false; }
3313
+ const enabled = installation.anonymousReportingEnabled === true && antiAbuseReady;
3314
+ return json({
3315
+ schema_version: "1",
3316
+ channels: publishedForms.filter((form) => publicFormIds.includes(form.id)).map((form) => ({
3317
+ id: `hosted:${form.id}`,
3318
+ mode: "hosted",
3319
+ name: form.title,
3320
+ formVersionId: form.id,
3321
+ url: new URL(`/report/?form=${encodeURIComponent(form.id)}`, publicBaseUrl(env)).href,
3322
+ enabled,
3323
+ status: antiAbuseReady ? (installation.anonymousReportingEnabled === true ? "ready" : "disabled") : "turnstile_required",
3324
+ })),
3325
+ });
3326
+ }
3327
+ if (request.method !== "POST") return methodNotAllowed(["GET", "POST"]);
3328
+ const session = await requireOperator(request, env, "manageIntegrations");
3329
+ const body = await readJsonObject(request, 16 * 1024);
3330
+ if (body.mode !== "hosted" || typeof body.form_version_id !== "string" || body.form_version_id.length > 160) {
3331
+ throw new ApiError(400, "reporting_channel_invalid", "Choose a published form for the hosted report page.");
3332
+ }
3333
+ const publishedForms = await listPublishedForms(env.DB);
3334
+ if (!publishedForms.some((form) => form.id === body.form_version_id)) {
3335
+ throw new ApiError(400, "reporting_channel_form_unpublished", "Publish this intake form before creating a hosted report page.");
3336
+ }
3337
+ turnstileConfig(env);
3338
+ const installation = await loadInstallation(env.DB);
3339
+ const currentSettings = installation.settings && typeof installation.settings === "object" && !Array.isArray(installation.settings)
3340
+ ? installation.settings as Record<string, unknown>
3341
+ : {};
3342
+ const currentPublicForms = Array.isArray(currentSettings.public_form_version_ids)
3343
+ ? currentSettings.public_form_version_ids.filter((value): value is string => typeof value === "string")
3344
+ : [];
3345
+ const settings = {
3346
+ ...currentSettings,
3347
+ public_form_version_ids: [...new Set([...currentPublicForms, body.form_version_id])],
3348
+ };
3349
+ await updateInstallation(env.DB, { anonymous_reporting_enabled: true, settings }, session, mutationKey(request));
3350
+ return json({
3351
+ schema_version: "1",
3352
+ channel: {
3353
+ mode: "hosted",
3354
+ formVersionId: body.form_version_id,
3355
+ url: new URL(`/report/?form=${encodeURIComponent(body.form_version_id)}`, publicBaseUrl(env)).href,
3356
+ },
3357
+ }, { status: 201 });
3358
+ }
3359
+
3360
+ async function handleAdminSetup(request: Request, env: Env): Promise<Response> {
3361
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
3362
+ await requireOperator(request, env, "manageInstallation");
3363
+ const installation = await loadInstallation(env.DB);
3364
+ const features = installation.features as Array<Record<string, unknown>>;
3365
+ return json({
3366
+ schema_version: "1",
3367
+ setup: {
3368
+ ready: features.filter((feature) => feature.featureKey !== "email").every((feature) => feature.state === "ready"),
3369
+ features,
3370
+ optional: ["email"],
3371
+ },
3372
+ });
3373
+ }
3374
+
3375
+ async function handleAdminSetupVerify(request: Request, env: Env, feature: string): Promise<Response> {
3376
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
3377
+ const session = await requireOperator(request, env, "manageInstallation");
3378
+ return json({ schema_version: "1", verification: await verifyFeature(env, feature, session.actor.id) });
3379
+ }
3380
+
3381
+ async function handleAdminIntegrations(request: Request, env: Env): Promise<Response> {
3382
+ if (request.method === "GET") {
3383
+ await requireOperator(request, env, "manageIntegrations");
3384
+ return json({ schema_version: "1", integrations: await listIntegrations(env.DB) });
3385
+ }
3386
+ if (request.method === "POST") {
3387
+ const session = await requireOperator(request, env, "manageIntegrations");
3388
+ const result = await createIntegration(env.DB, await readJsonObject(request, 64 * 1024), session, mutationKey(request));
3389
+ return json({ schema_version: "1", ...result, publicBaseUrl: publicBaseUrl(env) }, { status: 201, headers: { "cache-control": "no-store" } });
3390
+ }
3391
+ return methodNotAllowed(["GET", "POST"]);
3392
+ }
3393
+
3394
+ async function handleAdminIntegration(
3395
+ request: Request,
3396
+ env: Env,
3397
+ integrationId: string,
3398
+ action?: "rotate-key" | "revoke-key" | "test-report",
3399
+ ): Promise<Response> {
3400
+ if (!action) {
3401
+ if (request.method === "GET") {
3402
+ await requireOperator(request, env, "manageIntegrations");
3403
+ const integration = await loadIntegrationAdmin(env.DB, integrationId);
3404
+ if (!integration) throw new ApiError(404, "integration_not_found", "The integration does not exist.");
3405
+ return json({ schema_version: "1", integration });
3406
+ }
3407
+ if (request.method === "PATCH") {
3408
+ const session = await requireOperator(request, env, "manageIntegrations");
3409
+ const integration = await updateIntegration(env.DB, integrationId, await readJsonObject(request, 64 * 1024), session, mutationKey(request));
3410
+ return json({ schema_version: "1", integration });
3411
+ }
3412
+ return methodNotAllowed(["GET", "PATCH"]);
3413
+ }
3414
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
3415
+ const session = await requireOperator(request, env, "manageIntegrations");
3416
+ const body = await readJsonObject(request, 16 * 1024);
3417
+ if (action === "rotate-key") {
3418
+ const overlapSeconds = body.overlap_seconds === undefined ? 300 : body.overlap_seconds;
3419
+ if (!Number.isInteger(overlapSeconds)) throw new ApiError(400, "integration_overlap_invalid", "overlap_seconds must be an integer.");
3420
+ const result = await rotateIntegrationKey(env.DB, integrationId, Number(overlapSeconds), session, mutationKey(request));
3421
+ return json({ schema_version: "1", ...result }, { status: 201, headers: { "cache-control": "no-store" } });
3422
+ }
3423
+ if (action === "revoke-key") {
3424
+ if (body.key_id !== undefined && typeof body.key_id !== "string") throw new ApiError(400, "integration_key_id_invalid", "key_id must be a string.");
3425
+ const revoked = await revokeIntegrationKeys(env.DB, integrationId, typeof body.key_id === "string" ? body.key_id : null, session, mutationKey(request));
3426
+ return json({ schema_version: "1", integration_id: integrationId, revoked });
3427
+ }
3428
+ const [form, queues, integration] = await Promise.all([
3429
+ env.DB.prepare(`SELECT id FROM report_form_versions WHERE published_at IS NOT NULL AND retired_at IS NULL ORDER BY published_at DESC LIMIT 1`).first(),
3430
+ env.DB.prepare(`SELECT COUNT(*) AS count FROM queue_definitions WHERE status = 'active' AND purpose = 'reports'`).first<{ count: number }>(),
3431
+ loadIntegrationAdmin(env.DB, integrationId),
3432
+ ]);
3433
+ if (!integration) throw new ApiError(404, "integration_not_found", "The integration does not exist.");
3434
+ return json({
3435
+ schema_version: "1",
3436
+ test: {
3437
+ mode: "non_mutating",
3438
+ ready: Boolean(form) && Number(queues?.count ?? 0) > 0 && integration.enabled === true,
3439
+ checks: { publishedForm: Boolean(form), activeReportQueues: Number(queues?.count ?? 0), integrationEnabled: integration.enabled },
3440
+ },
3441
+ });
3442
+ }
3443
+
3444
+ async function route(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
3445
+ const url = new URL(request.url);
3446
+ if (url.pathname === "/api/auth" || url.pathname.startsWith("/api/auth/")) {
3447
+ return handleBetterAuthRequest(request, env, ctx);
3448
+ }
3449
+ if (url.pathname === "/embed" || url.pathname === "/embed/") return handleWidgetEmbed(request, env);
3450
+ if (url.pathname === "/health") return handleHealth(request, env);
3451
+ if (url.pathname === "/ready") return handleReady(request, env);
3452
+ if (url.pathname === "/v1/meta/capabilities") return handleMetaCapabilities(request, env);
3453
+ if (url.pathname === "/v1/meta/version") return handleMetaVersion(request, env);
3454
+ if (url.pathname === "/v1/auth/providers") return handleAuthProviders(request, env);
3455
+ if (url.pathname === "/v1/auth/login") return handleOperatorLogin(request, env, ctx);
3456
+ if (url.pathname === "/v1/auth/invitations/inspect") return inspectInvitation(request, env);
3457
+ if (url.pathname === "/v1/auth/invitations/accept") return handleOperatorInvitationAccept(request, env, ctx);
3458
+ if (url.pathname === "/v1/auth/password-resets") return handleOperatorPasswordResetRequest(request, env, ctx);
3459
+ if (url.pathname === "/v1/auth/password-resets/complete") return handleOperatorPasswordResetComplete(request, env, ctx);
3460
+ if (url.pathname === "/v1/auth/logout") return handleOperatorLogout(request, env, ctx);
3461
+ if (url.pathname === "/v1/account/profile") return handleOperatorProfile(request, env);
3462
+ if (url.pathname === "/v1/account/profile-picture") return handleOperatorProfilePicture(request, env, ctx);
3463
+ if (url.pathname === "/v1/infrastructure/bootstrap") return handleInfrastructureBootstrap(request, env, ctx);
3464
+ if (url.pathname === "/v1/widget/config") return handleWidgetConfig(request, env);
3465
+ if (url.pathname === "/v1/brand") return handlePublicBrand(request, env);
3466
+ if (url.pathname === "/v1/public/config") return handlePublicConfig(request, env);
3467
+ if (url.pathname === "/v1/context-tokens") return handleContextToken(request, env);
3468
+ if (url.pathname === "/v1/reports") return handleWidgetReport(request, env, ctx);
3469
+ if (url.pathname === "/v1/reports/anonymous") return handleAnonymousWidgetReport(request, env, ctx);
3470
+ if (url.pathname === "/v1/reports/server") return handleServerReport(request, env, ctx);
3471
+ if (url.pathname === "/v1/reports/public") return handlePublicReport(request, env, ctx);
3472
+ if (url.pathname === "/v1/evidence-responses") return handleEvidenceResponse(request, env);
3473
+ if (url.pathname === "/v1/action-results") return handleActionResult(request, env);
3474
+ if (url.pathname === "/v1/admin/session") return handleAdminSession(request, env);
3475
+ if (url.pathname === "/v1/admin/installation") return handleAdminInstallation(request, env);
3476
+ if (url.pathname === "/v1/admin/brand") return handleAdminBrand(request, env);
3477
+ if (url.pathname === "/v1/admin/reporting-channels") return handleAdminReportingChannels(request, env);
3478
+ if (url.pathname === "/v1/admin/setup") return handleAdminSetup(request, env);
3479
+ if (url.pathname === "/v1/admin/integrations") return handleAdminIntegrations(request, env);
3480
+ if (url.pathname === "/v1/admin/people") return handleAdminPeople(request, env);
3481
+ if (url.pathname === "/v1/admin/people/invitations") return handleAdminPeople(request, env);
3482
+ if (url.pathname === "/v1/admin/presence") return handleAdminPresence(request, env);
3483
+ if (url.pathname === "/v1/admin/presence/socket") return handleAdminPresenceSocket(request, env);
3484
+ if (url.pathname === "/v1/admin/reports") return handleAdminReports(request, env);
3485
+ if (url.pathname === "/v1/admin/queues") return handleAdminQueues(request, env);
3486
+ if (url.pathname === "/v1/admin/workflows") return handleAdminWorkflows(request, env);
3487
+ if (url.pathname === "/v1/admin/workflow-runs") return handleAdminWorkflowRuns(request, env);
3488
+ if (url.pathname === "/v1/admin/prompts") return handleAdminPrompts(request, env);
3489
+ if (url.pathname === "/v1/admin/model-aliases") return handleAdminModelAliases(request, env);
3490
+ if (url.pathname === "/v1/admin/agents") return handleAdminAgents(request, env);
3491
+ if (url.pathname === "/v1/admin/budgets") return handleAdminBudgets(request, env);
3492
+ if (url.pathname === "/v1/admin/forms") return handleAdminConfigurations(request, env, "form");
3493
+ if (url.pathname === "/v1/admin/forms/published") {
3494
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
3495
+ await requireOperator(request, env, "manageConfiguration");
3496
+ return json({ schema_version: "1", forms: await listPublishedForms(env.DB) });
3497
+ }
3498
+ if (url.pathname === "/v1/admin/policies") return handleAdminConfigurations(request, env, "policy");
3499
+ if (url.pathname === "/v1/admin/templates") return handleAdminConfigurations(request, env, "template");
3500
+ if (url.pathname === "/v1/admin/routing-rules") return handleAdminConfigurations(request, env, "routing_rules");
3501
+ if (url.pathname === "/v1/admin/routing-rules/draft") return handleAdminConfiguration(request, env, "routing_rules", "default", "draft");
3502
+ if (url.pathname === "/v1/admin/routing-rules/validate") return handleAdminConfiguration(request, env, "routing_rules", "default", "validate");
3503
+ if (url.pathname === "/v1/admin/routing-rules/publish") return handleAdminConfiguration(request, env, "routing_rules", "default", "publish");
3504
+ if (url.pathname === "/v1/admin/assistant/sessions") return handleAdminAssistantSessions(request, env);
3505
+ if (url.pathname === "/v1/admin/tasks") return handleAdminHumanTasks(request, env);
3506
+ if (url.pathname === "/v1/admin/connections") return handleAdminConnections(request, env);
3507
+ if (url.pathname === "/v1/admin/connections/oauth/callback") return handleAdminConnectionOAuthCallback(request, env);
3508
+ if (url.pathname === "/v1/admin/components") return handleAdminComponents(request, env);
3509
+ if (url.pathname === "/v1/admin/routing-agent") return handleAdminRoutingAgent(request, env);
3510
+ if (url.pathname === "/v1/admin/routing-agent/test") return handleAdminRoutingAgentTest(request, env);
3511
+ if (url.pathname === "/v1/admin/appeals") return handleAdminAppeals(request, env);
3512
+ if (url.pathname === "/v1/admin/operations") return handleAdminOperations(request, env);
3513
+ if (url.pathname === "/v1/admin/operations/capacity") return handleAdminOperations(request, env);
3514
+ if (url.pathname === "/v1/admin/operations/incidents") return handleAdminOperationsIncidents(request, env);
3515
+ if (url.pathname === "/v1/admin/operations/dead-letters") return handleAdminDeadLetters(request, env);
3516
+ if (url.pathname === "/v1/admin/ai/runs") return handleAdminAiRuns(request, env);
3517
+ if (url.pathname === "/v1/admin/ai/usage") return handleAdminAiUsage(request, env);
3518
+ if (url.pathname === "/v1/admin/ai/quality-samples") return handleAdminAiQualitySamples(request, env);
3519
+ if (url.pathname === "/v1/admin/shadow-comparisons") return handleAdminShadowComparisons(request, env);
3520
+ if (url.pathname === "/v1/admin/search") return handleAdminSearch(request, env);
3521
+ if (url.pathname === "/v1/admin/saved-views") return handleAdminSavedViews(request, env);
3522
+ if (url.pathname === "/v1/admin/analytics") return handleAdminAnalytics(request, env);
3523
+ if (url.pathname === "/v1/admin/analytics/ai") return handleAdminAnalyticsAi(request, env);
3524
+ if (url.pathname === "/v1/admin/analytics/workflows") return handleAdminAnalyticsWorkflows(request, env);
3525
+ if (url.pathname === "/v1/admin/exports/reports.csv") return handleAdminReportsExport(request, env);
3526
+ if (url.pathname === "/v1/admin/exports") return handleAdminExports(request, env);
3527
+ if (url.pathname === "/v1/admin/backups") return handleAdminBackups(request, env);
3528
+ if (url.pathname === "/v1/admin/audit") return handleAdminAudit(request, env);
3529
+ if (url.pathname === "/v1/admin/audit/verify") return handleAdminAuditVerify(request, env);
3530
+ if (url.pathname === "/v1/admin/legal-holds") return handleAdminLegalHolds(request, env);
3531
+ if (url.pathname === "/v1/admin/deletion-requests") return handleAdminDeletionRequests(request, env);
3532
+ if (url.pathname === "/v1/admin/retention") return handleAdminRetention(request, env, false);
3533
+ if (url.pathname === "/v1/admin/retention/preview") return handleAdminRetention(request, env, true);
3534
+ const evidenceContentRoute = /^\/v1\/admin\/evidence\/([^/]+)\/content$/u.exec(url.pathname);
3535
+ if (evidenceContentRoute) return handleAdminEvidenceContent(request, env, decodeURIComponent(evidenceContentRoute[1] ?? ""));
3536
+ const legalHoldReleaseRoute = /^\/v1\/admin\/legal-holds\/([^/]+)$/u.exec(url.pathname);
3537
+ if (legalHoldReleaseRoute) return handleAdminLegalHoldRelease(request, env, decodeURIComponent(legalHoldReleaseRoute[1] ?? ""));
3538
+ const savedViewRoute = /^\/v1\/admin\/saved-views\/([^/]+)$/u.exec(url.pathname);
3539
+ if (savedViewRoute) return handleAdminSavedView(request, env, decodeURIComponent(savedViewRoute[1] ?? ""));
3540
+ const exportRoute = /^\/v1\/admin\/exports\/([^/]+)$/u.exec(url.pathname);
3541
+ if (exportRoute) return handleAdminExport(request, env, decodeURIComponent(exportRoute[1] ?? ""));
3542
+ const backupRoute = /^\/v1\/admin\/backups\/([^/]+)(?:\/(r2|r2\/object))?$/u.exec(url.pathname);
3543
+ if (backupRoute) return handleAdminBackup(
3544
+ request,
3545
+ env,
3546
+ decodeURIComponent(backupRoute[1] ?? ""),
3547
+ backupRoute[2] as "r2" | "r2/object" | undefined,
3548
+ );
3549
+ const invitationRoute = /^\/v1\/admin\/people\/invitations\/([^/]+)$/u.exec(url.pathname);
3550
+ if (invitationRoute) return handleAdminInvitation(request, env, decodeURIComponent(invitationRoute[1] ?? ""));
3551
+ const operatorPasswordResetRoute = /^\/v1\/admin\/people\/([^/]+)\/password-reset$/u.exec(url.pathname);
3552
+ if (operatorPasswordResetRoute) return handleAdminOperatorPasswordReset(
3553
+ request,
3554
+ env,
3555
+ decodeURIComponent(operatorPasswordResetRoute[1] ?? ""),
3556
+ ctx,
3557
+ );
3558
+ const operatorAdminRoute = /^\/v1\/admin\/people\/([^/]+)(?:\/(revoke-sessions))?$/u.exec(url.pathname);
3559
+ if (operatorAdminRoute) return handleAdminOperator(
3560
+ request,
3561
+ env,
3562
+ decodeURIComponent(operatorAdminRoute[1] ?? ""),
3563
+ operatorAdminRoute[2] as "revoke-sessions" | undefined,
3564
+ );
3565
+ const profilePictureRoute = /^\/v1\/profile-pictures\/([^/]+)$/u.exec(url.pathname);
3566
+ if (profilePictureRoute) return handleOperatorProfilePictureRead(
3567
+ request,
3568
+ env,
3569
+ decodeURIComponent(profilePictureRoute[1] ?? ""),
3570
+ );
3571
+ const setupVerifyRoute = /^\/v1\/admin\/setup\/([^/]+)\/verify$/u.exec(url.pathname);
3572
+ if (setupVerifyRoute) return handleAdminSetupVerify(request, env, decodeURIComponent(setupVerifyRoute[1] ?? ""));
3573
+ const integrationAdminRoute = /^\/v1\/admin\/integrations\/([^/]+)(?:\/(rotate-key|revoke-key|test-report))?$/u.exec(url.pathname);
3574
+ if (integrationAdminRoute) return handleAdminIntegration(
3575
+ request,
3576
+ env,
3577
+ decodeURIComponent(integrationAdminRoute[1] ?? ""),
3578
+ integrationAdminRoute[2] as "rotate-key" | "revoke-key" | "test-report" | undefined,
3579
+ );
3580
+ const queueWorkflowPinRoute = /^\/v1\/admin\/queues\/([^/]+)\/workflow-pin$/u.exec(url.pathname);
3581
+ if (queueWorkflowPinRoute) return handleAdminQueueWorkflowPin(
3582
+ request,
3583
+ env,
3584
+ decodeURIComponent(queueWorkflowPinRoute[1] ?? ""),
3585
+ );
3586
+ const queueRoute = /^\/v1\/admin\/queues\/([^/]+)(?:\/(versions|policy-versions|pause|resume|health))?$/u.exec(url.pathname);
3587
+ if (queueRoute) return handleAdminQueue(
3588
+ request,
3589
+ env,
3590
+ decodeURIComponent(queueRoute[1] ?? ""),
3591
+ queueRoute[2] as "versions" | "policy-versions" | "pause" | "resume" | "health" | undefined,
3592
+ );
3593
+ const connectionRoute = /^\/v1\/admin\/connections\/([^/]+)(?:\/(versions|validate|test|pause|resume|dependencies))?$/u.exec(url.pathname);
3594
+ if (connectionRoute) return handleAdminConnection(
3595
+ request,
3596
+ env,
3597
+ decodeURIComponent(connectionRoute[1] ?? ""),
3598
+ connectionRoute[2] as "versions" | "validate" | "test" | "pause" | "resume" | "dependencies" | undefined,
3599
+ );
3600
+ const connectionOAuthRoute = /^\/v1\/admin\/connections\/([^/]+)\/oauth\/(start|revoke)$/u.exec(url.pathname);
3601
+ if (connectionOAuthRoute) return handleAdminConnectionOAuth(
3602
+ request,
3603
+ env,
3604
+ decodeURIComponent(connectionOAuthRoute[1] ?? ""),
3605
+ connectionOAuthRoute[2] as "start" | "revoke",
3606
+ );
3607
+ const componentRoute = /^\/v1\/admin\/components\/([^/]+)(?:\/(versions|validate|test|pause|resume|dependencies))?$/u.exec(url.pathname);
3608
+ if (componentRoute) return handleAdminComponent(
3609
+ request,
3610
+ env,
3611
+ decodeURIComponent(componentRoute[1] ?? ""),
3612
+ componentRoute[2] as "versions" | "validate" | "test" | "pause" | "resume" | "dependencies" | undefined,
3613
+ );
3614
+ const modelAliasVersionRoute = /^\/v1\/admin\/model-aliases\/([^/]+)\/versions$/u.exec(url.pathname);
3615
+ if (modelAliasVersionRoute) return handleAdminModelAliasVersion(request, env, decodeURIComponent(modelAliasVersionRoute[1] ?? ""));
3616
+ const agentVersionRoute = /^\/v1\/admin\/agents\/([^/]+)\/versions$/u.exec(url.pathname);
3617
+ if (agentVersionRoute) return handleAdminAgentVersion(request, env, decodeURIComponent(agentVersionRoute[1] ?? ""));
3618
+ const agentActionRoute = /^\/v1\/admin\/agents\/([^/]+)\/(test|publish)$/u.exec(url.pathname);
3619
+ if (agentActionRoute) {
3620
+ const agentId = decodeURIComponent(agentActionRoute[1] ?? "");
3621
+ return agentActionRoute[2] === "test"
3622
+ ? handleAdminAgentTest(request, env, agentId)
3623
+ : handleAdminAgentVersion(request, env, agentId);
3624
+ }
3625
+ const configurationRoute = /^\/v1\/admin\/(forms|policies|templates)\/([^/]+)\/(draft|validate|publish|versions|render)$/u.exec(url.pathname);
3626
+ if (configurationRoute) {
3627
+ const type = configurationRoute[1] === "forms" ? "form" : configurationRoute[1] === "policies" ? "policy" : "template";
3628
+ return handleAdminConfiguration(
3629
+ request,
3630
+ env,
3631
+ type,
3632
+ decodeURIComponent(configurationRoute[2] ?? ""),
3633
+ configurationRoute[3] as "draft" | "validate" | "publish" | "versions" | "render",
3634
+ );
3635
+ }
3636
+ const workflowFixtureRoute = /^\/v1\/admin\/workflows\/([^/]+)\/fixtures\/([^/]+)$/u.exec(url.pathname);
3637
+ if (workflowFixtureRoute) return handleAdminWorkflowFixture(
3638
+ request,
3639
+ env,
3640
+ decodeURIComponent(workflowFixtureRoute[1] ?? ""),
3641
+ decodeURIComponent(workflowFixtureRoute[2] ?? ""),
3642
+ );
3643
+ const workflowTestRunRoute = /^\/v1\/admin\/workflows\/([^/]+)\/test-runs\/([^/]+)$/u.exec(url.pathname);
3644
+ if (workflowTestRunRoute) return handleAdminWorkflowTestRun(
3645
+ request,
3646
+ env,
3647
+ decodeURIComponent(workflowTestRunRoute[1] ?? ""),
3648
+ decodeURIComponent(workflowTestRunRoute[2] ?? ""),
3649
+ );
3650
+ const workflowVersionRoute = /^\/v1\/admin\/workflows\/([^/]+)\/versions\/([^/]+)(?:\/(diff|retire))?$/u.exec(url.pathname);
3651
+ if (workflowVersionRoute) return handleAdminWorkflowVersion(
3652
+ request,
3653
+ env,
3654
+ decodeURIComponent(workflowVersionRoute[1] ?? ""),
3655
+ decodeURIComponent(workflowVersionRoute[2] ?? ""),
3656
+ workflowVersionRoute[3] as "diff" | "retire" | undefined,
3657
+ );
3658
+ const workflowRoute = /^\/v1\/admin\/workflows\/([^/]+)(?:\/(draft|validate|publish|versions|fixtures|test|test-runs|shadow-runs))?$/u.exec(url.pathname);
3659
+ if (workflowRoute) return handleAdminWorkflow(
3660
+ request,
3661
+ env,
3662
+ ctx,
3663
+ decodeURIComponent(workflowRoute[1] ?? ""),
3664
+ workflowRoute[2] as "draft" | "validate" | "publish" | "versions" | "fixtures" | "test" | "test-runs" | "shadow-runs" | undefined,
3665
+ );
3666
+ const workflowRunRoute = /^\/v1\/admin\/workflow-runs\/([^/]+)(?:\/(pause|resume|terminate|events|reconcile))?$/u.exec(url.pathname);
3667
+ if (workflowRunRoute) return handleAdminWorkflowRun(
3668
+ request,
3669
+ env,
3670
+ decodeURIComponent(workflowRunRoute[1] ?? ""),
3671
+ workflowRunRoute[2] as "pause" | "resume" | "terminate" | "events" | "reconcile" | undefined,
3672
+ );
3673
+ const actionProposalRoute = /^\/v1\/admin\/actions\/([^/]+)(?:\/(approve|reject|retry|compensate))?$/u.exec(url.pathname);
3674
+ if (actionProposalRoute) return handleAdminActionProposal(
3675
+ request,
3676
+ env,
3677
+ decodeURIComponent(actionProposalRoute[1] ?? ""),
3678
+ actionProposalRoute[2] as "approve" | "reject" | "retry" | "compensate" | undefined,
3679
+ );
3680
+ const decisionRoute = /^\/v1\/admin\/decisions\/([^/]+)$/u.exec(url.pathname);
3681
+ if (decisionRoute) return handleAdminDecision(request, env, decodeURIComponent(decisionRoute[1] ?? ""));
3682
+ const operationsIncidentRoute = /^\/v1\/admin\/operations\/incidents\/([^/]+)(?:\/(acknowledge|resolve|retry))?$/u.exec(url.pathname);
3683
+ if (operationsIncidentRoute) return handleAdminOperationsIncident(
3684
+ request,
3685
+ env,
3686
+ decodeURIComponent(operationsIncidentRoute[1] ?? ""),
3687
+ operationsIncidentRoute[2] as "acknowledge" | "resolve" | "retry" | undefined,
3688
+ );
3689
+ const reconciliationRoute = /^\/v1\/admin\/operations\/reconciliation\/([^/]+)$/u.exec(url.pathname);
3690
+ if (reconciliationRoute) return handleAdminReconciliation(request, env, decodeURIComponent(reconciliationRoute[1] ?? ""));
3691
+ const assistantSessionRoute = /^\/v1\/admin\/assistant\/sessions\/([^/]+)(?:\/(messages))?$/u.exec(url.pathname);
3692
+ if (assistantSessionRoute) return handleAdminAssistantSession(
3693
+ request,
3694
+ env,
3695
+ decodeURIComponent(assistantSessionRoute[1] ?? ""),
3696
+ assistantSessionRoute[2] === "messages",
3697
+ );
3698
+ const assistantSocketRoute = /^\/v1\/admin\/assistant\/sessions\/([^/]+)\/socket$/u.exec(url.pathname);
3699
+ if (assistantSocketRoute) return handleAdminAssistantSocket(
3700
+ request,
3701
+ env,
3702
+ decodeURIComponent(assistantSocketRoute[1] ?? ""),
3703
+ );
3704
+ const assistantToolRoute = /^\/v1\/admin\/assistant\/tool-calls\/([^/]+)$/u.exec(url.pathname);
3705
+ if (assistantToolRoute) return handleAdminAssistantToolCall(request, env, decodeURIComponent(assistantToolRoute[1] ?? ""));
3706
+ const assistantDraftRoute = /^\/v1\/admin\/assistant\/drafts\/([^/]+)\/adopt$/u.exec(url.pathname);
3707
+ if (assistantDraftRoute) return handleAdminAssistantDraftAdopt(request, env, decodeURIComponent(assistantDraftRoute[1] ?? ""));
3708
+ const qualitySampleRoute = /^\/v1\/admin\/ai\/quality-samples\/([^/]+)(?:\/(claim|review))?$/u.exec(url.pathname);
3709
+ if (qualitySampleRoute) {
3710
+ const sampleId = decodeURIComponent(qualitySampleRoute[1] ?? "");
3711
+ if (qualitySampleRoute[2] === "claim") return handleAdminAiQualityClaim(request, env, sampleId);
3712
+ if (qualitySampleRoute[2] === "review") return handleAdminAiQualityReview(request, env, sampleId);
3713
+ return handleAdminAiQualitySample(request, env, sampleId);
3714
+ }
3715
+ const aiRunRoute = /^\/v1\/admin\/ai\/runs\/([^/]+)$/u.exec(url.pathname);
3716
+ if (aiRunRoute) return handleAdminAiRun(request, env, decodeURIComponent(aiRunRoute[1] ?? ""));
3717
+ const shadowComparisonRoute = /^\/v1\/admin\/shadow-comparisons\/([^/]+)\/(dismiss|create-task)$/u.exec(url.pathname);
3718
+ if (shadowComparisonRoute) return handleAdminShadowComparison(
3719
+ request,
3720
+ env,
3721
+ decodeURIComponent(shadowComparisonRoute[1] ?? ""),
3722
+ shadowComparisonRoute[2] as "dismiss" | "create-task",
3723
+ );
3724
+ const taskRoute = /^\/v1\/admin\/tasks\/([^/]+)(?:\/(claim|respond|response|reassign|cancel))?$/u.exec(url.pathname);
3725
+ if (taskRoute) return handleAdminHumanTask(
3726
+ request,
3727
+ env,
3728
+ decodeURIComponent(taskRoute[1]!),
3729
+ taskRoute[2] as "claim" | "respond" | "response" | "reassign" | "cancel" | undefined,
3730
+ );
3731
+ const reportEvidenceRoute = /^\/v1\/admin\/reports\/([^/]+)\/evidence(?:\/(fetch|snapshots))?$/u.exec(url.pathname);
3732
+ if (reportEvidenceRoute) return handleAdminReportEvidence(
3733
+ request, env, ctx, decodeURIComponent(reportEvidenceRoute[1] ?? ""),
3734
+ reportEvidenceRoute[2] as "fetch" | "snapshots" | undefined,
3735
+ );
3736
+ const reportEnrichmentRoute = /^\/v1\/admin\/reports\/([^/]+)\/enrichments\/([^/]+)\/runs$/u.exec(url.pathname);
3737
+ if (reportEnrichmentRoute) return handleAdminReportEnrichment(
3738
+ request,
3739
+ env,
3740
+ decodeURIComponent(reportEnrichmentRoute[1] ?? ""),
3741
+ decodeURIComponent(reportEnrichmentRoute[2] ?? ""),
3742
+ );
3743
+ const reportRoute = /^\/v1\/admin\/reports\/([^/]+)(?:\/(claim|messages|decisions|workflow-runs|reassign|timeline|findings|actions|close|reopen))?$/u.exec(url.pathname);
3744
+ if (reportRoute) {
3745
+ const reportId = decodeURIComponent(reportRoute[1] ?? "");
3746
+ if (reportRoute[2] === "claim") return handleAdminReportClaim(request, env, ctx, reportId);
3747
+ if (reportRoute[2] === "messages") return handleAdminReportMessage(request, env, ctx, reportId);
3748
+ if (reportRoute[2] === "decisions") return handleAdminReportDecision(request, env, ctx, reportId);
3749
+ if (reportRoute[2] === "workflow-runs") return handleAdminReportWorkflowRuns(request, env, ctx, reportId);
3750
+ if (reportRoute[2] === "reassign") return handleAdminReportReassign(request, env, ctx, reportId);
3751
+ if (reportRoute[2] === "timeline" || reportRoute[2] === "findings" || reportRoute[2] === "actions") {
3752
+ return handleAdminReportCollection(request, env, reportId, reportRoute[2]);
3753
+ }
3754
+ if (reportRoute[2] === "close" || reportRoute[2] === "reopen") {
3755
+ return handleAdminReportCloseOrReopen(request, env, ctx, reportId, reportRoute[2]);
3756
+ }
3757
+ return handleAdminReportDetail(request, env, reportId);
3758
+ }
3759
+ const appealClaimRoute = /^\/v1\/admin\/appeals\/([^/]+)\/claim$/u.exec(url.pathname);
3760
+ if (appealClaimRoute) return handleAdminAppealClaim(request, env, decodeURIComponent(appealClaimRoute[1] ?? ""));
3761
+ const appealDecisionRoute = /^\/v1\/admin\/appeals\/([^/]+)\/decision$/u.exec(url.pathname);
3762
+ if (appealDecisionRoute) return handleAdminAppealDecision(request, env, ctx, decodeURIComponent(appealDecisionRoute[1] ?? ""));
3763
+ const appealDetailRoute = /^\/v1\/admin\/appeals\/([^/]+)$/u.exec(url.pathname);
3764
+ if (appealDetailRoute) return handleAdminAppealDetail(request, env, decodeURIComponent(appealDetailRoute[1] ?? ""));
3765
+ if (url.pathname.startsWith("/v1/")) throw new ApiError(404, "route_not_found", "The API route does not exist.");
3766
+ if (request.method !== "GET" && request.method !== "HEAD") return methodNotAllowed(["GET", "HEAD"]);
3767
+ return env.ASSETS.fetch(request);
3768
+ }
3769
+
3770
+ function isReportJob(value: unknown): value is ReportJob {
3771
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
3772
+ const job = value as Partial<ReportJob>;
3773
+ return job.version === 1 && job.type === "route_report" && typeof job.jobId === "string"
3774
+ && job.jobId.length <= 200 && typeof job.reportId === "string" && job.reportId.length <= 100;
3775
+ }
3776
+
3777
+ function isDeliveryJob(value: unknown): value is DeliveryJob {
3778
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
3779
+ const job = value as Partial<DeliveryJob>;
3780
+ return job.version === 1 && (job.type === "deliver_action" || job.type === "deliver_notification" || job.type === "deliver_email")
3781
+ && typeof job.jobId === "string" && typeof job.outboxId === "string";
3782
+ }
3783
+
3784
+ function isAiJob(value: unknown): value is AiJob {
3785
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
3786
+ const job = value as Partial<AiJob>;
3787
+ return job.version === 1 && job.type === "process_ai_report" && typeof job.jobId === "string"
3788
+ && job.jobId.length <= 300 && typeof job.reportId === "string" && job.reportId.length <= 100;
3789
+ }
3790
+
3791
+ function isWorkflowDispatchJob(value: unknown): value is WorkflowDispatchJob {
3792
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
3793
+ const job = value as Partial<WorkflowDispatchJob>;
3794
+ return job.version === 1 && job.type === "dispatch_workflow"
3795
+ && typeof job.jobId === "string" && job.jobId.length <= 300
3796
+ && typeof job.runId === "string" && job.runId.length <= 100
3797
+ && typeof job.outboxId === "string" && job.outboxId.length <= 100;
3798
+ }
3799
+
3800
+ function isEvidenceFetchJob(value: unknown): value is EvidenceFetchJob {
3801
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
3802
+ const job = value as Partial<EvidenceFetchJob>;
3803
+ return job.version === 1 && job.type === "fetch_evidence"
3804
+ && typeof job.jobId === "string" && job.jobId.length <= 300
3805
+ && typeof job.reportId === "string" && job.reportId.length <= 100
3806
+ && typeof job.requestId === "string" && job.requestId.length <= 100;
3807
+ }
3808
+
3809
+ function isActionProposalJob(value: unknown): value is ActionProposalJob {
3810
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
3811
+ const job = value as Partial<ActionProposalJob>;
3812
+ return job.version === 1 && job.type === "apply_action_proposal"
3813
+ && typeof job.jobId === "string" && job.jobId.length <= 600
3814
+ && typeof job.proposalId === "string" && job.proposalId.length <= 500
3815
+ && typeof job.runId === "string" && job.runId.length <= 100;
3816
+ }
3817
+
3818
+ async function handleQueue(batch: MessageBatch<unknown>, env: Env, ctx: ExecutionContext): Promise<void> {
3819
+ for (const message of batch.messages) {
3820
+ try {
3821
+ if (batch.queue === env.OPERATIONS_DLQ_NAME) {
3822
+ const job = isReportJob(message.body) || isAiJob(message.body) || isEvidenceFetchJob(message.body)
3823
+ || isWorkflowDispatchJob(message.body) || isDeliveryJob(message.body) || isActionProposalJob(message.body) ? message.body : null;
3824
+ await recordDeadLetter(env.DB, batch.queue, message.id, job);
3825
+ message.ack();
3826
+ continue;
3827
+ }
3828
+ if (isWorkflowDispatchJob(message.body)) {
3829
+ await processWorkflowDispatchJob(env, ctx, message.body);
3830
+ message.ack();
3831
+ continue;
3832
+ }
3833
+ if (isEvidenceFetchJob(message.body)) {
3834
+ await processEvidenceFetch(env, message.body);
3835
+ message.ack();
3836
+ continue;
3837
+ }
3838
+ if (isReportJob(message.body)) {
3839
+ try {
3840
+ const jobs = await processRoutingJob(
3841
+ env.DB,
3842
+ message.body.jobId,
3843
+ configuredAiBinding(env),
3844
+ (input) => executeRoutingReadOnlyComponent(env, input),
3845
+ );
3846
+ if (jobs.length) {
3847
+ try {
3848
+ await env.REPORT_JOBS.sendBatch(jobs.map((job) => ({ body: job, contentType: "json" as const })));
3849
+ await Promise.all(jobs.filter(isAiJob).map((job) => markAiJobEnqueued(env.DB, job.jobId, true)));
3850
+ } catch (error) {
3851
+ console.error("post-routing job enqueue failed; scheduled reconciliation will retry", {
3852
+ jobCount: jobs.length,
3853
+ errorType: error instanceof Error ? error.name : typeof error,
3854
+ });
3855
+ await Promise.all(jobs.filter(isAiJob).map((job) => markAiJobEnqueued(env.DB, job.jobId, false)));
3856
+ }
3857
+ }
3858
+ message.ack();
3859
+ } catch (error) {
3860
+ await markJobEnqueueFailed(env.DB, message.body.jobId, "routing_processing_failed");
3861
+ throw error;
3862
+ }
3863
+ continue;
3864
+ }
3865
+ if (isAiJob(message.body)) {
3866
+ let jobs: DeliveryJob[];
3867
+ try {
3868
+ jobs = await processAiJob(env.DB, configuredAiBinding(env), message.body, deliveryOptions(env));
3869
+ } catch (error) {
3870
+ if (error instanceof RetryableAiError) throw error;
3871
+ await markAiJobUnexpectedFailure(env.DB, message.body.jobId);
3872
+ throw new RetryableAiError(30);
3873
+ }
3874
+ if (jobs.length) {
3875
+ try {
3876
+ await env.DELIVERY_JOBS.sendBatch(jobs.map((job) => ({ body: job, contentType: "json" as const })));
3877
+ } catch (error) {
3878
+ console.error("AI notice enqueue failed; delivery reconciliation will retry", {
3879
+ jobCount: jobs.length,
3880
+ errorType: error instanceof Error ? error.name : typeof error,
3881
+ });
3882
+ }
3883
+ }
3884
+ message.ack();
3885
+ continue;
3886
+ }
3887
+ if (isDeliveryJob(message.body)) {
3888
+ await processDeliveryJob(env.DB, message.body, env);
3889
+ message.ack();
3890
+ continue;
3891
+ }
3892
+ if (isActionProposalJob(message.body)) {
3893
+ await processActionProposalJob(env, message.body);
3894
+ message.ack();
3895
+ continue;
3896
+ }
3897
+ console.error("invalid queue message rejected", { queue: batch.queue, messageId: message.id });
3898
+ message.ack();
3899
+ } catch (error) {
3900
+ console.error("queue message processing failed", {
3901
+ queue: batch.queue,
3902
+ messageId: message.id,
3903
+ errorType: error instanceof Error ? error.name : typeof error,
3904
+ });
3905
+ if (error instanceof RetryableDeliveryError || error instanceof RetryableAiError
3906
+ || error instanceof RetryableWorkflowDispatchError
3907
+ || error instanceof RetryableEvidenceError || error instanceof RetryableActionProposalError) {
3908
+ message.retry({ delaySeconds: error.delaySeconds });
3909
+ }
3910
+ else message.retry();
3911
+ }
3912
+ }
3913
+ }
3914
+
3915
+ interface ReconciliationResult {
3916
+ scanned: number;
3917
+ repaired: number;
3918
+ errorCode?: string;
3919
+ }
3920
+
3921
+ async function reconcileRouting(env: Env): Promise<ReconciliationResult> {
3922
+ const jobs = await pendingRoutingJobs(env.DB, 50);
3923
+ if (!jobs.length) return { scanned: 0, repaired: 0 };
3924
+ try {
3925
+ await env.REPORT_JOBS.sendBatch(jobs.map((job) => ({
3926
+ body: { version: 1, jobId: job.id, type: "route_report", reportId: job.reportId } satisfies ReportJob,
3927
+ contentType: "json" as const,
3928
+ })));
3929
+ await Promise.all(jobs.map((job) => markJobEnqueued(env.DB, job.id)));
3930
+ return { scanned: jobs.length, repaired: jobs.length };
3931
+ } catch (error) {
3932
+ console.error("routing reconciliation enqueue failed", { jobCount: jobs.length, errorType: error instanceof Error ? error.name : typeof error });
3933
+ await Promise.all(jobs.map((job) => markJobEnqueueFailed(env.DB, job.id, "reconciliation_queue_send_failed")));
3934
+ return { scanned: jobs.length, repaired: 0, errorCode: "routing_reconciliation_enqueue_failed" };
3935
+ }
3936
+ }
3937
+
3938
+ async function reconcileDeliveries(env: Env): Promise<ReconciliationResult> {
3939
+ const [jobs, actionJobs] = await Promise.all([
3940
+ pendingDeliveryJobs(env.DB, 50),
3941
+ pendingActionProposalJobs(env.DB, 50),
3942
+ ]);
3943
+ const eventRepairs = await reconcileActionProposalEvents(env, 50);
3944
+ if (!jobs.length && !actionJobs.length) return { scanned: eventRepairs, repaired: eventRepairs };
3945
+ try {
3946
+ await env.DELIVERY_JOBS.sendBatch([...jobs, ...actionJobs].map((job) => ({ body: job, contentType: "json" as const })));
3947
+ return { scanned: jobs.length + actionJobs.length + eventRepairs, repaired: jobs.length + actionJobs.length + eventRepairs };
3948
+ } catch (error) {
3949
+ console.error("delivery reconciliation enqueue failed", {
3950
+ jobCount: jobs.length + actionJobs.length,
3951
+ errorType: error instanceof Error ? error.name : typeof error,
3952
+ });
3953
+ return { scanned: jobs.length + actionJobs.length + eventRepairs, repaired: eventRepairs, errorCode: "delivery_reconciliation_enqueue_failed" };
3954
+ }
3955
+ }
3956
+
3957
+ async function reconcileEvidenceFetches(env: Env): Promise<ReconciliationResult> {
3958
+ const jobs = await pendingEvidenceFetchJobs(env.DB, 50);
3959
+ if (!jobs.length) return { scanned: 0, repaired: 0 };
3960
+ try {
3961
+ await env.REPORT_JOBS.sendBatch(jobs.map((job) => ({ body: job, contentType: "json" as const })));
3962
+ return { scanned: jobs.length, repaired: jobs.length };
3963
+ } catch (error) {
3964
+ console.error("evidence fetch reconciliation enqueue failed", { jobCount: jobs.length, errorType: error instanceof Error ? error.name : typeof error });
3965
+ return { scanned: jobs.length, repaired: 0, errorCode: "evidence_reconciliation_enqueue_failed" };
3966
+ }
3967
+ }
3968
+
3969
+ async function reconcileAi(env: Env): Promise<ReconciliationResult> {
3970
+ const jobs = await pendingAiJobs(env.DB, 50);
3971
+ if (!jobs.length) return { scanned: 0, repaired: 0 };
3972
+ try {
3973
+ await env.REPORT_JOBS.sendBatch(jobs.map((job) => ({ body: job, contentType: "json" as const })));
3974
+ await Promise.all(jobs.map((job) => markAiJobEnqueued(env.DB, job.jobId, true)));
3975
+ return { scanned: jobs.length, repaired: jobs.length };
3976
+ } catch (error) {
3977
+ console.error("AI reconciliation enqueue failed", { jobCount: jobs.length, errorType: error instanceof Error ? error.name : typeof error });
3978
+ await Promise.all(jobs.map((job) => markAiJobEnqueued(env.DB, job.jobId, false)));
3979
+ return { scanned: jobs.length, repaired: 0, errorCode: "ai_reconciliation_enqueue_failed" };
3980
+ }
3981
+ }
3982
+
3983
+ async function reconcileWorkflowDispatches(env: Env): Promise<ReconciliationResult> {
3984
+ const jobs = await pendingWorkflowDispatchJobs(env.DB, 50);
3985
+ if (!jobs.length) return { scanned: 0, repaired: 0 };
3986
+ try {
3987
+ await env.CONTROL_JOBS.sendBatch(jobs.map((job) => ({ body: job, contentType: "json" as const })));
3988
+ return { scanned: jobs.length, repaired: jobs.length };
3989
+ } catch (error) {
3990
+ console.error("workflow dispatch reconciliation enqueue failed", {
3991
+ jobCount: jobs.length,
3992
+ errorType: error instanceof Error ? error.name : typeof error,
3993
+ });
3994
+ return { scanned: jobs.length, repaired: 0, errorCode: "workflow_reconciliation_enqueue_failed" };
3995
+ }
3996
+ }
3997
+
3998
+ const RECONCILIATION_KINDS = ["routing", "evidence", "ai", "workflows", "deliveries", "budgets", "all"] as const;
3999
+ type ReconciliationKind = typeof RECONCILIATION_KINDS[number];
4000
+
4001
+ async function performReconciliation(
4002
+ env: Env,
4003
+ kind: ReconciliationKind,
4004
+ reconciliationRunId: string,
4005
+ actorId: string,
4006
+ ): Promise<ReconciliationResult> {
4007
+ if (kind === "routing") return reconcileRouting(env);
4008
+ if (kind === "evidence") {
4009
+ const stale = await repairStaleOperationalLeases(env.DB, reconciliationRunId, actorId);
4010
+ const queued = await reconcileEvidenceFetches(env);
4011
+ return { scanned: stale.scanned + queued.scanned, repaired: stale.repaired + queued.repaired, ...(queued.errorCode ? { errorCode: queued.errorCode } : {}) };
4012
+ }
4013
+ if (kind === "ai") return reconcileAi(env);
4014
+ if (kind === "workflows") {
4015
+ const stale = await repairStaleOperationalLeases(env.DB, reconciliationRunId, actorId);
4016
+ const queued = await reconcileWorkflowDispatches(env);
4017
+ return { scanned: stale.scanned + queued.scanned, repaired: stale.repaired + queued.repaired, ...(queued.errorCode ? { errorCode: queued.errorCode } : {}) };
4018
+ }
4019
+ if (kind === "deliveries") return reconcileDeliveries(env);
4020
+ if (kind === "budgets") {
4021
+ const repaired = await releaseExpiredBudgetReservations(env.DB);
4022
+ return { scanned: repaired, repaired };
4023
+ }
4024
+ const stale = await repairStaleOperationalLeases(env.DB, reconciliationRunId, actorId);
4025
+ const results = await Promise.all([
4026
+ reconcileRouting(env), reconcileEvidenceFetches(env), reconcileAi(env),
4027
+ reconcileWorkflowDispatches(env), reconcileDeliveries(env),
4028
+ ]);
4029
+ const expiredBudgets = await releaseExpiredBudgetReservations(env.DB);
4030
+ return {
4031
+ scanned: results.reduce((sum, result) => sum + result.scanned, expiredBudgets + stale.scanned),
4032
+ repaired: results.reduce((sum, result) => sum + result.repaired, expiredBudgets + stale.repaired),
4033
+ ...(results.some((result) => result.errorCode) ? { errorCode: "one_or_more_reconciliation_kinds_failed" } : {}),
4034
+ };
4035
+ }
4036
+
4037
+ async function runRecordedReconciliation(env: Env, kind: ReconciliationKind, actorId: string): Promise<{ runId: string } & ReconciliationResult> {
4038
+ const runId = await beginReconciliationRun(env.DB, kind, actorId);
4039
+ try {
4040
+ const result = await performReconciliation(env, kind, runId, actorId);
4041
+ await finishReconciliationRun(env.DB, runId, result);
4042
+ return { runId, ...result };
4043
+ } catch (error) {
4044
+ await finishReconciliationRun(env.DB, runId, { scanned: 0, repaired: 0, errorCode: "reconciliation_failed" });
4045
+ throw error;
4046
+ }
4047
+ }
4048
+
4049
+ async function handleAdminReconciliation(request: Request, env: Env, rawKind: string): Promise<Response> {
4050
+ if (request.method !== "POST") return methodNotAllowed(["POST"]);
4051
+ const session = await requireOperator(request, env, "operateSystem");
4052
+ if (!RECONCILIATION_KINDS.includes(rawKind as ReconciliationKind)) {
4053
+ throw new ApiError(400, "reconciliation_kind_invalid", "The reconciliation kind is invalid.");
4054
+ }
4055
+ const result = await runRecordedReconciliation(env, rawKind as ReconciliationKind, session.actor.id);
4056
+ return json({ schema_version: "1", reconciliation: result }, { status: 202 });
4057
+ }
4058
+
4059
+ export default {
4060
+ async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
4061
+ const requestedOrigin = request.headers.get("origin");
4062
+ let origin = allowedOrigin(requestedOrigin, env.ALLOWED_ORIGINS);
4063
+ const pathname = new URL(request.url).pathname;
4064
+ if (!origin && requestedOrigin && ["/v1/widget/config", "/v1/reports"].includes(pathname)
4065
+ && await installationAllowsWidgetOrigin(env.DB, requestedOrigin)) origin = requestedOrigin;
4066
+ const requestId = request.headers.get("cf-ray")?.slice(0, 200) || crypto.randomUUID();
4067
+ const finish = (response: Response): Response => {
4068
+ const withOrigin = withCors(response, origin);
4069
+ const headers = new Headers(withOrigin.headers);
4070
+ headers.set("request-id", requestId);
4071
+ return new Response(withOrigin.body, {
4072
+ status: withOrigin.status,
4073
+ statusText: withOrigin.statusText,
4074
+ headers,
4075
+ webSocket: withOrigin.webSocket,
4076
+ });
4077
+ };
4078
+ if (request.method === "OPTIONS") {
4079
+ if (!origin) return finish(json({ error: { code: "origin_not_allowed", message: "The request origin is not allowed." } }, { status: 403 }));
4080
+ return finish(new Response(null, { status: 204 }));
4081
+ }
4082
+ try {
4083
+ return finish(await route(request, env, ctx));
4084
+ } catch (error) {
4085
+ return finish(apiErrorResponse(error));
4086
+ }
4087
+ },
4088
+
4089
+ async queue(batch: MessageBatch<unknown>, env: Env, ctx: ExecutionContext): Promise<void> {
4090
+ await handleQueue(batch, env, ctx);
4091
+ },
4092
+
4093
+ async scheduled(_controller: ScheduledController, env: Env, ctx: ExecutionContext): Promise<void> {
4094
+ ctx.waitUntil((async () => {
4095
+ await Promise.all([
4096
+ runRecordedReconciliation(env, "all", "system:maintenance"), escalateExpiredAiWaits(env.DB),
4097
+ maintainIntakeProtection(env.DB),
4098
+ pruneReportContexts(env.DB),
4099
+ compareCompletedShadowRuns(env.DB),
4100
+ expireExports(env),
4101
+ pruneConnectionOAuthStates(env.DB),
4102
+ ]);
4103
+ await pruneStaleAnalystSessions(env.DB);
4104
+ await chainPendingAuditEvents(env.DB);
4105
+ await refreshDailyAggregatesIfStale(env.DB);
4106
+ await processDeletionRequests(env.DB);
4107
+ await runRetentionIfDue(
4108
+ env.DB,
4109
+ integer(env.MESSAGE_RETENTION_DAYS, 180, 1, 3_650),
4110
+ integer(env.REPORT_RETENTION_DAYS, 365, 30, 3_650),
4111
+ );
4112
+ })());
4113
+ },
4114
+ };