---
name: specification
description: Use when transcribing an intent document into the requirements ledger, deriving the design boundary, architecture and timing constraints from it, or updating them from rework feedback; not for RTL implementation or verification.
---

# Requirements and Specification Freeze

Your sole responsibility: turn the delivered `<intent>/brainstorm.md` into a frozen design
source of truth. You dispatch and hold no document body: the intent document, the ledger and
`design.md` are read and written only inside sub-Task contexts.

What this stage freezes is the **boundary** — the rows, the clocks, the ports, the obligations the
design is held to at that boundary, and the timing scenarios. **Which RTL modules exist is not
yours to fix.** `design.md` argues for a structure, and the engineer's rows constrain it, but the
module split is decided by whoever writes the RTL against the boundary you froze.

## Iron Rule

Write only under `{workdir}`; never another module's artifacts. Reading templates and upstream inputs outside it is fine.

## Artifacts

`<skill>` is this skill's own base directory, named on the first line of this file.

Read `{workdir}/dispatch.json` for this round's inputs: its `inputs` table maps each upstream key to a location, so `<key>/<subpath>` is how you address one. The only input is the intent tree, the container `<intent>/`: `brainstorm.md`, in whatever shape the engineer wrote it, plus whatever the engineer delivered with it and the document names as authoritative. Frozen for the run. The document is read whole only by the transcriber and the reviewers; another file in the container is read by whoever a row pointing at it concerns.

Everything below is produced under `{workdir}`. Each JSON sidecar's shape is `references/<name>.schema.json`.

| Path | What it is |
|---|---|
| `requirements.json` | The ledger: one row per proposition the intent document states, in the engineer's words, with `judge` naming who establishes it. Every downstream rule binds it |
| `manifest.json` | `module` — the top RTL module's name, and the only thing here. Six readers resolve `<TOP>` from it |
| `design.md` | The decisions made around the ledger: the architecture it proposes, the boundary narrative, the obligations more than one part must jointly keep, and the timing scenarios (template: `references/design-template.md`) |
| `clocks.json`, `top-io.json` | The boundary: clocks and their arrival budgets, top-level ports |
| `check-hints.json` | How simulation observes each ledger row it judges |
| `constraints/<TOP>.sdc`, `constraints/<TOP>.sgdc` | Generated by `derive-constraints` |
| `spec-review/decisions.md` | The user's rulings, in their words. It accumulates across rounds — a ruling this round did not revisit still stands, so it is carried forward and appended to, never rewritten |
| `spec-review/findings/` | The two reviews. This round's record: it does not carry into the next round, so a review on disk is always about what is on disk now |
| `result.json` | The status envelope |

## Fan-out

- **Dispatch-and-wait:** after dispatching, send a brief status and end the turn; a wave's gate begins once every sub-Task in it has reported.
- **Sub-Task `STATUS: BLOCKED`:** a crash, not a `fail` verdict. Finalize with a `--fail-reason` naming what failed — it lands in the envelope's `stage_specific.fail_reason` — and leave re-dispatch to a repair round.

## Workflow

### Which round is this

Read `{workdir}/dispatch.json` first. The kernel writes `scope` / `caused_by` / `reasons` **only when they carry something**, so their presence is what tells you:

- **`caused_by` present — failures downstream are waiting on this stage.** Each entry is one failing run's own `result.json`, and a round scheduled for some other reason carries them too: answer them in this round. Scope is the union of `dispatch.json`'s `scope` and what the `caused_by` envelopes attribute; Read each envelope once. Dispatch one repair sub-Task over the boundary and `design.md`, then pick up at **`check-crossrefs`** and run through to finalize: the authoring dispatches are skipped, but the join and the constraints must re-verify against what changed, and the delivery review re-runs so the promoted review is never stale. The ledger is not re-transcribed: the intent document is frozen, and a row changes only by a human's ruling.
- **`caused_by` absent — a first delivery.** Run every step below.

Either way your previous round, if any, is already in `{workdir}`: edit it in place, touching only what this round requires. Rewriting an artifact this round did not change still changes its bytes, and every stage downstream declares these files as inputs, so a cosmetic rewrite invalidates their proofs and buys a rebuild of everything transitively below for no change in content. Row ids never change and are never renumbered; a row added in a repair takes a fresh id.

A `{workdir}` already holding part of a round means the session was compacted or interrupted: that work is yours to continue or redo, and artifacts on disk are not a gate you already passed.

### Transcribe

Dispatch one Level-1 sub-Task per `references/ledger-task-contract.md`. In its own context it reads `<intent>/brainstorm.md` and writes `requirements.json`.

**Gate, script.**

```bash
python3 <skill>/scripts/spec/__main__.py check-ledger --workdir {workdir}
```

A non-zero exit names every shape or content defect on stderr (an unknown judge, a duplicate id, an `unassignable` row with no reason); re-dispatch the transcriber. On success stdout is the gate view: the count per judge, and verbatim the rows judged `unassignable`, `outside` and `human`, and the rows carrying a `target`. Keep it; the human gate below hands it over.

### Decompose

Dispatch one Level-1 sub-Task per `references/decompose-task-contract.md`. It reads `requirements.json`, not the intent document, and writes `manifest.json`, `design.md`, `clocks.json` and `top-io.json`. The author holds the rows and their ids and nothing else, so the design cites requirements rather than restating them.

### Review

Dispatch one Level-1 reviewer per `references/review-task-contract.md`, naming its output `requirements.md`. It reads the intent document and everything on disk so far, and reports where any two of them disagree.

**Gate, human — the ledger and boundary gate.** Path-handoff, echoing no body, with four exceptions the gate view already holds verbatim: the `unassignable` rows with their `note`, the `outside` rows, the `human` rows, and the rows carrying a `target`. Add the `spec-review/findings/requirements.md` and `design.md` paths.

What the user decides here: every `unassignable` row (how it is measured, who judges it, or that it is not a requirement, in which case it becomes `none` with the reason in `note`); each `human` row, which they judge now from the documents in front of them; and the boundary, which the hints and the constraints derive from next, so this is the last cheap moment to change it. Write each ruling to `spec-review/decisions.md` in **their words, not yours**, then dispatch a repair sub-Task that edits what it names — nothing outside a sub-Task edits an artifact. After any edit to the ledger, re-run `check-ledger` and this review. A reported omission re-enters at transcribe, a boundary change at decompose.

### Check hints

Dispatch one Level-1 sub-Task per `references/check-hints-task-contract.md`, writing `check-hints.json`. It runs here rather than beside decompose because a row the human just ruled on can have become one simulation judges.

**Gate, script.** Run `check-crossrefs` to check references between the current ledger, boundary and hints. It reports invalid or missing requirement references, duplicate check IDs and undeclared clock domains. Finalize repeats this check against the files being delivered.

```bash
python3 <skill>/scripts/spec/__main__.py check-crossrefs --workdir {workdir}
```

The verdict is structured on stdout: exit 0 clean, exit 1 the join found something. Exit 1 with **nothing** on stdout is a file it could not read, named on stderr — route that to whoever authors it. **Fix nothing yourself.** Each disagreement names both sides, and which of the two is wrong is a judgment: the hint may name the wrong row, or the row may name the wrong judge. Decide that, then route the rework to whoever authored that file.

On a clean gate, immediately derive the constraints:

```bash
python3 <skill>/scripts/spec/__main__.py derive-constraints --workdir {workdir}
```

It generates `constraints/<TOP>.{sdc,sgdc}` from `clocks.json` + `top-io.json`. Running it here surfaces defects no cross-file join can see (the exactly-one-`primary` rule, clock-name collisions) while a rework is still cheap. It reads only the boundary sidecars, so a defect it reports on stderr routes to decompose.

### Delivery review

Dispatch one Level-1 reviewer per `references/review-task-contract.md`, naming its output `delivery.md`. Same contract as the first one, and everything the round delivers is now on disk — the ledger the human ruled on, the boundary, the hints, the constraints — so this is the pass where a hint's rule and a `specification`-judged row's realization can be checked at all.

**Handoff, not a gate.** Give the user the paths, echoing no body: `design.md`, the `check-crossrefs` verdict, and both `spec-review/findings/` paths, so they can read the reviews themselves.

Nothing here asks the user for a verdict, and the round does not wait on one. What the reviews reach for is the engineering soundness no script can — port roles, reset polarity, clock relationships, whether the boundary carries what the rows require — and one word cannot carry an answer to each finding they raise. So a blocking finding is acted on below, never shipped under a word typed to keep the round moving.

Read the findings and dispatch sub-Tasks to assess and repair defects in this stage. A boundary change re-enters at decompose; a row change at transcribe. Re-run the affected checks and reviews after repair. Ask the user when resolution requires a decision about intent. Record any finding the user explicitly accepts as-is in `spec-review/decisions.md` in their words. If a blocking defect remains unresolved, close the round with `--fail-reason` naming it.

### Finalize

Every run ends here, an unresolvable failure included:

```bash
python3 <skill>/scripts/spec/__main__.py finalize --workdir {workdir} [--fail-reason "<one-line reason>"]
```

The status is derived, so you assert nothing about the outcome: a reason names what stopped the round, and its absence means the stage delivered what it owes. On the delivering path finalize re-runs `check-crossrefs` and `derive-constraints` in-process, then re-validates `requirements.json` and refuses one that still carries an `unassignable` row — the first two were clean at the cross-reference gate, so a failure there means an artifact was edited after that gate, which is BLOCKED rather than a routable fail. Exit 0 = `result.json` written. A non-zero exit is a program exception: BLOCKED, reason on stderr, never a `status=fail`.

## Return Contract

Control returns to the caller, which decides what runs next from `result.json`.

Your sole completion signal is `{workdir}/result.json` present with `status=pass`.
