Skip to content

Model a Quota Burn step as a scheduled-task reference plus per-invocation overrides #6377

Description

@atomantic

Problem / Goal

A Quota Burn step today is a copied prompt plus a free-form params bag: normalizeQuotaBurnJob in server/lib/quotaBurnConfig.js keys on a jobType from the QUOTA_BURN_JOB_TYPE enum and stores prompt text, so a step has no durable link to the scheduled task it was cloned from. #6372 replaces that with a reference to an existing scheduled task plus per-invocation overrides.

This child lands the data model, normalization, and validation only — no new execution path, no UI replacement, no migration.

Scope

  • Define a discriminated task reference on a burn step: either a built-in scheduled type (plus a validated target app where the type requires one) or a custom scheduled job ID (whose own app scope is authoritative). Extend normalizeQuotaBurnJob / normalizeQuotaBurnFamily / normalizeQuotaBurnConfig in server/lib/quotaBurnConfig.js and the Zod schemas in server/lib/quotaBurnValidation.js (quotaBurnJobSchema / quotaBurnFamilySchema / quotaBurnConfigUpdateSchema, all .strict(), re-exported through server/lib/validation.js and used by PUT /api/quota-burn in server/routes/quotaBurn.js).
  • Preserve the step's identity and settings: stable step id, family order, enabled, label, runOnce state, and family-level settings all survive normalization untouched.
  • Per-invocation overrides: provider / model / effort and the supported run parameters, stored explicitly on the step. Unset values inherit the source task's saved settings. Overrides never write back to the schedule. A provider pin must belong to the selected burn family; an absent pin resolves inside that family and must never fall back to another family's subscription.
  • Unavailable steps: a deleted, disabled, wrong-scope, or incompatible reference normalizes to a retained-but-unavailable step carrying an actionable reason. Its settings are never dropped, and it is not dispatchable.
  • Legacy compatibility: old jobType payloads still load. Freeze QUOTA_BURN_JOB_TYPE, QUOTA_BURN_JOB_CATALOG, and server/lib/quotaBurnPresets.js as compatibility/migration inputs only — readable, never the canonical definition of new work. A legacy payload that has not yet been converted normalizes to unavailable with a migration reason rather than silently downgrading a reference or duplicating an automation.
  • Preset identity is currently never persisted. Both the server (upgradeStoredAuditPrompt, server/lib/quotaBurnPresets.js) and the client (matchedPreset in client/src/components/quotaBurn/JobRow.jsx) re-derive it by comparing prompt text, which is exactly the fragility this reference model exists to remove. A reference must record identity durably, not re-derive it.
  • Config lives in data/cos/quota-burn.json via server/services/quotaBurnStore.js and stays machine-local and unfederated (see the store's header comment and docs/STORAGE.md).

Acceptance criteria

  • Round-trip tests: a reference-shaped config saves and reloads with step id, order, enabled, label, run-once state, family settings, and overrides byte-identical.
  • Zod rejects a reference missing a required target app, a custom job id that is not a string, and a provider pin outside the step's family; each rejection names the offending field.
  • An absent provider pin resolves within the step's own family — a test proves it cannot resolve to another family's provider.
  • Dangling / disabled / wrong-scope references normalize to unavailable with a reason and retain every stored setting.
  • An un-migrated legacy jobType payload normalizes to unavailable-with-migration-reason; it is never rewritten into a bogus reference and never duplicated.
  • saveQuotaBurnConfig's shallow family merge and the client mirror client/src/lib/quotaBurnPatch.js#mergeQuotaBurnPatch stay line-for-line consistent (the store comment asserts this) — extend both or neither.

Notes

Foundation for the invocation-path, accounting, migration, and editor children. Ship this first among the burn-side children.


Part of #6372

When this ships, tick its box in the ## Decomposed into checklist on #6372. If it is the last unchecked box, close #6372.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:cos-agentsChief-of-Staff autonomous agentseffort:highDispatch reasoning effort: highenhancementNew feature or requestmodel:heavyDispatch capability: strongest available coding modelplanTracked by /do:replanplanner:opus-5Plan authored by the opus-5 model

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions