Spec, plan, execute, verify, and the four places drift gets in

Day 2: Loops and your environment · Lesson 1 of 4 · 12 min

Drift is the gap between what the spec says and what the code does. It enters at four identifiable moments in the loop, each with a detector that costs under a minute.

Watch first · 5 min

Learnings from a No-Code Library: Keeping the Spec Driven Development Triangle in Sync

Drew Breunig, 2026-03-04. Watch for spec, tests and code as three corners that agree only because something holds them there. The chapter from 17:35 to 22:34 carries this lesson's argument; the rest is optional, outside today's schedule.

The loop you ran yesterday, drawn as a loop

Yesterday's plan, execute, verify ran as a line, and you stood at its end. Draw it as a ring, with the verified result feeding back into the spec, and the question changes from "did it work", which the test command answers, to "where can spec and code come apart unnoticed".

Böckeler's levels place today. Days 1 and 2 run spec-first, where the spec drives the work and may then go stale, not spec-anchored, where it is kept as the feature evolves. At spec-first nothing after the run compares the spec with the code, so the comparison has to happen inside the run, and that is where today works.

What drift actually is

Drift is not sabotage or a weak model. It is bookkeeping: the spec says four things, the code does three and a half, and no step in the run had to compare them. It shows up as a merged pull request that passes every test it ships with and still misses the ticket.

Where drift enters the spec, plan, execute, verify loop Four stages sit on a ring and the arrows run clockwise: spec at the top, plan at the right, execute at the bottom, verify at the left, and back to spec. A numbered marker sits on each arrow. Marker 1, spec to plan, sits on the arrow from spec to plan. Marker 2, plan to diff, sits on the arrow from plan to execute. Marker 3, diff to tests, sits on the arrow from execute to verify. Marker 4, run to run, sits on the return arrow from verify back up to spec, which is drawn dashed because it is the only arrow that crosses from one session to the next rather than joining two steps inside one. The legend below gives the detector for each marker in the same order: one, the criterion-to-command grid read against the plan; two, the diff's file list against the spec's bounds; three, delete the line and watch a named test go red; four, the decision lives in a file, not a transcript. spec plan execute verify 1 spec to plan 2 plan to diff 3 diff to tests 4 run to run detector 1 criterion-to-command grid, read against the plan 2 the diff's file list against the spec's bounds 3 delete the line, watch a named test go red 4 the decision lives in a file, not a transcript
The four drift points sit on the arrows between stages; each detector runs from outside the loop.

Drift point one: spec to plan

The plan quietly reinterprets a criterion, or covers three of four. The detector is Day 1's criterion-to-command grid, read against the plan before you approve it: every numbered criterion needs a row, and a row whose command would pass with the criterion unmet is a criterion the plan is not promising.

In Change B, the spec you run in Exercise 1, criterion 4 wants totals per currency, ordered by currency code ascending. A plan that tests keeping currencies apart and ignores the ordering has dropped half a criterion; the grid shows it in twenty seconds.

Drift point two: plan to diff

The agent changes a file the plan never named, for a reason that reads well in the transcript. The detector is a file list: what differs from the shipped sandbox/, held against the spec's bounds. A file outside them may be needed, but then the plan or the bounds were incomplete, and you say which before merging. Exercise 1 gives you the command and has you run it.

Drift point three: diff to tests

Green means less when the tests moved in the same diff as the code. Two questions settle it: did any existing assertion change, and does each criterion have a test that fails without its implementation?

The second has a cheap physical version: remove the line that implements a behavior, run the suite, and watch a named test go red. If nothing goes red, that behavior has no sensor. This probe is the one thing in this lesson to run now. Try it in a throwaway copy, never in sandbox/, which Exercise 1 copies. From the course root:

Swift lane
cp -R sandbox/swift day2-probe-work && cd day2-probe-work

Expect several tests to go red, not one; the one to find is testThousandsAreGroupedWithCommas. On line 24 of Sources/LedgerKit/TransactionFormatter.swift, change grouped(wholeUnits), at the end of the var text = … line, to String(wholeUnits), then run swift test. Unchanged, green is the line Executed 34 tests, with 1 test skipped and 0 failures (0 unexpected) in … seconds. It is not the last line: the Swift Testing summary prints after it and reads Test run with 0 tests in 0 suites passed either way, so look a few lines up for the line starting Executed.

swift test
Kotlin lane
cp -R sandbox/kotlin day2-probe-work && cd day2-probe-work

Expect several tests to go red, not one; the one to find is thousands are grouped with commas in the integer part only. On line 33 of src/main/kotlin/ledgerkit/TransactionFormatter.kt, change val grouped = group(whole) to val grouped = whole, then run ./gradlew test. Unchanged, green is the last line, BUILD SUCCESSFUL in ….

./gradlew test

Then remove the copy, edit and all:

cd .. && rm -rf day2-probe-work

Of the red tests, read the one whose name says what broke, since a green line never names the criterion it checked. Exercise 2 runs a suite written against the spec over the tree Exercise 1 leaves; each red test names its criterion.

Drift point four: run to run

Tomorrow's session starts with none of today's conversation. Before your first message it loads only the startup set, your CLAUDE.md files among them, and auto memory where it is on (the course lanes turn it off, so each day starts from its own files alone), so a decision that lives only in the transcript gets re-derived, differently. The detector is structural: the decision lives in a file. A spec. A line in CLAUDE.md, which Claude Code reads from ./CLAUDE.md or ./.claude/CLAUDE.md. A rule file at .claude/rules/<name>.md, whose frontmatter paths: glob list scopes it to the files it concerns.

Guides and sensors

Böckeler's other split is the frame worth keeping. Guides feed instructions forward: CLAUDE.md, rule files, the spec itself. Sensors report back: tests, linters, the diff, a review pass. The first three detectors are sensors: each one reports back on a run. The fourth is a guide, so it only works if the next session reads the file and follows it, which is why the cheaper fix, when one exists, is a sensor.

Most drift complaints arrive as requests for more guidance when what is missing is a sensor: a CLAUDE.md line spends context every session and depends on being followed, while an assertion fails loudly. A useful team norm: when someone asks for a new rule, ask first what would have gone red.

When drift does not matter

For a one-file spike you will read end to end and probably delete, the detectors cost more than the mistake they prevent: drift needs someone downstream to be surprised. Skip them on purpose and say so. A spike stops being one the moment a second person will read it, so start the detectors there.

Quick check

The tests are green, the diff is small, and criterion 3 was never implemented. Which detector should have caught it, and at which step?

The criterion-to-command grid, read against the plan before any code was written: criterion 3 had no row naming a command that would fail without it, so nothing in the run was ever obliged to deliver it. Point three's detector also fits, later: no test fails for want of criterion 3, which is why the suite is green. Either answer is right; the grid is the cheaper catch because it comes before any code.

Your agent changed a file the plan never mentioned. Why is that a signal rather than a tidy-up you can wave through?

Because the bounds are part of the contract you are holding the result against. A file outside them means the plan was incomplete or the bounds were wrong, and until you decide which, you cannot tell whether the extra change was necessary or invented.

Sources

Claude Code documentation accessed 2026-09-22 against version 2.1.280, and 2026-09-23 for cc-core-44.