Skip to content
Select themeSelect language

Spec readiness contract

A spec says how a domain behaves; a work item says which slice an agent builds next. This page is the reference for how the two are bound, when an item is ready, which tracker labels express that, and who writes them. The spec files themselves are in Delivery spec format.

Work-item fields that bind an item to a spec

Section titled “Work-item fields that bind an item to a spec”

A work item is a YAML contract (agents/work_items/WI-*.yaml in the reference implementation). Three fields bind it to specs:

Field Shape Meaning
spec_refs list of clause IDs (<DOM>-R-###), unique, at least one the clauses this item implements
decision_refs list of decision IDs (DEC-<DOM>-###), unique, at least one when present the design decisions this item depends on; the item stays blocked until each is approved
acceptance[] objects with text (at least 8 characters) and verifies (clause IDs, unique, at least one) each entry names the clauses it proves
# excerpt of a work item; a complete item has at least three acceptance entries
spec_refs: [ECON-R-004, ECON-R-005]
decision_refs: [DEC-ECON-005]
acceptance:
- text: A season budget above the board's ceiling is rejected with the ceiling named
verifies: [ECON-R-004]

Two further fields keep their meaning:

  • complexity (T0 to T3) selects the review depth on the project’s merge ladder: T0 merges once every gate is green; T1 adds one independent review agent; T2 needs two independent reviews from different model vendors, or a human gate; T3 is human-led.
  • human_gate: true marks an item that is a decision to be taken before work starts. An item that implements an approved spec carries decision_refs instead of a gate of its own.

Plain-string acceptance entries are the pre-spec form. They stay valid for items without spec_refs, so a backlog can migrate domain by domain.

Binding rules and the gates that enforce them

Section titled “Binding rules and the gates that enforce them”
Rule Gate
With spec_refs, every acceptance entry is an object whose verifies names clauses from the item’s own spec_refs depth gate
Every clause in spec_refs exists in some spec depth gate
Every clause in spec_refs is verified by at least one acceptance entry traceability gate — a clause claimed but proven by nothing fails
decision_refs belong to a domain the item’s spec_refs reference, and exist in that domain’s decisions.yaml depth gate
decision_refs or verifies without spec_refs are rejected depth gate
At most six clauses in spec_refs and at most eight acceptance entries — a larger item is split depth gate
The number of items without spec_refs may only fall depth gate, shrink-only baseline unmigrated_work_items

Every item, spec-bound or not, also meets the depth rules of a contract: a goal of at least 120 characters that says what changes and why, at least three acceptance criteria of at least eight words, at least one criterion that states a failure case, at least two evidence entries, at least one command, a requirement that resolves to the catalogue, and notes. The count of items that miss them is a second shrink-only baseline (shallow_work_items). Both baselines live in one file with its own policy string: every migrated item lowers a number in the same pull request, and raising one needs a named human decision.

The depth check and the traceability check do not know any ID shape of their own. Both resolve a work item’s requirement references, and a spec’s satisfies links, through the project profile (specs/project.yaml) — the same one definition the spec validator and the spec gate read, which names the catalogue file, the lists that hold requirements and acceptance criteria, and the ID patterns. A repository whose profile is missing or malformed fails these checks loudly and by name; nothing falls back to built-in ID shapes. See The project profile.

The traceability check then traces one level deeper for items with spec_refs: every clause an item claims must be proven by at least one of its own acceptance entries. A clause listed but verified by nothing is a claim without a proof and fails.

Not every baseline in this chain works the same way, and the difference matters when a pull request takes on new scope:

Baseline Rule
Spec coverage (spec-coverage-baseline.json: catalogue IDs no domain owns) must equal the current count. A count above it is a regression; a baseline above it is slack that would let the next regression pass. A pull request that gives a domain new catalogue IDs rewrites it with --write-baseline in the same change.
Traceability (uncovered_must_p1, uncovered_acceptance_criteria) and work-item depth (shallow_work_items, unmigrated_work_items) shrink-only, without the equality rule: the number may fall freely, and only raising one needs a named human decision.

Readiness says an item may start; gate classes say which finished change still waits for a person. They are separate mechanisms and a project needs both. In the reference implementation the class map (tools/qa/gate_classes.yaml) holds a rule kind for each of the two spec files this contract touches: decision_answers on specs/*/decisions.yaml and parameter_ranges on specs/*/parameters.yaml, both class G1. So answering a decision, or moving a parameter’s approved range, is held for the owner — while tuning a default inside its range is free. The status is gate-class, and it is a required check on the reference implementation’s main alongside its three core-ci contexts. The full class list is in How SupaCloud integrates with a spec-driven project.

Terminal window
uv run python tools/fmctl.py validate work-items # schema, depth and link rules
uv run python tools/fmctl.py validate requirements # traceability, including unproven clauses

An item is ready when all of these hold:

  1. every item in its dependencies has a closed issue;
  2. every decision in its decision_refs has status: approved;
  3. every spec its spec_refs point at has the status design-approved, implementing or verified.

An item with neither spec_refs nor decision_refs is ready on its dependencies alone. An item is spec-ready when it is ready and has spec_refs: it implements approved clauses under approved decisions, so a dispatcher may hand it to an agent without asking anyone.

Readiness fails closed: an unknown decision, an unreadable spec or the template domain never makes an item ready. A closed issue carries no readiness label at all.

The readiness rule is published on the tracker as labels, so every tool — a dispatcher, a board, a person — reads the same fact.

Label Set when Removed when
ready the item is ready it no longer is, or the issue is closed
blocked the item is open and not ready it becomes ready, or the issue is closed
spec-ready the item is ready and has spec_refs a dependency reopens, a referenced decision is no longer approved, the spec returns to draft, or the issue is closed
human-gate the contract has human_gate: true the contract no longer has it — the label mirrors the contract in both directions

The writer also puts the marker labels on each issue — work-item, the complexity label T0 to T3 and skill/<owner_skill> — creates any label it relies on that does not exist yet, and, in the reference implementation, assigns one milestone per delivery wave. It creates an issue for a work item that has none, but it never closes or reopens an issue and never removes a label outside the four in the table. A consistency check reports every label that disagrees with the contracts and specs and exits non-zero:

Terminal window
uv run python tools/fmctl.py workitems sync -- --check # read-only; non-zero on drift
uv run python tools/fmctl.py workitems sync -- --dry-run # print the writes it would make
uv run python tools/fmctl.py workitems sync # create missing issues, relabel

Exactly one system writes readiness to the tracker, and in a spec-driven project on SupaCloud that system is SupaCloud:

  • SupaCloud derives readiness from the repository’s work-item contracts and specs, as above, and maintains ready, blocked, spec-ready and human-gate through the project’s existing forge connection — the credential the project already uses to clone, open pull requests and merge.
  • The repository’s CI holds no tracker credentials. Its gates read the repository — and a gate that judges a pull request reads that pull request with the forge’s own job token — but none of them writes to the tracker.
  • A project needs no bot account and no CI secret for this.

Two writers would disagree the moment one of them lags, and a per-project bot account with a CI secret is setup every adopting project would have to repeat. The first design of the reference implementation ran the sync as a scheduled CI job under a dedicated bot account; it was dropped on 2026-09-22 for exactly these reasons.

What a dispatcher does with the labels (planned)

Section titled “What a dispatcher does with the labels (planned)”

SC-2 makes SupaCloud’s backlog intake label-aware. The recommended rule set for a spec-driven project dispatches exactly the open issues that carry work-item, ready and spec-ready and none that carry blocked; an item that loses a required label before dispatch goes back to a non-dispatchable state, and a closed issue cancels its queued item. T3 items are never dispatched. The complexity label also selects the capability tier the work runs on (SC-3). None of this exists in SupaCloud today — see How SupaCloud integrates with a spec-driven project.