jonnifyagile agents
jonnify / Agile Agent Workflow / A4 · Spec / A4.3 / A4.3.3 · Constrain, not over-specify

A4.3 · Anatomy of a spec · dive 3

Constrain, not over-specify

A spec constrains the build without over-specifying the solution: it states what must be true, never how to write the code.

A constraint fixes a behaviour and leaves the implementation open; an over-specification dictates the code and leaves the Author nothing to decide. The cleanest place to see the line is Scope's Out, which constrains by deferring — naming the later rung each concern moves to. Pick a statement on the right to classify it as constraining or over-specifying.

Constrains or over-specifies? · pick a statement

"renders the empty state for an unknown id" a statement that could appear in a spec CONSTRAINS states what must be true leaves the code free OVER-SPECIFIES dictates the solution leaves nothing to decide
"renders the empty state for an unknown id" · constrains — it fixes a behaviour and leaves the implementation open. A spec states what must be true.

The line

Every section of a spec has the same job: constrain the build without over-specifying the solution. A constraint states what must be true and leaves the code free; an over-specification dictates the code and leaves the Author nothing to decide. “renders the empty state for an unknown id” constrains — it fixes an observable behaviour and says nothing about how to produce it. “use a case not a cond” over-specifies — it dictates a solution detail a spec has no business holding.

The distinction matters because the Author is a fast, well-specified implementer: give it a behaviour and it builds; give it a forced solution and it builds the wrong thing precisely. A spec that over-specifies trades the implementer's judgement for the writer's, and usually loses. The hero figure classifies single statements; the next section shows how Scope draws the same line at the level of whole concerns.

constrains
States what must be true and leaves the code free — e.g. “renders the empty state for an unknown id”, “/health returns 200”, “calls only the facade”.
over-specifies
Dictates the solution and leaves nothing to decide — e.g. “use a case not a cond”, “name the variable conn”.
the test
Ask: could two correct implementations both satisfy this? If yes, it constrains. If it admits only one shape of code, it over-specifies.

An Invariant is a constraint over every value — F6.1-INV1 (master), verbatim: “PortalWeb calls only the Portal facade and renders only the closed %Portal.Error{} set”. It forbids a class of code without writing any.

Defer, do not forbid

Scope's Out is where the line is sharpest. A weak spec forbids a concern outright; a strong one defers it — naming the later rung it moves to. The difference is the (→ F6.N) marker. “Ecto persistence” is not refused; it is (→ F6.3) — out of this rung, in a named later one. That turns a refusal into a constraint: it bounds the current build without pretending the deferred concern will never exist.

The figure below reads each Out entry from the rung's spec — — and reports the rung it defers to. The move is distinct from the hero: there you judge whether a statement constrains; here you see how an Out constrains by pointing forward rather than forbidding.

Each Out defers to a rung · pick one

Scope · Out multiple pipelines defers to a named later rung → F6.2 an Out that names a rung defers; an Out with no rung would forbid deferred → F6.2 · a constraint on this rung, not a permanent refusal
"multiple pipelines, protected scopes, and custom plugs" · deferred → F6.2 — out of this rung, in a named later one. The Out constrains by pointing forward, not by forbidding.

Scope's Out constrains by deferring — the (→ F6.N) marker turns a refusal into a bound on this rung, not a permanent forbidding.

The line on the Portal

On the Portal — the learning platform this course builds — the rung's spec, , holds the line in every section. Its Deliverable D6 constrains: “a liveness route get "/health", … returning 200 (a plain "ok") without touching the domain” — it fixes the route, the status, and the body, but names no plug. Its D4 constrains the call — the controller “calls only Portal.courses_of/1” (which returns the courses a user is enrolled in) — without dictating the control flow. And its Scope Out defers, not forbids.

The fragment below is the Scope Out, verbatim — five concerns, each with its (→ F6.N) marker. It is markdown, not code: the line between constraining and over-specifying is a property of the spec's words, and the deferral marker is the device that holds it.

# f6.1.md — Scope · Out (each Out names the rung it defers to, not a forbidding).

# concern left out of this rung          deferred to
multiple pipelines, scopes, custom plugs   → F6.2
Ecto persistence                           → F6.3
context wiring beyond courses_of/1          → F6.4
rich templates, components, and forms      → F6.5
LiveView                                    → F6.6

# Each line constrains THIS rung by pointing forward — it bounds the build,
# it does not over-specify how the code is written.
the principle

A spec constrains the build without over-specifying the solution — it states what must be true, never how to write the code, and Scope's Out constrains by deferring.

on the Portal

The rung's Out defers five concerns with (→ F6.2)…(→ F6.6); its Deliverables fix behaviour and name no implementation. Each is a bound, not a recipe.

A spec constrains the build without over-specifying the solution — and Scope's Out draws that line by deferring each concern to a named later rung.

Recap — bound the build, free the code

A spec constrains the build without over-specifying the solution: a constraint states what must be true and leaves the code free; an over-specification dictates the code and leaves nothing to decide. The test is whether two correct implementations could both satisfy it. Scope's Out draws the line at the level of whole concerns — it constrains by deferring, the (→ F6.N) marker turning a refusal into a bound on this rung. With the anatomy now read whole — the six sections, the five Ws, and the line every section holds — the chapter turns to deriving the proof a spec is accepted by.

A4.3 is complete: a spec is six named sections that constrain the build without over-specifying the solution. Return to the A4.3 module hub, then continue to A4.4 · From stories to a .stories.md from the A4 navigation.

References

The sources behind constraining without over-specifying, and where the line connects in the course.

Sources

Related in this course