From 3b1e1c77493f328eaabc0246a6b8be5653096fb7 Mon Sep 17 00:00:00 2001 From: "[._.]/ Adam Eivy" Date: Sun, 6 Sep 2026 14:46:42 +0000 Subject: [PATCH] feat: run universe-bible describe/render as on-demand scheduled tasks (#6376) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two programmatic Quota Burn actions — filling blank universe bible sheets and rendering the entries that have no image — were reachable only from a burn plan. They are now ordinary on-demand scheduled tasks: they appear in CoS → Schedule with their own settings and a Run Now button, and Quota Burn dispatches the same handler rather than a private executor. The implementations move to server/services/scheduledHandlers/, and quotaBurnJobs' JOB_MODULES points at them, so there is one implementation instead of a quota-only copy that drifts. Passing the burning family is what pins the provider / render backend to the subscription being drained; a manual run has no family and resolves like any other scheduled task — the task's own pin, else the install's active provider and the universe-bible render-target ladder. Both ship enabled with no interval and no cron, so they are never clock-due and a fresh install spends nothing until someone presses Run. The side-effect-free probe and its opaque probe→run context handoff are preserved: probing writes nothing, enqueues nothing, and calls no provider, and a run without probe context still does its own scan. --- .../cos/tabs/schedule/AppTaskCard.jsx | 1 + .../tabs/schedule/GlobalConfigControls.jsx | 1 + .../cos/tabs/schedule/PromptEditor.jsx | 16 ++ .../cos/tabs/schedule/PromptEditor.test.jsx | 27 +++ .../cos/tabs/schedule/RunTaskButton.jsx | 7 +- .../cos/tabs/schedule/RunTaskButton.test.jsx | 14 ++ client/src/hooks/useOnDemandTaskToast.js | 17 ++ .../src/hooks/useOnDemandTaskToast.test.jsx | 31 ++++ docs/QUOTA-BURN.md | 35 +++- server/lib/cosValidation.js | 59 ++++++- server/services/cos.js | 10 ++ server/services/cosTaskGenerator.js | 97 ++++++++++- ...TaskGenerator.programmaticOnDemand.test.js | 156 ++++++++++++++++++ server/services/quotaBurnJobs/agentPrompt.js | 2 +- server/services/quotaBurnJobs/index.js | 19 ++- server/services/scheduledHandlers/index.js | 87 ++++++++++ .../providerPick.js | 0 .../providerPick.test.js | 0 .../universeBacklog.js | 0 .../universeBibleDescribe.js | 70 +++++--- .../universeBibleDescribe.test.js | 55 ++++++ .../universeBibleImages.js | 73 +++++--- .../universeBibleImages.test.js | 77 ++++++++- server/services/socket.js | 3 + server/services/taskSchedule.js | 11 +- server/services/taskSchedule.test.js | 63 +++++++ server/services/taskScheduleRegistry.js | 73 +++++++- .../taskScheduleRegistry.programmatic.test.js | 93 +++++++++++ 28 files changed, 1021 insertions(+), 76 deletions(-) create mode 100644 server/services/cosTaskGenerator.programmaticOnDemand.test.js create mode 100644 server/services/scheduledHandlers/index.js rename server/services/{quotaBurnJobs => scheduledHandlers}/providerPick.js (100%) rename server/services/{quotaBurnJobs => scheduledHandlers}/providerPick.test.js (100%) rename server/services/{quotaBurnJobs => scheduledHandlers}/universeBacklog.js (100%) rename server/services/{quotaBurnJobs => scheduledHandlers}/universeBibleDescribe.js (72%) rename server/services/{quotaBurnJobs => scheduledHandlers}/universeBibleDescribe.test.js (75%) rename server/services/{quotaBurnJobs => scheduledHandlers}/universeBibleImages.js (74%) rename server/services/{quotaBurnJobs => scheduledHandlers}/universeBibleImages.test.js (61%) create mode 100644 server/services/taskScheduleRegistry.programmatic.test.js diff --git a/client/src/components/cos/tabs/schedule/AppTaskCard.jsx b/client/src/components/cos/tabs/schedule/AppTaskCard.jsx index 9fc526f181..4e1027dfe4 100644 --- a/client/src/components/cos/tabs/schedule/AppTaskCard.jsx +++ b/client/src/components/cos/tabs/schedule/AppTaskCard.jsx @@ -92,6 +92,7 @@ export default function AppTaskCard({ taskType, config, apps, onTrigger, onConfi apps={apps} onTrigger={onTrigger} installWide={config.installWide} + programmatic={config.programmatic} disabledReason={improvementDisabled ? IMPROVEMENT_DISABLED_TITLE : (pins.saving ? SAVING_TITLE : '')} />