Skip to content

UCCA — Job Spine Spec

What this is. The machinery under the frozen contract's job surface (POST /v1/jobs → 202 → poll / webhook). The pack promises the surface; this designs the spine: states, stores, the queue message, and the vocabulary rules. Alex builds the spine against this instead of deriving it mid-build.


1. The state machine

accepted → queued → running → completed
                    failed

Five states. Terminal means terminal: completed and failed never transition.

State Meaning Entered when
accepted Passed the gate; job-id minted; 202 returned. Warranty begins here (fence ruling: no warranty before the gate, full warranty after). Gate verdict OK
queued On the queue; no container owns it yet. Enqueue success
running A container owns it; LLM calls in flight; cost ledger writing. Consumer pickup
completed Envelope assembled, signed, stored in R2; poll returns it. Envelope stored + signed
failed Terminal, with machine-readable reason. Retry budget exhausted or unrecoverable error

Deliberate absences (do not add without a ruling): - No cancelled. The frozen pack offers no cancellation. Adding it invents client surface → v1.1 candidate if ever wanted. - No retrying state. Retries happen inside running; the client sees running until terminal. Retry mechanics are engine business, not contract surface.

2. The three governing rules (ruled by Tim, 2026-07-02)

2.1 Gate rejects are not jobs. A payload that bounces at the gate receives a GATE_* error and no job-id, no row in the jobs table. The job ledger starts at accepted: every row is something the engine owed work on. (Rejects are logged — see §6 — but in the ops log, never the job ledger. Two ledgers, two meanings: the ops log records what knocked; the job ledger records what we owed.)

2.2 Partial failure is failure. A job that dies mid-run returns failed, never a partial envelope. The envelope is atomic — it exists whole and signed, or not at all. A half-traced envelope is the false-blessing shape FOUNDATION-01 §2 exists to prevent. The cost ledger still records every call made (append-only; money was spent; provenance is honest). No output artefact ships.

2.3 Job rows are permanent. No TTL, no eviction. Volume is trivial and job history is audit surface for a trust company. Revisit only if volume ever makes this a real question; deleting audit trail to save nothing is not a default.

3. Keep, don't ship — the diagnostics rule (Tim, 2026-07-02)

Two views of every failure:

  • Client view: terminal failed + machine-readable reason code. Clean, bounded, final.
  • Engine view (admin.ucca.online): the full autopsy — failing step, the error that killed it, all cost-ledger rows for the run, and intermediate work product stashed in R2 under an engine-side diagnostics prefix (diagnostics/{job_id}/…).

Rules: the diagnostics prefix is never client-visible, never signed, never referenced by any envelope. Surfaced to us, summarised to them. The wreckage never ships; the wreckage is never discarded.

4. Failure vocabulary (two-lens rule, applied to error codes)

Failure codes speak about the job and the engine's own state — never about the material. Error codes are output surface; the two-lens rule (pack §4.4) applies to them exactly as to envelopes.

Permitted shape: JOB_LLM_UNREACHABLE, JOB_STEP_RETRY_EXHAUSTED {step}, JOB_ENVELOPE_ASSEMBLY_ERROR, JOB_INTERNAL_ERROR. Forbidden shape: anything readable as a verdict on the client's material ("content inadequate", "coverage insufficient"). The engine reports its own reasoning state, not judgements. If a failure is caused by material the engine cannot process, the code still describes the engine's state ("could not proceed at step N"), and the detail lives in engine-side diagnostics.

5. Stores

5.1 D1 — jobs table (job state):

CREATE TABLE jobs (
  job_id        TEXT PRIMARY KEY,          -- minted at accept
  status        TEXT NOT NULL CHECK (status IN
                  ('accepted','queued','running','completed','failed')),
  job_type      TEXT NOT NULL,             -- 'generation' | 'diagnosis' (501 path)
  payload_ref   TEXT NOT NULL,             -- R2 key of the validated payload
  envelope_ref  TEXT,                      -- R2 key of the signed envelope (completed only)
  failure_code  TEXT,                      -- machine-readable, per §4 (failed only)
  webhook_url   TEXT,                      -- optional, per pack
  created_at    TEXT NOT NULL,             -- ISO-8601 UTC
  updated_at    TEXT NOT NULL,
  completed_at  TEXT                       -- terminal timestamp
);

State transitions update status/updated_at only; rows are never deleted (§2.3). Placement/storage-locus note: payload_ref/envelope_ref are keys that tolerate a storage-locus identifier (the residency bake-in from the ADR-0004 relay) — the key format is {locus}/{path} with locus default now.

5.2 D1 — cognitive_cost table (the ledger, migrated from JSONL):

Fields per the on-disk ledger (provider, model, unit_id, request_id, input_tokens, output_tokens, pricing ref, cost_usd, cumulative_after_usd, ts_utc) plus job_id. Insert-only: no UPDATE, no DELETE, enforced in the data layer (and by convention in code review). Provenance-adjacent and future billing data; an updatable ledger is not a ledger.

5.3 R2 layout:

{locus}/payloads/{job_id}.json        -- validated payload (gate-passed bytes)
{locus}/envelopes/{job_id}.json       -- signed envelope (completed jobs only)
{locus}/diagnostics/{job_id}/…        -- engine-side autopsy (never client-visible)

6. The gate's reject behaviour (precise, mechanical, structural)

Rejects say why — with a GATE_* code and the location of the structural fault. The conformer needs precision to fix and resubmit; a silent bounce is hostile. Examples of shape: GATE_SCHEMA_VERSION (unsupported triumvirate version), GATE_SIGNATURE (adapter signature invalid), GATE_STRUCTURE (+ field pointer), GATE_SIZE, GATE_RATE.

The why is always structural, never semantic. "Field X missing" — yes. Anything that drifts toward judging content — no. Semantics are the conformer's warranty (fence ruling); the gate never re-checks them and must never appear to have judged what it did not judge.

Rejects are logged engine-side in an ops log (full reject record: code, timestamp, fault location, source identity) so persistent gate-failure patterns are visible in admin — but per §2.1, never in the jobs table.

7. The queue message

The queue carries pointers, never payloads.

{ "job_id": "…", "payload_ref": "{locus}/payloads/{job_id}.json" }

The validated payload lives in exactly one place (R2); the queue moves a claim ticket. This keeps the payload from existing in two systems, keeps queue messages tiny, and makes replay/redelivery safe (redelivery re-reads the same bytes).

8. Webhook (per pack, optional)

On terminal transition, if webhook_url present: POST {job_id, status} — status only, no envelope in the webhook body; the client polls for the envelope. Failures of webhook delivery are logged, retried on a small budget, then dropped — webhook delivery is best-effort and never affects job state.

9. What this spec deliberately does not decide

  • Retry budgets and backoff numbers (Alex's implementation call; record them in code and in the findings when chosen).
  • Container concurrency (ADR-0004: cap is a first-class control, initial 2).
  • The diagnosis path internals (501 per the pack; full gate checks run; a rejected-or-accepted job that reaches the diagnosis runner returns the honest 501 and the job terminates failed with JOB_TYPE_NOT_IMPLEMENTED — the one pre-agreed exception shape, since the pack promises the stub).
  • Anything client-visible beyond the frozen surface. Named v1.1 candidates remain: cancellation, thinking-tiers, residency selection.

Job spine. Five states, terminal means terminal; the ledger starts where the warranty starts; failure ships clean and keeps the wreckage; the gate says precisely why in structure and nothing in semantics; the queue moves claim tickets, not cargo. Build against this; amend it by finding, not by drift.