# Specs as secondary artifacts, and named tests Status: parked Raised: 2026-07-28 ## Why it came up The harness is deliberately not an SDD framework: the goal is to maintain content and trust the harness to keep the code minimal and maintainable, rather than to author requirements for every change. But two things are missing that SDD would have given for free — a place to put a well-defined spec when one already exists, and any trace from a code change back to the behaviour it was supposed to produce. Proposal: per-feature specs as **secondary** artifacts. ADRs, `state.md` and the engine-wide contracts stay primary. A spec may be written first (true SDD for a well-defined feature) or generated after the fact from what was requested and what was built. Either way it can then be edited, and the edit is implemented — flowing upward into the harness where it touches anything the harness owns. ## What is already established - The harness has no per-feature artifact. A plan lives in the conversation; what survives is an ADR plus a `state.md` delta. There is no way to ask later "which behaviour was this code meant to have". - Tests are mandatory for `cmd/` and `internal/` changes but nothing ties a test to the rule it verifies. `content-model.md` says a bundle key excludes language, and that a bundle supplying both `about.md` and `about.en.md` is rejected — nothing checks a test exists for either. - One fact, one place is enforced throughout (`harness/README.md` authority table). Specs must not become a fourth copy of decisions. ## The non-overlapping slot The only content a spec should hold is what nothing else does: | Artifact | Holds | |---|---| | ADR | why a load-bearing choice was made, permanently | | `state.md` | what exists right now | | `architecture.md`, `content-model.md` | engine-wide shape and contract | | **spec** | **per-feature observable behaviour: given X, the engine does Y** | Acceptance criteria, in other words — which is also exactly the traceability gap. So the cheapest useful spec is a list of criteria whose names are the test names, and the named-test convention below is the subset of this proposal that can ship on its own. ## The rule that stops it rotting: two states, one owner each A generated spec describes what exists; an edited spec describes what should exist. Flipping between those per edit, with nothing marking which, is how spec systems rot — after three round trips nobody knows whether a clause is a record or a requirement. So each spec carries a status and it cycles: - **`derived`** — describes what exists. The code wins; a mismatch is a spec bug, fixed silently. - **`authored`** — a human edited it, or wrote it first. The spec wins; a mismatch is unimplemented work. The next implement pass brings the code into line and flips it back to `derived`. `verify.sh` can surface `authored` specs as a warning — pending requirements become visible state rather than a silent backlog. This mirrors the existing "if `state.md` disagrees with the code, the code wins" rule, which works precisely because the direction is never ambiguous. ## Two things to get right or it is worse than nothing **A derived spec must not paraphrase the code.** If it restates the implementation it carries no information, costs tokens every feature, and goes stale instantly. Its value is the half the code cannot hold: the request as made, the behaviour at the edges, what was deliberately not done. Capture the request, not the diff. **Not every change earns a spec.** A spec per one-line tweak is overhead. The natural threshold is the one already enforced: a change that must ship a test is a change with observable behaviour worth pinning, and the spec's criteria are that test's names. Changes below that line get an ADR or nothing. ## Upward flow needs no new machinery An edited spec is just another request, so the existing conflict check (`CLAUDE.md` rule 9) applies unchanged: contradicts an ADR or an engine contract → hard conflict, stop and surface; is a load-bearing choice → it produces an ADR; is plain new behaviour → code plus spec, nothing else. "Flows upward" means the spec is an *entry point* to the loop, not a parallel process. ## Cheapest next step Two independent pieces, in order: 1. **Named tests, alone.** A line in `conventions.md`: a rule stated in an engine contract earns a test named after it — `TestBundleKeyExcludesLanguage`, `TestDuplicateVariantRejected`. Zero infrastructure, most of the traceability, and it makes the spec step later almost free because the criteria already exist as test names. 2. **`specs/.md`**, one file per feature with a `Status:` line, an index like `ideas/`, excluded from the Go gates the way `ideas/` and `reference/` are, read on demand and never swept. Open question if this is adopted: whether a spec is ever deleted. Suggest not — a `derived` spec for a shipped feature is the closest thing to living documentation of behaviour, and it is cheap to keep.