Five routes symmetric with the factory front-end:
POST /api/eval/batches (funded 201 / unfunded 202 queued)
GET /api/eval/batches/<id> status + per-task state
GET /api/eval/batches/<id>/results the results table
POST /api/eval/batches/<id>/cancel stop draining + release the hold
GET /api/eval/health liveness, no auth
Auth delegates to gitea_saas.auth (issue #24 single path — no sixth
implementation): missing/blank key -> canonical 401; no key configured
at all -> 503 SERVICE_UNAVAILABLE (issue #38's assembly acceptance).
Unfunded submits now queue in pending_eval_batches (no Gitea issue is
ever created); flush_pending ships them after funding, exactly like a
fresh funded submit. Funded submits reserve eval_batch_hold_minor
(SAAS_EVAL_BATCH_HOLD_MINOR, default = debit floor) via the billing
core's reserve_hold; cancel releases it so the balance returns to its
pre-submit value exactly, and marks the batch cancelled (poll/emit
become no-ops). Rolled-back intake releases the hold too.
Schema: pending_eval_batches + eval_batch_cancels tables (idempotent
CREATE IF NOT EXISTS), hold_id column with a PRAGMA-guarded ALTER for
pre-existing DB files.