@purposeinplay/payload-ai-translate 0.1.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 (301) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +714 -0
  3. package/dist/alerts-collection.d.ts +21 -0
  4. package/dist/alerts-collection.js +159 -0
  5. package/dist/api.d.ts +4 -0
  6. package/dist/api.js +918 -0
  7. package/dist/bulk-translate-batches-collection.d.ts +29 -0
  8. package/dist/bulk-translate-batches-collection.js +404 -0
  9. package/dist/bulk-translate-units-collection.d.ts +35 -0
  10. package/dist/bulk-translate-units-collection.js +310 -0
  11. package/dist/client/estimated-cost-cell.d.ts +6 -0
  12. package/dist/client/estimated-cost-cell.js +12 -0
  13. package/dist/client/excluded-fields-field.d.ts +45 -0
  14. package/dist/client/excluded-fields-field.js +553 -0
  15. package/dist/client/field-translate-button.d.ts +6 -0
  16. package/dist/client/field-translate-button.js +199 -0
  17. package/dist/client/index.d.ts +6 -0
  18. package/dist/client/index.js +6 -0
  19. package/dist/client/lib/use-global-kill-switches.d.ts +20 -0
  20. package/dist/client/lib/use-global-kill-switches.js +58 -0
  21. package/dist/client/translate-button.d.ts +2 -0
  22. package/dist/client/translate-button.js +228 -0
  23. package/dist/client/translate-modal.d.ts +16 -0
  24. package/dist/client/translate-modal.js +549 -0
  25. package/dist/client/translation-progress.d.ts +10 -0
  26. package/dist/client/translation-progress.js +297 -0
  27. package/dist/components/TranslationNavGroup.d.ts +45 -0
  28. package/dist/components/TranslationNavGroup.js +104 -0
  29. package/dist/defaults.d.ts +11 -0
  30. package/dist/defaults.js +16 -0
  31. package/dist/endpoints/client-config.d.ts +44 -0
  32. package/dist/endpoints/client-config.js +145 -0
  33. package/dist/endpoints/estimate.d.ts +5 -0
  34. package/dist/endpoints/estimate.js +237 -0
  35. package/dist/endpoints/progress.d.ts +2 -0
  36. package/dist/endpoints/progress.js +314 -0
  37. package/dist/endpoints/translate.d.ts +11 -0
  38. package/dist/endpoints/translate.js +376 -0
  39. package/dist/endpoints/translation-hub/_helpers.d.ts +140 -0
  40. package/dist/endpoints/translation-hub/_helpers.js +297 -0
  41. package/dist/endpoints/translation-hub/active.d.ts +21 -0
  42. package/dist/endpoints/translation-hub/active.js +220 -0
  43. package/dist/endpoints/translation-hub/cancel.d.ts +22 -0
  44. package/dist/endpoints/translation-hub/cancel.js +233 -0
  45. package/dist/endpoints/translation-hub/enqueue.d.ts +70 -0
  46. package/dist/endpoints/translation-hub/enqueue.js +529 -0
  47. package/dist/endpoints/translation-hub/failures.d.ts +12 -0
  48. package/dist/endpoints/translation-hub/failures.js +67 -0
  49. package/dist/endpoints/translation-hub/force-reset.d.ts +20 -0
  50. package/dist/endpoints/translation-hub/force-reset.js +144 -0
  51. package/dist/endpoints/translation-hub/index.d.ts +21 -0
  52. package/dist/endpoints/translation-hub/index.js +20 -0
  53. package/dist/endpoints/translation-hub/list.d.ts +40 -0
  54. package/dist/endpoints/translation-hub/list.js +182 -0
  55. package/dist/endpoints/translation-hub/preflight.d.ts +19 -0
  56. package/dist/endpoints/translation-hub/preflight.js +141 -0
  57. package/dist/endpoints/translation-hub/retry-failed.d.ts +38 -0
  58. package/dist/endpoints/translation-hub/retry-failed.js +235 -0
  59. package/dist/endpoints/translation-hub/revert.d.ts +88 -0
  60. package/dist/endpoints/translation-hub/revert.js +405 -0
  61. package/dist/endpoints/translation-hub/status.d.ts +45 -0
  62. package/dist/endpoints/translation-hub/status.js +391 -0
  63. package/dist/endpoints/translation-hub/usage-summary.d.ts +114 -0
  64. package/dist/endpoints/translation-hub/usage-summary.js +481 -0
  65. package/dist/exports/client.d.ts +6 -0
  66. package/dist/exports/client.js +6 -0
  67. package/dist/exports/components.d.ts +6 -0
  68. package/dist/exports/components.js +5 -0
  69. package/dist/exports/index.d.ts +8 -0
  70. package/dist/exports/index.js +7 -0
  71. package/dist/exports/providers.d.ts +9 -0
  72. package/dist/exports/providers.js +5 -0
  73. package/dist/exports/views-client.d.ts +23 -0
  74. package/dist/exports/views-client.js +22 -0
  75. package/dist/exports/views.d.ts +30 -0
  76. package/dist/exports/views.js +29 -0
  77. package/dist/hooks/after-change-global.d.ts +4 -0
  78. package/dist/hooks/after-change-global.js +109 -0
  79. package/dist/hooks/after-change.d.ts +16 -0
  80. package/dist/hooks/after-change.js +205 -0
  81. package/dist/hooks/after-delete.d.ts +30 -0
  82. package/dist/hooks/after-delete.js +95 -0
  83. package/dist/index.d.ts +5 -0
  84. package/dist/index.js +5 -0
  85. package/dist/jobs-collection.d.ts +17 -0
  86. package/dist/jobs-collection.js +139 -0
  87. package/dist/lexical/classifier.d.ts +3 -0
  88. package/dist/lexical/classifier.js +108 -0
  89. package/dist/lexical/deserializer.d.ts +4 -0
  90. package/dist/lexical/deserializer.js +263 -0
  91. package/dist/lexical/placeholder-integrity.d.ts +6 -0
  92. package/dist/lexical/placeholder-integrity.js +21 -0
  93. package/dist/lexical/placeholders.d.ts +21 -0
  94. package/dist/lexical/placeholders.js +117 -0
  95. package/dist/lexical/serializer.d.ts +21 -0
  96. package/dist/lexical/serializer.js +233 -0
  97. package/dist/lexical/types.d.ts +32 -0
  98. package/dist/lexical/types.js +1 -0
  99. package/dist/lib/auth-diagnostics.d.ts +14 -0
  100. package/dist/lib/auth-diagnostics.js +19 -0
  101. package/dist/lib/batch-counts.d.ts +58 -0
  102. package/dist/lib/batch-counts.js +105 -0
  103. package/dist/lib/bulk-translate-migrations.d.ts +92 -0
  104. package/dist/lib/bulk-translate-migrations.js +153 -0
  105. package/dist/lib/coalescing-queue.d.ts +38 -0
  106. package/dist/lib/coalescing-queue.js +69 -0
  107. package/dist/lib/content-extractor.d.ts +16 -0
  108. package/dist/lib/content-extractor.js +410 -0
  109. package/dist/lib/content-hash.d.ts +1 -0
  110. package/dist/lib/content-hash.js +19 -0
  111. package/dist/lib/content-patcher.d.ts +15 -0
  112. package/dist/lib/content-patcher.js +293 -0
  113. package/dist/lib/cost-guards.d.ts +2 -0
  114. package/dist/lib/cost-guards.js +18 -0
  115. package/dist/lib/daily-spend-cap.d.ts +58 -0
  116. package/dist/lib/daily-spend-cap.js +233 -0
  117. package/dist/lib/effective-locales.d.ts +181 -0
  118. package/dist/lib/effective-locales.js +302 -0
  119. package/dist/lib/error-messages.d.ts +245 -0
  120. package/dist/lib/error-messages.js +626 -0
  121. package/dist/lib/events.d.ts +39 -0
  122. package/dist/lib/events.js +146 -0
  123. package/dist/lib/exclude-fields.d.ts +3 -0
  124. package/dist/lib/exclude-fields.js +64 -0
  125. package/dist/lib/field-breadcrumb.d.ts +31 -0
  126. package/dist/lib/field-breadcrumb.js +227 -0
  127. package/dist/lib/field-diff.d.ts +1 -0
  128. package/dist/lib/field-diff.js +25 -0
  129. package/dist/lib/field-empty.d.ts +2 -0
  130. package/dist/lib/field-empty.js +68 -0
  131. package/dist/lib/field-resolver.d.ts +3 -0
  132. package/dist/lib/field-resolver.js +164 -0
  133. package/dist/lib/group-soft-skips.d.ts +39 -0
  134. package/dist/lib/group-soft-skips.js +45 -0
  135. package/dist/lib/locale-merge.d.ts +44 -0
  136. package/dist/lib/locale-merge.js +357 -0
  137. package/dist/lib/locale-row-check.d.ts +30 -0
  138. package/dist/lib/locale-row-check.js +64 -0
  139. package/dist/lib/logger.d.ts +74 -0
  140. package/dist/lib/logger.js +97 -0
  141. package/dist/lib/manual-edit-guard.d.ts +128 -0
  142. package/dist/lib/manual-edit-guard.js +393 -0
  143. package/dist/lib/output-validation.d.ts +48 -0
  144. package/dist/lib/output-validation.js +148 -0
  145. package/dist/lib/payload-read.d.ts +16 -0
  146. package/dist/lib/payload-read.js +51 -0
  147. package/dist/lib/per-doc-claim.d.ts +90 -0
  148. package/dist/lib/per-doc-claim.js +140 -0
  149. package/dist/lib/per-doc-lock.d.ts +94 -0
  150. package/dist/lib/per-doc-lock.js +119 -0
  151. package/dist/lib/persist-usage.d.ts +91 -0
  152. package/dist/lib/persist-usage.js +116 -0
  153. package/dist/lib/progress-store.d.ts +103 -0
  154. package/dist/lib/progress-store.js +314 -0
  155. package/dist/lib/rate-limiter.d.ts +3 -0
  156. package/dist/lib/rate-limiter.js +53 -0
  157. package/dist/lib/snapshot-select.d.ts +43 -0
  158. package/dist/lib/snapshot-select.js +108 -0
  159. package/dist/lib/translate-prompt.d.ts +31 -0
  160. package/dist/lib/translate-prompt.js +66 -0
  161. package/dist/lib/translation-token-bucket.d.ts +57 -0
  162. package/dist/lib/translation-token-bucket.js +365 -0
  163. package/dist/lib/truncate-source-value.d.ts +1 -0
  164. package/dist/lib/truncate-source-value.js +27 -0
  165. package/dist/manual-edit-collection.d.ts +22 -0
  166. package/dist/manual-edit-collection.js +124 -0
  167. package/dist/plugin.d.ts +3 -0
  168. package/dist/plugin.js +934 -0
  169. package/dist/providers/ai-sdk-adapter.d.ts +35 -0
  170. package/dist/providers/ai-sdk-adapter.js +100 -0
  171. package/dist/providers/anthropic.d.ts +31 -0
  172. package/dist/providers/anthropic.js +66 -0
  173. package/dist/providers/custom.d.ts +36 -0
  174. package/dist/providers/custom.js +24 -0
  175. package/dist/providers/gemini.d.ts +20 -0
  176. package/dist/providers/gemini.js +48 -0
  177. package/dist/providers/mock.d.ts +2 -0
  178. package/dist/providers/mock.js +29 -0
  179. package/dist/providers/openai.d.ts +28 -0
  180. package/dist/providers/openai.js +69 -0
  181. package/dist/settings-global.d.ts +74 -0
  182. package/dist/settings-global.js +216 -0
  183. package/dist/tasks/bulk-translate-coordinator.d.ts +115 -0
  184. package/dist/tasks/bulk-translate-coordinator.js +708 -0
  185. package/dist/tasks/bulk-translate-doc-task.d.ts +142 -0
  186. package/dist/tasks/bulk-translate-doc-task.js +1000 -0
  187. package/dist/tasks/bulk-translate-janitor.d.ts +87 -0
  188. package/dist/tasks/bulk-translate-janitor.js +311 -0
  189. package/dist/tasks/translate-job-task.d.ts +51 -0
  190. package/dist/tasks/translate-job-task.js +154 -0
  191. package/dist/translate.d.ts +113 -0
  192. package/dist/translate.js +911 -0
  193. package/dist/translation-daily-spend-collection.d.ts +24 -0
  194. package/dist/translation-daily-spend-collection.js +133 -0
  195. package/dist/translation-rate-limits-collection.d.ts +30 -0
  196. package/dist/translation-rate-limits-collection.js +144 -0
  197. package/dist/types.d.ts +672 -0
  198. package/dist/types.js +1 -0
  199. package/dist/usage-collection.d.ts +14 -0
  200. package/dist/usage-collection.js +377 -0
  201. package/dist/views/BulkRunsHub/BatchRow.d.ts +32 -0
  202. package/dist/views/BulkRunsHub/BatchRow.js +1222 -0
  203. package/dist/views/BulkRunsHub/BucketRow.d.ts +62 -0
  204. package/dist/views/BulkRunsHub/BucketRow.js +982 -0
  205. package/dist/views/BulkRunsHub/BulkRunsHub.client.d.ts +18 -0
  206. package/dist/views/BulkRunsHub/BulkRunsHub.client.js +331 -0
  207. package/dist/views/BulkRunsHub/EmptyState.d.ts +6 -0
  208. package/dist/views/BulkRunsHub/EmptyState.js +64 -0
  209. package/dist/views/BulkRunsHub/FilterBar.d.ts +16 -0
  210. package/dist/views/BulkRunsHub/FilterBar.js +284 -0
  211. package/dist/views/BulkRunsHub/InFlightBanner.d.ts +14 -0
  212. package/dist/views/BulkRunsHub/InFlightBanner.js +59 -0
  213. package/dist/views/BulkRunsHub/StatusBadge.d.ts +64 -0
  214. package/dist/views/BulkRunsHub/StatusBadge.js +248 -0
  215. package/dist/views/BulkRunsHub/SummaryStrip.d.ts +22 -0
  216. package/dist/views/BulkRunsHub/SummaryStrip.js +249 -0
  217. package/dist/views/BulkRunsHub/bucket-grouping.d.ts +200 -0
  218. package/dist/views/BulkRunsHub/bucket-grouping.js +344 -0
  219. package/dist/views/BulkRunsHub/bucketFailureSummary.d.ts +9 -0
  220. package/dist/views/BulkRunsHub/bucketFailureSummary.js +36 -0
  221. package/dist/views/BulkRunsHub/dedupedStatusFetch.d.ts +5 -0
  222. package/dist/views/BulkRunsHub/dedupedStatusFetch.js +45 -0
  223. package/dist/views/BulkRunsHub/index.d.ts +17 -0
  224. package/dist/views/BulkRunsHub/index.js +80 -0
  225. package/dist/views/BulkRunsHub/urlFilters.d.ts +14 -0
  226. package/dist/views/BulkRunsHub/urlFilters.js +50 -0
  227. package/dist/views/BulkRunsHub/useBulkRunsList.d.ts +26 -0
  228. package/dist/views/BulkRunsHub/useBulkRunsList.js +204 -0
  229. package/dist/views/BulkRunsHub/useUrlFilters.d.ts +10 -0
  230. package/dist/views/BulkRunsHub/useUrlFilters.js +88 -0
  231. package/dist/views/TranslationHub/ActiveJobs.d.ts +6 -0
  232. package/dist/views/TranslationHub/ActiveJobs.js +320 -0
  233. package/dist/views/TranslationHub/AdvancedPanel.d.ts +17 -0
  234. package/dist/views/TranslationHub/AdvancedPanel.js +996 -0
  235. package/dist/views/TranslationHub/AlertBanner.d.ts +6 -0
  236. package/dist/views/TranslationHub/AlertBanner.js +568 -0
  237. package/dist/views/TranslationHub/AuditPanel.d.ts +6 -0
  238. package/dist/views/TranslationHub/AuditPanel.helpers.d.ts +44 -0
  239. package/dist/views/TranslationHub/AuditPanel.helpers.js +71 -0
  240. package/dist/views/TranslationHub/AuditPanel.js +1367 -0
  241. package/dist/views/TranslationHub/BulkTranslate.types.d.ts +242 -0
  242. package/dist/views/TranslationHub/BulkTranslate.types.js +36 -0
  243. package/dist/views/TranslationHub/BulkTranslateFailureDrawer.d.ts +19 -0
  244. package/dist/views/TranslationHub/BulkTranslateFailureDrawer.js +332 -0
  245. package/dist/views/TranslationHub/BulkTranslateMonitor.d.ts +28 -0
  246. package/dist/views/TranslationHub/BulkTranslateMonitor.js +305 -0
  247. package/dist/views/TranslationHub/BulkTranslateNarrowViewportBanner.d.ts +3 -0
  248. package/dist/views/TranslationHub/BulkTranslateNarrowViewportBanner.js +42 -0
  249. package/dist/views/TranslationHub/BulkTranslatePostEnqueueTransition.d.ts +26 -0
  250. package/dist/views/TranslationHub/BulkTranslatePostEnqueueTransition.js +95 -0
  251. package/dist/views/TranslationHub/BulkTranslatePreflightModal.d.ts +22 -0
  252. package/dist/views/TranslationHub/BulkTranslatePreflightModal.js +879 -0
  253. package/dist/views/TranslationHub/BulkTranslateTerminalCard.d.ts +29 -0
  254. package/dist/views/TranslationHub/BulkTranslateTerminalCard.js +445 -0
  255. package/dist/views/TranslationHub/BulkTranslateTrigger.d.ts +66 -0
  256. package/dist/views/TranslationHub/BulkTranslateTrigger.js +161 -0
  257. package/dist/views/TranslationHub/EditorRecentRunsPanel.d.ts +33 -0
  258. package/dist/views/TranslationHub/EditorRecentRunsPanel.js +290 -0
  259. package/dist/views/TranslationHub/Hub.client.d.ts +74 -0
  260. package/dist/views/TranslationHub/Hub.client.js +357 -0
  261. package/dist/views/TranslationHub/ModelCombobox.d.ts +14 -0
  262. package/dist/views/TranslationHub/ModelCombobox.js +415 -0
  263. package/dist/views/TranslationHub/PerCollectionConfig.d.ts +10 -0
  264. package/dist/views/TranslationHub/PerCollectionConfig.helpers.d.ts +16 -0
  265. package/dist/views/TranslationHub/PerCollectionConfig.helpers.js +19 -0
  266. package/dist/views/TranslationHub/PerCollectionConfig.js +759 -0
  267. package/dist/views/TranslationHub/SettingsRail.d.ts +11 -0
  268. package/dist/views/TranslationHub/SettingsRail.js +382 -0
  269. package/dist/views/TranslationHub/StatusStrip.d.ts +6 -0
  270. package/dist/views/TranslationHub/StatusStrip.js +451 -0
  271. package/dist/views/TranslationHub/UsageTable.d.ts +6 -0
  272. package/dist/views/TranslationHub/UsageTable.helpers.d.ts +69 -0
  273. package/dist/views/TranslationHub/UsageTable.helpers.js +49 -0
  274. package/dist/views/TranslationHub/UsageTable.js +1240 -0
  275. package/dist/views/TranslationHub/alertGrouping.d.ts +70 -0
  276. package/dist/views/TranslationHub/alertGrouping.js +99 -0
  277. package/dist/views/TranslationHub/index.d.ts +20 -0
  278. package/dist/views/TranslationHub/index.js +109 -0
  279. package/dist/views/TranslationHub/tabNavigation.d.ts +53 -0
  280. package/dist/views/TranslationHub/tabNavigation.js +74 -0
  281. package/dist/views/TranslationHub/terminalBannerVisibility.d.ts +33 -0
  282. package/dist/views/TranslationHub/terminalBannerVisibility.js +124 -0
  283. package/dist/views/TranslationHub/useBulkTranslateActive.d.ts +49 -0
  284. package/dist/views/TranslationHub/useBulkTranslateActive.js +251 -0
  285. package/dist/views/TranslationHub/useFocusTrap.d.ts +6 -0
  286. package/dist/views/TranslationHub/useFocusTrap.js +81 -0
  287. package/dist/views/TranslationHub/useTranslationHubUsageSummary.d.ts +77 -0
  288. package/dist/views/TranslationHub/useTranslationHubUsageSummary.js +267 -0
  289. package/dist/views/shared/EditorError.d.ts +97 -0
  290. package/dist/views/shared/EditorError.js +205 -0
  291. package/dist/views/shared/ModelCell.d.ts +18 -0
  292. package/dist/views/shared/ModelCell.js +31 -0
  293. package/dist/views/shared/docHref.d.ts +16 -0
  294. package/dist/views/shared/docHref.js +26 -0
  295. package/dist/views/shared/fetch-error-body.d.ts +25 -0
  296. package/dist/views/shared/fetch-error-body.js +42 -0
  297. package/dist/views/shared/filterPillStyle.d.ts +35 -0
  298. package/dist/views/shared/filterPillStyle.js +40 -0
  299. package/dist/views/shared/format.d.ts +75 -0
  300. package/dist/views/shared/format.js +131 -0
  301. package/package.json +141 -0
@@ -0,0 +1,144 @@
1
+ import { DEFAULT_BULK_TRANSLATE_BATCHES_COLLECTION_SLUG } from '../../bulk-translate-batches-collection.js';
2
+ import { DEFAULT_BULK_TRANSLATE_UNITS_COLLECTION_SLUG } from '../../bulk-translate-units-collection.js';
3
+ import { createScopedLogger } from '../../lib/logger.js';
4
+ import { errorResponse, isAdminUser, unauthorizedResponse } from './_helpers.js';
5
+ import { extractBatchId } from './status.js';
6
+ // ---------------------------------------------------------------------------
7
+ // Handler
8
+ // ---------------------------------------------------------------------------
9
+ /**
10
+ * `POST /api/translation-hub/bulk-translate/:id/force-reset`
11
+ *
12
+ * Recovery action — F-SEC-CRON-RESET. Marks every `running` unit in
13
+ * the batch as `failed` with `failureCode: 'transient.crashed'` and
14
+ * the batch itself as `failed`. Used by on-call when the worker /
15
+ * coordinator stalled and the normal janitor sweep hasn't picked it
16
+ * up.
17
+ *
18
+ * Auth: admin only — explicitly NO TOTP gate (this is a recovery
19
+ * action; admins must be able to unblock production without a TOTP
20
+ * dance if their mobile device is unavailable). The daily USD cap
21
+ * already enforced spending bounds for the batch in question.
22
+ */ export const getBulkTranslateForceResetHandler = (options = {})=>async (req)=>{
23
+ const batchesSlug = options.batchesCollectionSlug ?? DEFAULT_BULK_TRANSLATE_BATCHES_COLLECTION_SLUG;
24
+ const unitsSlug = options.unitsCollectionSlug ?? DEFAULT_BULK_TRANSLATE_UNITS_COLLECTION_SLUG;
25
+ if (!req.user) {
26
+ return unauthorizedResponse(req);
27
+ }
28
+ if (!isAdminUser(req.user)) {
29
+ return errorResponse('forbidden', 'Admin role required to force-reset a bulk-translation batch.', 403);
30
+ }
31
+ const batchId = extractBatchId(req.url ?? '');
32
+ if (!batchId) {
33
+ return errorResponse('invalid_batch_id', 'Batch ID could not be parsed from the request URL.', 400);
34
+ }
35
+ let batch;
36
+ try {
37
+ batch = await req.payload.findByID({
38
+ collection: batchesSlug,
39
+ id: batchId,
40
+ overrideAccess: true,
41
+ depth: 0
42
+ });
43
+ } catch (err) {
44
+ const log = createScopedLogger(req.payload, {
45
+ component: 'hub.force-reset',
46
+ batchId
47
+ });
48
+ log.event('warn', 'hub.force-reset.batch-read.failed', {
49
+ err,
50
+ endpoint: 'hub.force-reset',
51
+ batchId
52
+ });
53
+ batch = undefined;
54
+ }
55
+ if (!batch) {
56
+ return errorResponse('not_found', 'This translation run no longer exists. Refresh the page.', 404);
57
+ }
58
+ // Reset every `running` unit. The batch may also have `pending`
59
+ // units (queued payload-jobs rows that never dequeued because of
60
+ // a coordinator stall) — those are reset too, since the operator
61
+ // is explicitly unblocking the batch.
62
+ let resetUnits = 0;
63
+ const RESET_STATUSES = [
64
+ 'running',
65
+ 'pending'
66
+ ];
67
+ for (const status of RESET_STATUSES){
68
+ let page = 1;
69
+ // eslint-disable-next-line no-constant-condition
70
+ while(true){
71
+ const result = await req.payload.find({
72
+ collection: unitsSlug,
73
+ where: {
74
+ and: [
75
+ {
76
+ batchId: {
77
+ equals: batchId
78
+ }
79
+ },
80
+ {
81
+ status: {
82
+ equals: status
83
+ }
84
+ }
85
+ ]
86
+ },
87
+ page,
88
+ limit: 1000,
89
+ depth: 0,
90
+ overrideAccess: true
91
+ });
92
+ for (const u of result.docs){
93
+ try {
94
+ await req.payload.update({
95
+ collection: unitsSlug,
96
+ id: u.id,
97
+ data: {
98
+ status: 'failed',
99
+ failureCode: 'transient.crashed',
100
+ failureMessage: 'force-reset by admin',
101
+ completedAt: new Date().toISOString()
102
+ },
103
+ overrideAccess: true
104
+ });
105
+ resetUnits += 1;
106
+ } catch (err) {
107
+ req.payload.logger?.warn?.(`[ai-translate] force-reset: failed to reset unit ${String(u.id)}: ${err instanceof Error ? err.message : String(err)}`);
108
+ }
109
+ }
110
+ if (!result.hasNextPage) break;
111
+ page += 1;
112
+ }
113
+ }
114
+ // Mark batch itself as failed. We don't transition to `cancelled`
115
+ // because force-reset is an abnormal termination, not an admin-
116
+ // initiated cancel — the distinction matters for downstream
117
+ // alerting / analytics.
118
+ try {
119
+ await req.payload.update({
120
+ collection: batchesSlug,
121
+ id: batchId,
122
+ data: {
123
+ status: 'failed',
124
+ completedAt: new Date().toISOString(),
125
+ failures: {
126
+ ...batch.failures ?? {},
127
+ force_reset_by: String(req.user.id),
128
+ force_reset_note: 'force-reset by admin'
129
+ }
130
+ },
131
+ overrideAccess: true
132
+ });
133
+ } catch (err) {
134
+ req.payload.logger?.warn?.(`[ai-translate] force-reset: failed to mark batch failed for ${batchId}: ${err instanceof Error ? err.message : String(err)}`);
135
+ return errorResponse('reset_failed', "The reset couldn't be saved. Try again, or contact engineering if it keeps happening.", 500);
136
+ }
137
+ return Response.json({
138
+ data: {
139
+ batchId,
140
+ resetUnits,
141
+ status: 'failed'
142
+ }
143
+ });
144
+ };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Translation Hub bulk-translate REST endpoints (PR2 v1.2.0).
3
+ *
4
+ * Public surface mounted at `/api/translation-hub/bulk-translate/...`
5
+ * by the plugin's central wiring step (`plugin.ts`). Each handler is
6
+ * a factory that takes optional slug/task overrides for testability;
7
+ * production wiring uses defaults.
8
+ *
9
+ * Reference: docs/tickets/Plan-2026-05-27-bulk-translate.md §4.
10
+ */
11
+ export { type BulkActiveHandlerOptions, getBulkTranslateActiveHandler, } from './active.js';
12
+ export { type BulkCancelHandlerOptions, getBulkTranslateCancelHandler, } from './cancel.js';
13
+ export { type BulkEnqueueBody, type BulkEnqueueHandlerOptions, type BulkEnqueueScope, getBulkTranslateEnqueueHandler, } from './enqueue.js';
14
+ export { type BulkFailuresHandlerOptions, getBulkTranslateFailuresHandler, } from './failures.js';
15
+ export { type BulkForceResetHandlerOptions, getBulkTranslateForceResetHandler, } from './force-reset.js';
16
+ export { type BulkListHandlerOptions, getBulkTranslateListHandler, } from './list.js';
17
+ export { type BulkPreflightHandlerOptions, getBulkTranslatePreflightHandler, } from './preflight.js';
18
+ export { type BulkRetryFailedBody, type BulkRetryFailedHandlerOptions, getBulkTranslateRetryFailedHandler, } from './retry-failed.js';
19
+ export { type BulkRevertBody, type BulkRevertHandlerOptions, type BulkRevertWarning, getBulkTranslateRevertHandler, } from './revert.js';
20
+ export { type BatchJobSummary, type BulkStatusHandlerOptions, extractBatchId, getBulkTranslateStatusHandler, } from './status.js';
21
+ export { getTranslationHubUsageSummaryHandler, parseRangeParams, type UsageSummaryCollectionBucket, type UsageSummaryHandlerOptions, type UsageSummaryLocaleBucket, type UsageSummaryModelBucket, type UsageSummaryRange, type UsageSummaryResponse, type UsageSummarySampleRow, type UsageSummaryTotals, } from './usage-summary.js';
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Translation Hub bulk-translate REST endpoints (PR2 v1.2.0).
3
+ *
4
+ * Public surface mounted at `/api/translation-hub/bulk-translate/...`
5
+ * by the plugin's central wiring step (`plugin.ts`). Each handler is
6
+ * a factory that takes optional slug/task overrides for testability;
7
+ * production wiring uses defaults.
8
+ *
9
+ * Reference: docs/tickets/Plan-2026-05-27-bulk-translate.md §4.
10
+ */ export { getBulkTranslateActiveHandler } from './active.js';
11
+ export { getBulkTranslateCancelHandler } from './cancel.js';
12
+ export { getBulkTranslateEnqueueHandler } from './enqueue.js';
13
+ export { getBulkTranslateFailuresHandler } from './failures.js';
14
+ export { getBulkTranslateForceResetHandler } from './force-reset.js';
15
+ export { getBulkTranslateListHandler } from './list.js';
16
+ export { getBulkTranslatePreflightHandler } from './preflight.js';
17
+ export { getBulkTranslateRetryFailedHandler } from './retry-failed.js';
18
+ export { getBulkTranslateRevertHandler } from './revert.js';
19
+ export { extractBatchId, getBulkTranslateStatusHandler } from './status.js';
20
+ export { getTranslationHubUsageSummaryHandler, parseRangeParams } from './usage-summary.js';
@@ -0,0 +1,40 @@
1
+ import type { PayloadHandler } from 'payload';
2
+ export interface BulkListHandlerOptions {
3
+ batchesCollectionSlug?: string;
4
+ unitsCollectionSlug?: string;
5
+ /**
6
+ * Window after a batch's `completedAt` during which Revert is still
7
+ * allowed. Defaults to 24h, mirroring the same constant in
8
+ * `active.ts` (Decision #28). Exported as `revertExpiresAt` on each
9
+ * row so the UI can render the countdown without a second query.
10
+ */
11
+ revertWindowMs?: number;
12
+ }
13
+ /**
14
+ * `GET /api/translation-hub/bulk-translate`
15
+ *
16
+ * Paginated list of every bulk-translate batch the operator can see,
17
+ * sorted by `enqueuedAt DESC`. Powers the Bulk Translate Runs hub
18
+ * (see `views/BulkRunsHub`).
19
+ *
20
+ * Query params:
21
+ * - `status` — comma-separated list (active|terminal|queued|running|
22
+ * cancelling|success|failed|partial|reverted|cancelled). The
23
+ * aliases `active` and `terminal` expand to the full status sets.
24
+ * - `mode` — changed|force|canary
25
+ * - `triggeredBy` — email exact match
26
+ * - `since`, `until` — ISO timestamps on `enqueuedAt`
27
+ * - `hasFailures` — `true` filters to batches with failed_units > 0
28
+ * - `cursor` — opaque base64 from the previous response
29
+ * - `limit` — page size (default 20, max 100)
30
+ *
31
+ * Response shape:
32
+ * `{ batches: BulkTranslateBatchSummary[], nextCursor: string | null,
33
+ * total: number }`
34
+ *
35
+ * The per-collection progress breakdown is NOT included here for
36
+ * performance; the Runs hub drill-down fetches it via `/:id/status`
37
+ * when a row is expanded. `collections: []` is returned for type
38
+ * compatibility with the shared `BulkTranslateBatchSummary` shape.
39
+ */
40
+ export declare const getBulkTranslateListHandler: (options?: BulkListHandlerOptions) => PayloadHandler;
@@ -0,0 +1,182 @@
1
+ import { DEFAULT_BULK_TRANSLATE_BATCHES_COLLECTION_SLUG } from '../../bulk-translate-batches-collection.js';
2
+ import { DEFAULT_BULK_TRANSLATE_UNITS_COLLECTION_SLUG } from '../../bulk-translate-units-collection.js';
3
+ import { computeLiveBatchCounts } from '../../lib/batch-counts.js';
4
+ import { decodeCursor, encodeCursor, errorResponse, isEditorOrAdmin, unauthorizedResponse } from './_helpers.js';
5
+ const DEFAULT_REVERT_WINDOW_MS = 24 * 60 * 60 * 1000;
6
+ const ACTIVE_STATUSES = [
7
+ 'queued',
8
+ 'running',
9
+ 'cancelling'
10
+ ];
11
+ const TERMINAL_STATUSES = [
12
+ 'success',
13
+ 'failed',
14
+ 'partial',
15
+ 'reverted',
16
+ 'cancelled'
17
+ ];
18
+ /**
19
+ * `GET /api/translation-hub/bulk-translate`
20
+ *
21
+ * Paginated list of every bulk-translate batch the operator can see,
22
+ * sorted by `enqueuedAt DESC`. Powers the Bulk Translate Runs hub
23
+ * (see `views/BulkRunsHub`).
24
+ *
25
+ * Query params:
26
+ * - `status` — comma-separated list (active|terminal|queued|running|
27
+ * cancelling|success|failed|partial|reverted|cancelled). The
28
+ * aliases `active` and `terminal` expand to the full status sets.
29
+ * - `mode` — changed|force|canary
30
+ * - `triggeredBy` — email exact match
31
+ * - `since`, `until` — ISO timestamps on `enqueuedAt`
32
+ * - `hasFailures` — `true` filters to batches with failed_units > 0
33
+ * - `cursor` — opaque base64 from the previous response
34
+ * - `limit` — page size (default 20, max 100)
35
+ *
36
+ * Response shape:
37
+ * `{ batches: BulkTranslateBatchSummary[], nextCursor: string | null,
38
+ * total: number }`
39
+ *
40
+ * The per-collection progress breakdown is NOT included here for
41
+ * performance; the Runs hub drill-down fetches it via `/:id/status`
42
+ * when a row is expanded. `collections: []` is returned for type
43
+ * compatibility with the shared `BulkTranslateBatchSummary` shape.
44
+ */ export const getBulkTranslateListHandler = (options = {})=>async (req)=>{
45
+ const batchesSlug = options.batchesCollectionSlug ?? DEFAULT_BULK_TRANSLATE_BATCHES_COLLECTION_SLUG;
46
+ const unitsSlug = options.unitsCollectionSlug ?? DEFAULT_BULK_TRANSLATE_UNITS_COLLECTION_SLUG;
47
+ const revertWindowMs = options.revertWindowMs ?? DEFAULT_REVERT_WINDOW_MS;
48
+ if (!req.user) {
49
+ return unauthorizedResponse(req);
50
+ }
51
+ if (!isEditorOrAdmin(req.user)) {
52
+ return errorResponse('forbidden', "You don't have permission to view bulk-translation history. Contact an admin.", 403);
53
+ }
54
+ const url = new URL(req.url ?? 'http://localhost');
55
+ const params = url.searchParams;
56
+ const limitRaw = params.get('limit');
57
+ let limit = limitRaw ? Number.parseInt(limitRaw, 10) : 20;
58
+ if (!Number.isFinite(limit) || limit <= 0) limit = 20;
59
+ if (limit > 100) limit = 100;
60
+ const offset = decodeCursor(params.get('cursor') ?? undefined);
61
+ const page = Math.floor(offset / limit) + 1;
62
+ const andClauses = [];
63
+ // status filter — expand aliases
64
+ const statusParam = params.get('status');
65
+ if (statusParam) {
66
+ const raw = statusParam.split(',').map((s)=>s.trim()).filter(Boolean);
67
+ const expanded = new Set();
68
+ for (const s of raw){
69
+ if (s === 'active') {
70
+ for (const x of ACTIVE_STATUSES)expanded.add(x);
71
+ } else if (s === 'terminal') {
72
+ for (const x of TERMINAL_STATUSES)expanded.add(x);
73
+ } else {
74
+ expanded.add(s);
75
+ }
76
+ }
77
+ if (expanded.size > 0) {
78
+ andClauses.push({
79
+ status: {
80
+ in: Array.from(expanded)
81
+ }
82
+ });
83
+ }
84
+ }
85
+ const modeParam = params.get('mode');
86
+ if (modeParam) {
87
+ andClauses.push({
88
+ mode: {
89
+ equals: modeParam
90
+ }
91
+ });
92
+ }
93
+ const triggeredByParam = params.get('triggeredBy');
94
+ if (triggeredByParam) {
95
+ andClauses.push({
96
+ triggeredByEmail: {
97
+ equals: triggeredByParam
98
+ }
99
+ });
100
+ }
101
+ const sinceParam = params.get('since');
102
+ if (sinceParam) {
103
+ andClauses.push({
104
+ enqueuedAt: {
105
+ greater_than_equal: sinceParam
106
+ }
107
+ });
108
+ }
109
+ const untilParam = params.get('until');
110
+ if (untilParam) {
111
+ andClauses.push({
112
+ enqueuedAt: {
113
+ less_than_equal: untilParam
114
+ }
115
+ });
116
+ }
117
+ const hasFailuresParam = params.get('hasFailures');
118
+ if (hasFailuresParam === 'true') {
119
+ andClauses.push({
120
+ failedUnits: {
121
+ greater_than: 0
122
+ }
123
+ });
124
+ }
125
+ const where = andClauses.length > 0 ? {
126
+ and: andClauses
127
+ } : undefined;
128
+ const result = await req.payload.find({
129
+ collection: batchesSlug,
130
+ where,
131
+ sort: '-enqueuedAt',
132
+ page,
133
+ limit,
134
+ depth: 0,
135
+ overrideAccess: true
136
+ });
137
+ // Live counts per batch — source of truth is `bulk_translate_units`,
138
+ // not the cached `completed_units / failed_units / skipped_units`
139
+ // columns on the batch row (which drift on retries / cancels /
140
+ // admin SDK interventions). See `lib/batch-counts.ts`.
141
+ const batchIds = result.docs.map((r)=>String(r.id));
142
+ const liveCounts = await computeLiveBatchCounts(req.payload, unitsSlug, batchIds);
143
+ const batches = result.docs.map((row)=>{
144
+ const completedAtMs = row.completedAt ? Date.parse(row.completedAt) : null;
145
+ const revertExpiresAt = completedAtMs !== null ? new Date(completedAtMs + revertWindowMs).toISOString() : null;
146
+ const live = liveCounts.get(String(row.id));
147
+ // Use the live unit-derived total when it's > 0 — for in-flight
148
+ // and recently-completed batches the unit rows ARE the source of
149
+ // truth. Fall back to the cached `totalUnits` on the batch row
150
+ // only when the live count is 0 (empty batch / coordinator hasn't
151
+ // run yet) so the user sees the planned size, not "0 / 0".
152
+ const liveTotal = live?.total ?? 0;
153
+ const totalUnits = liveTotal > 0 ? liveTotal : Number(row.totalUnits ?? 0);
154
+ return {
155
+ id: String(row.id),
156
+ status: row.status,
157
+ mode: row.mode,
158
+ totalUnits,
159
+ completedUnits: live?.completed ?? 0,
160
+ failedUnits: live?.failed ?? 0,
161
+ estimatedCostUsd: row.estimatedCostUsd !== undefined ? Number(row.estimatedCostUsd) : undefined,
162
+ actualCostUsd: row.actualCostUsd !== undefined ? Number(row.actualCostUsd) : undefined,
163
+ createdAt: row.createdAt ?? row.enqueuedAt ?? new Date(0).toISOString(),
164
+ startedAt: row.startedAt ?? null,
165
+ completedAt: row.completedAt ?? null,
166
+ etaSeconds: null,
167
+ collections: [],
168
+ revertExpiresAt,
169
+ revertedAt: row.revertedAt ?? null,
170
+ triggeredByEmail: row.triggeredByEmail ?? null,
171
+ // 1.2.8: editor variant uses this to set the "you" chip on
172
+ // rows the viewer owns (`EditorRecentRunsPanel`).
173
+ triggeredByUserId: row.triggeredByUserId != null ? String(row.triggeredByUserId) : null
174
+ };
175
+ });
176
+ const nextCursor = batches.length === limit ? encodeCursor(offset + limit) : null;
177
+ return Response.json({
178
+ batches,
179
+ nextCursor,
180
+ total: result.totalDocs
181
+ });
182
+ };
@@ -0,0 +1,19 @@
1
+ import type { PayloadHandler } from 'payload';
2
+ export interface BulkPreflightHandlerOptions {
3
+ dailySpendCollectionSlug?: string;
4
+ }
5
+ /**
6
+ * `GET /api/translation-hub/bulk-translate/preflight`
7
+ *
8
+ * Returns a snapshot of the work the operator would dispatch if they
9
+ * confirmed a bulk-translate run: scope (collections × locales),
10
+ * document counts, today's USD spend vs cap, and whether TOTP is
11
+ * required + enrolled for this user.
12
+ *
13
+ * The cost estimate is intentionally omitted in v1 — the trigger UI
14
+ * tolerates `undefined` (renders a "cost loading" placeholder) and the
15
+ * real estimator path runs at enqueue-time inside the coordinator.
16
+ * Adding a true preflight estimate would re-run the diff + provider
17
+ * pricing fetch on every modal open, which is too expensive.
18
+ */
19
+ export declare const getBulkTranslatePreflightHandler: (options?: BulkPreflightHandlerOptions) => PayloadHandler;
@@ -0,0 +1,141 @@
1
+ import { DEFAULT_TRANSLATION_DAILY_SPEND_COLLECTION_SLUG } from '../../translation-daily-spend-collection.js';
2
+ import { errorResponse, getAiTranslateConfig, getBulkConfig, isEditorOrAdmin, unauthorizedResponse } from './_helpers.js';
3
+ import { estimateBatchCost, resolveScope } from './enqueue.js';
4
+ /**
5
+ * `GET /api/translation-hub/bulk-translate/preflight`
6
+ *
7
+ * Returns a snapshot of the work the operator would dispatch if they
8
+ * confirmed a bulk-translate run: scope (collections × locales),
9
+ * document counts, today's USD spend vs cap, and whether TOTP is
10
+ * required + enrolled for this user.
11
+ *
12
+ * The cost estimate is intentionally omitted in v1 — the trigger UI
13
+ * tolerates `undefined` (renders a "cost loading" placeholder) and the
14
+ * real estimator path runs at enqueue-time inside the coordinator.
15
+ * Adding a true preflight estimate would re-run the diff + provider
16
+ * pricing fetch on every modal open, which is too expensive.
17
+ */ export const getBulkTranslatePreflightHandler = (options = {})=>async (req)=>{
18
+ const dailySlug = options.dailySpendCollectionSlug ?? DEFAULT_TRANSLATION_DAILY_SPEND_COLLECTION_SLUG;
19
+ if (!req.user) {
20
+ return unauthorizedResponse(req);
21
+ }
22
+ if (!isEditorOrAdmin(req.user)) {
23
+ return errorResponse('forbidden', "You don't have permission to start a bulk translation. Contact an admin.", 403);
24
+ }
25
+ const config = getAiTranslateConfig(req.payload);
26
+ const bulkConfig = getBulkConfig(req.payload);
27
+ if (!config || !bulkConfig) {
28
+ return errorResponse('not_configured', "Bulk translation isn't set up for this site. Contact engineering.", 404);
29
+ }
30
+ const excludeSet = new Set(bulkConfig.excludeCollections ?? []);
31
+ const collections = (config.collections ?? []).filter((slug)=>!excludeSet.has(slug));
32
+ const globals = (config.globals ?? []).filter((slug)=>!excludeSet.has(slug));
33
+ const targetLocales = config.targetLocales ?? [];
34
+ // Per-collection counts. `total` = docs in the collection;
35
+ // `changed` is a best-effort guess that matches `total` (the real
36
+ // diff-skip evaluation happens inside the coordinator at enqueue
37
+ // time, not preflight). The UI uses these for proportions only.
38
+ const perCollection = [];
39
+ let totalDocs = 0;
40
+ for (const slug of collections){
41
+ try {
42
+ const result = await req.payload.count({
43
+ collection: slug,
44
+ overrideAccess: true
45
+ });
46
+ const count = Number(result.totalDocs ?? 0);
47
+ perCollection.push({
48
+ slug,
49
+ label: slug,
50
+ changed: count,
51
+ total: count
52
+ });
53
+ totalDocs += count;
54
+ } catch {
55
+ perCollection.push({
56
+ slug,
57
+ label: slug,
58
+ changed: 0,
59
+ total: 0
60
+ });
61
+ }
62
+ }
63
+ // Globals contribute exactly one "document" each.
64
+ for (const slug of globals){
65
+ perCollection.push({
66
+ slug,
67
+ label: slug,
68
+ changed: 1,
69
+ total: 1
70
+ });
71
+ totalDocs += 1;
72
+ }
73
+ // Daily spend lookup — keyed by ISO date string (YYYY-MM-DD).
74
+ const today = new Date().toISOString().slice(0, 10);
75
+ let spentUsd = 0;
76
+ try {
77
+ const result = await req.payload.find({
78
+ collection: dailySlug,
79
+ where: {
80
+ and: [
81
+ {
82
+ date: {
83
+ equals: today
84
+ }
85
+ },
86
+ {
87
+ consumerKey: {
88
+ equals: 'default'
89
+ }
90
+ }
91
+ ]
92
+ },
93
+ limit: 1,
94
+ depth: 0,
95
+ overrideAccess: true
96
+ });
97
+ const row = result.docs[0];
98
+ spentUsd = Number(row?.spendUsd ?? 0);
99
+ } catch {
100
+ spentUsd = 0;
101
+ }
102
+ const capUsd = Number(bulkConfig.dailyUsdCap ?? 0);
103
+ const tomorrow = new Date();
104
+ tomorrow.setUTCHours(24, 0, 0, 0);
105
+ const resetsAt = tomorrow.toISOString();
106
+ const requireTotp = bulkConfig.requireTotp === true;
107
+ const userTotp = req.user?.totp;
108
+ const totpEnrolled = !!userTotp?.enabled;
109
+ // Real cost estimate — reuse the enqueue handler's estimator so
110
+ // preflight and enqueue agree on the number. Caps at 500 docs per
111
+ // collection inside `estimateBatchCost`; bounded wall-clock.
112
+ let estimatedCostUsd;
113
+ try {
114
+ const resolved = resolveScope({}, config, bulkConfig);
115
+ const result = await estimateBatchCost(req.payload, config, resolved);
116
+ estimatedCostUsd = result.estimatedCostUsd;
117
+ } catch (err) {
118
+ req.payload.logger?.warn?.(`[ai-translate] preflight: estimate failed: ${err instanceof Error ? err.message : String(err)}`);
119
+ estimatedCostUsd = undefined;
120
+ }
121
+ return Response.json({
122
+ scope: {
123
+ collections,
124
+ globals,
125
+ locales: targetLocales,
126
+ sourceLocale: config.sourceLocale
127
+ },
128
+ documents: {
129
+ total: totalDocs * Math.max(1, targetLocales.length),
130
+ perCollection
131
+ },
132
+ estimatedCostUsd,
133
+ dailySpend: {
134
+ spentUsd,
135
+ capUsd,
136
+ resetsAt
137
+ },
138
+ requireTotp,
139
+ totpEnrolled
140
+ });
141
+ };
@@ -0,0 +1,38 @@
1
+ import type { PayloadHandler } from 'payload';
2
+ export interface BulkRetryFailedBody {
3
+ /**
4
+ * Failure codes to exclude from this retry (e.g. permanent classes
5
+ * a human reviewed and decided not to re-run). Optional.
6
+ */
7
+ skipErrorCodes?: string[];
8
+ /**
9
+ * When set, only retry failed units in this collection or global. Used
10
+ * by the tree-grouped DrillDown's per-bucket "Retry N failed" CTA so
11
+ * the editor can retry one collection's subset without touching the
12
+ * others. Optional — omitted means "all failed units in the batch."
13
+ */
14
+ collection?: string;
15
+ /**
16
+ * When set alongside `collection`, only retry failed units for THIS
17
+ * document. Used by the per-doc Retry button. Combined with
18
+ * `collection` rather than alone because a docId is only meaningful
19
+ * within a collection. Optional.
20
+ */
21
+ documentId?: string;
22
+ }
23
+ export interface BulkRetryFailedHandlerOptions {
24
+ batchesCollectionSlug?: string;
25
+ unitsCollectionSlug?: string;
26
+ workerTaskSlug?: string;
27
+ }
28
+ /**
29
+ * `POST /api/translation-hub/bulk-translate/:id/retry-failed`
30
+ *
31
+ * Re-enqueues all `failed` units for the batch back to `pending`.
32
+ * Refuses when the batch is non-terminal — there's no semantic way
33
+ * to "retry failed" while units are still running.
34
+ *
35
+ * Daily-cap is re-checked (F-SEC-TOTP-BYPASS — the cap applies to all
36
+ * translate paths, not just first-enqueue).
37
+ */
38
+ export declare const getBulkTranslateRetryFailedHandler: (options?: BulkRetryFailedHandlerOptions) => PayloadHandler;