Field course · Nº 28

Specs as the New Source Code

Writing requirements an AI can execute

When a capable model does the typing, the scarce artifact is no longer the code — it is the specification the code was generated from. This course teaches the craft of writing requirements precise enough to execute: intent without backseat driving, constraints and non-goals that close every silence, acceptance criteria that read as tests, and a generate-verify loop you run like a process, not a slot machine.

Module 01 The leverage moved

Something quietly inverted in the last few years, and most teams have not adjusted their working day to it. Producing a plausible implementation of a well-understood feature used to be the expensive part of software: hours of typing, syntax, boilerplate, and the small decisions that accumulate inside a function body. That cost has collapsed. What did not collapse — what got more expensive, in relative terms, because nothing else is competing for the bottleneck — is deciding precisely what the software must do, and stating that decision in a form something else can execute.

This module makes the shift concrete and gives you the diagnostic move the rest of the course depends on: when the output is wrong, ask first whether the words you supplied permitted it. Most of the time they did. That is not a comfortable finding, but it is an actionable one, because a spec silence is something you can fix at the source and a model's temperament is not. This course is about the specification, not about how models work internally (Guide Nº 13) or how to build systematic evaluations of them (Guide Nº 03); both are named here and then left alone.

Typing got cheap

Every production discipline has one input that is scarce relative to the others, and that input sets the pace of everything else. For thirty years in software that input was implementation labor: a competent engineer converting a decision into working code. Design meetings were cheap next to the weeks of typing they authorized, which is why the industry built its rituals — story points, sprint capacity, headcount planning — around implementation throughput.

When a capable model does the typing, that scarcity moves. The leverage shift is the migration of the binding constraint from producing code to specifying it. The code becomes an output: regenerable, disposable, cheap to replace wholesale. The specification becomes the thing you actually author, keep, revise, and argue about — because it is the only artifact whose quality still gates the result.

Two loops compared: in the old loop a human writes code and tests verify it, with the code as the scarce artifact and typing as the bottleneck; in the new loop a human writes a spec, a model writes the code, and the human verifies against the spec, with the spec as the scarce artifact and verification as the bottleneckThe old loopThe new loopHuman decides what to buildHuman writes the codescarce, load-bearing artifactTests verify the codebottleneck: typing hoursHuman writes the specscarce, load-bearing artifactModel writes the codeHuman verifies against the speccriteria decide, not tasteamend specbottleneck: spec + verificationthe constraint does not disappear — it relocates upstream
Figure 1.1 — The leverage shift. In the old loop the code is the artifact a human labors over and tests are the check; typing hours set the pace. In the new loop the human authors the spec, the model produces the code, and the human verifies the output against the spec's criteria — so the spec is the scarce artifact and the bottleneck moves to specification and verification. The dashed return edge, amending the spec rather than the code, is the high-leverage path this course keeps returning to.

Notice what the diagram does not say. It does not say the discipline got easier or that fewer engineering judgments are required. The same number of decisions must still be made — currency handling, retry semantics, what counts as a duplicate — and each one still has a right answer that costs thought. The change is only in who types them and where an unmade decision surfaces. In the old loop an unmade decision surfaced as a question in your own head while your hands were on the keyboard, and you resolved it, usually correctly, usually without noticing. In the new loop it surfaces as a confident line of generated code you did not think about at all.

The load-bearing idea

Cheap generation does not remove decisions from your system; it removes the moment where you were forced to notice you were making them. The spec is where you put them back.

The bug moved upstream

Here is the sentence this whole course exists to interrogate. A product manager on the wine-cellar team writes a ticket: let a user set a target price and get alerted when a wine drops below it. It is a perfectly normal requirement. Every human who reads it believes they understand it. Hand it to a capable model with access to the codebase and you get back working, tested, plausible code within minutes.

You also get back a coin flip. At least three implementations are legitimately derivable from those words, and they behave nothing alike in production. One alerts on every price sample recorded below the target, which means the nightly scrape emails the user every single morning for as long as the wine stays cheap. One alerts on any listing below target, including auction lots and foreign-currency offers the user cannot act on. One reads "when a wine drops below it" as a check performed at the moment the target is set — a one-shot comparison that never fires again. Module 3 dissects all three. What matters now is that none of them is a misreading. Each is a faithful execution of a sentence that failed to decide.

Anti-pattern — blaming the executor

Symptom: your postmortem sentence starts "the model hallucinated the alert logic" and the ticket that produced it was one line long. Corrective: before assigning fault, put the original request and the output side by side and ask whether a careful engineer, given only those words, could have produced the same thing. If yes, the defect is underspecification, and it lives in your text.

Underspecification is a silence in the spec that delegates a real decision without meaning to. Its distinguishing feature is that it does not feel like a delegation while you are writing. "Drops below" felt complete. It concealed three separate decisions — over what price series, at what moment, with what repeat behavior — and delegated all three to a system whose defaults you never inspected. The model resolved them the way it resolves everything: instantly, plausibly, and without flagging that a choice was made. That is a confident guess, and its danger is precisely that it does not look like one in the diff.

The reframe is uncomfortable in the way useful reframes usually are. It converts a complaint about someone else's tool into a defect in your own work product — which is exactly what makes it actionable, because your own work product is the thing you control.

You are the compiler's compiler

Stating intent precisely enough that a machine can execute it is not a new discipline; it is the only discipline software has ever had. A programming language is a contract for stating intent unambiguously, and a compiler is the machine that executes it literally. What changed is the altitude of the input. You now write at a level above the source, in constrained natural language, and something downstream compiles your words into code. You are the compiler's compiler — and your bugs are ambiguities.

The analogy is load-bearing, so it is worth stating where it holds and where it breaks. It holds in the direction of literalism: like a compiler, the model does exactly what your text licenses, not what you meant. It breaks in one crucial respect, and the break is what makes this hard. A compiler refuses ambiguous input — an unresolved symbol is a build error, and the error message points at the line. A model resolves ambiguous input silently and hands you a passing build. You get no diagnostic, no line number, no failure at all until behavior diverges from an expectation you never wrote down.

From your other domain

You have done this job before under a different name. A contract, a statute, and a spec are all instruments read by a party who is not you and who will act on the words as written. In litigation you learned that every silence is eventually occupied by whoever benefits from occupying it. Here the occupant has no motive at all — it is simply a confident default — but it arrives faster and it never asks a clarifying question.

Now the honest boundary, because the reframe over-rotates easily. Not every failure is yours. If the spec named the interface, the boundary behavior, and the criteria, and the output still calls a standard-library function that does not exist, that is a model failure — a hallucinated interface, and no amount of additional prose would have prevented it. The test is mechanical: reconstruct the input the model actually had, and ask whether the output is a defensible reading of it. If yes, the defect is upstream in your text. If the output contradicts something the input plainly stated, or invents an entity nothing in the input suggested, the defect is downstream in the generation, and the corrective is regeneration, a different model or effort level, or a tighter grounding pass — not another paragraph of spec.

Worked distinction

Upstream (yours): the spec said "alert when the price drops below target" and the code fires on every nightly sample. Defensible reading of your words → fix the spec. Downstream (the model's): the spec said "deliver via the existing notification service, interface attached" and the code calls notifications.sendBatch(), a method that appears nowhere in the attached interface. Contradicts the input → fix the generation.

What the reframe changes about your day

Accept the shift and your calendar has to move, because the hours you used to spend converting decisions into syntax are now available and the hours you need for authorship and verification were never budgeted. Teams that adopt AI assistance without rebalancing get the worst arrangement available: generation speed at the front, unchanged review capacity at the back, and a growing queue of fluent code nobody has checked against anything.

Horizontal bar chart comparing where effort goes in the old and new loops: authoring the spec grows, typing shrinks to near zero, verification grows, and total time fallsWhere the hours go on one featurerelative effort, same feature delivered to the same qualityOLD LOOPauthor spec1 unittype code6 unitsverify2 unitsNEW LOOPauthor spec3 unitstype code0.3 unitsverify3.5 units
Figure 1.2 — Where the hours go. The same feature, delivered to the same quality, in both loops. Typing collapses toward zero; authoring roughly triples and verification nearly doubles, because both now carry weight that typing used to absorb implicitly. Total effort falls — but only for a team that actually moves the freed hours into the two bars that grew, rather than into starting more work.

Three concrete changes follow. First, writing becomes the engineering activity, not the overhead around it: an afternoon spent tightening a spec is not preparation for the work, it is the work, and it is the highest-return afternoon on the board. Second, verification needs a named owner and real time, because generated code arrives faster than any team's habitual review capacity. Third, your unit of planning shifts from "a story someone can implement in a sprint" to "a unit a model gets right in one pass, with a human gate after it" — the subject of module 5.

A measurable version of the claim

If the shift is real for your team, you should see it in the artifacts. Over a month, the ratio of spec-and-criteria text to shipped diff should rise; the share of review comments citing a spec criterion rather than a style preference should rise; and defects should increasingly be traceable to a sentence you can point at. If none of those move, you have adopted fast generation without adopting the discipline, which is the arrangement that produces the fluent unreviewed backlog.

Module 02 Intent vs. implementation

Module 1 established that your ambiguities now compile. The obvious over-correction is to remove all ambiguity by specifying everything, down to the function bodies — and that produces a different failure, quieter and more expensive, in which you forfeit the executor's competence and import your own design bugs at full strength. The craft is a line, not a direction: state what must be true precisely, and leave how it becomes true open unless you have a reason to close it.

This module gives you a vocabulary for locating a spec on that line — the altitude ladder — the named failure at each rung, and a defensible rule for when to drop altitude and pin the how. Nothing here is a matter of taste; each rung has a symptom you can observe in your own drafts.

Destination, not driving directions

Intent is a claim about the state of the world after the work is done, stated without prescribing the mechanism that gets there. "After this change, a user who has set a target price on a tracked wine receives exactly one notification the first time that wine's retail price crosses at or below the target" is intent. "Add a target_price column, then in the ingestion handler compare each new sample to it and call the notifier" is driving directions.

The difference is not stylistic. Intent is checkable — you can observe the world afterward and decide whether the claim holds. Driving directions are not: you can follow every step faithfully and still produce something that fails the purpose, and there is no test you can write against "did it add the column in the way I imagined." The moment you write directions, you have also quietly made yourself responsible for their correctness, because the executor is now implementing your design rather than solving your problem.

This is also where the executor's competence becomes usable rather than wasted. A capable model knows the idioms of the codebase you grounded it in, knows fifty ways to structure a crossing detector, and will pick a reasonable one if you leave the choice open. Every choice you pre-make is a choice it cannot improve on — and a place where your 2 a.m. instinct becomes a permanent constraint on the system.

The load-bearing idea

Specify the destination with enough precision that arriving somewhere else is detectable; specify the route only where arriving by the wrong road is itself a defect. Everything else is the executor's to decide, and leaving it open is what makes the executor worth having.

The altitude ladder

Specs sit on a ladder. Each rung is a legitimate way to write English about software, and three of the four rungs fail — for different reasons, with different symptoms.

A four-rung vertical ladder of specification altitude from wish at the top through intent and intent-plus-constraints to dictated implementation at the bottom, with the executable band highlighted and each rung labeled with its failure modeSpecification altitudetoo hightoo low1 · Wish"Make price alerts great for users."fails: unverifiable2 · Intent"A user is notified when a tracked wine crosses below target."fails: under-constrained3 · Intent + constraints + non-goals + criteriaretail series only · at ingestion · one per crossing · ≤60 minexecutable band4 · Dictated implementationif (s.price <= t.target) notifier.send(...)fails: strangled
Figure 2.1 — The altitude ladder. Four rungs, one of which works. A wish is unverifiable and constrains nothing; bare intent is verifiable but under-constrained, so the executor legitimately picks among divergent solutions; dictated implementation forfeits competence and imports your design bugs. The executable band — intent plus constraints, non-goals, and acceptance criteria — is where a spec becomes something a capable executor can act on without asking.

Read the rungs by their symptoms. A wish announces a value without a claim: no observation could contradict it, so it constrains nothing and nobody can tell you they are done. Bare intent is a real claim and therefore an improvement, but it admits many implementations — this is the rung where the ambiguity fork of module 3 lives. The executable band is intent plus the constraints that rule out the readings you do not want, the non-goals that close the silences, and the criteria that decide done. The bottom rung reads like code with the syntax removed.

A quick placement test

Ask two questions of any spec paragraph. Could an observation falsify it? If no, you are at rung 1 — add a claim. Could two competent engineers implement it in ways that behave differently for a user? If yes, you are at rung 2 — add constraints. If you find yourself writing conditionals and variable names, you have fallen to rung 4 — and unless a stated reason justifies it, move back up.

When to drop altitude

Rung 4 is not forbidden; it is expensive, and the price is worth paying when arriving by the wrong road is itself a defect. There are four recurring legitimate reasons, and each one has a structural tell: it can be written as a named constraint with a reason attached, rather than as pseudocode.

Reason to pinExample on the wine-cellar productHow it is written
Regulatory or contractual mandateAlert-delivery events must be written to the append-only audit log in the existing event envelope, because the retention control requires a tamper-evident record of every customer communicationConstraint + the control it satisfies
Interop contractNotifications go through NotificationService.send(); three other services consume its emitted events and a second path would silently bypass themConstraint + the consumer that breaks
Performance budgetThe crossing check runs inside ingestion, not as a scheduled sweep, so p95 alert latency stays ≤ 60 minutes from ingestionConstraint + the number it protects
Codebase conventionMoney is stored as integer minor units with an ISO-4217 code, per the existing price_samples columns; mixing in floats corrupts comparisonsConstraint + the existing artifact it matches

The reason clause is not decoration. It does three jobs: it lets the executor satisfy the real requirement in a way you did not anticipate, it tells a future reader when the constraint may be retired, and it converts a taste assertion into a reviewable engineering claim. "Use the notification service" is an order. "Use the notification service, because three downstream consumers subscribe to its emitted events and a direct send bypasses them" is a fact, and facts survive personnel changes.

From your other domain

This is the difference between a covenant and a recital. A bare obligation with no stated purpose is enforced literally and mechanically forever, including in circumstances nobody contemplated. An obligation with its purpose on the record can be applied intelligently at the edges, and everyone can tell when it has become obsolete.

Over-specification strangles

The failure at rung 4 is quiet, which is why it survives in teams that would never tolerate a wish. A dictated spec produces code that matches the dictation, tests pass, review is uneventful — and you have shipped your own unexamined design at machine speed, with none of the friction that used to catch it.

Three costs, in ascending order of expense. You forfeit competence: the executor had a better structure available and was not permitted to use it. You import your design bugs at full strength: the off-by-one you would have noticed while typing is now specified, so it survives review as an intended requirement. And you make the spec unmaintainable: a spec that mirrors an implementation must be rewritten every time the implementation legitimately changes, so it rots, drifts, and stops being the source of truth — which quietly returns you to a world where the code is the only real artifact.

Anti-pattern — the strangled spec

Symptom: your spec contains conditionals, variable names, or a function decomposition; a reader could reconstruct the diff from it; and you cannot state, for any given line, what would go wrong if the executor did it differently. Corrective: for each implementation-level line, either attach a reason and promote it to a named constraint, or delete it and replace it with the observable outcome it was protecting.

Three-column comparison of the same price-alert requirement written as a wish, at executable altitude, and as dictated pseudocode, with failure labels under the first and third columnsOne feature, three altitudesWish"Users should get timely,useful alerts when the winesthey want get cheaper."no claim to falsifyno boundary statednobody can say "done"fails: unverifiableExecutableNotify once when the trackedretail price crosses at orbelow target, at ingestion.constraint: ≤60 min, retail onlynon-goal: no digests, no chartscriteria: 7 claims + oracleshow: the executor's to chooseworks: bounded and checkableDictatedadd col target_pricein ingest.py, loop rowsif s.price <= t.target: notifier.send(u, w)your design bug is nowa requirement (no re-arm)fails: strangled
Figure 2.2 — One feature, three altitudes. The same price-alert requirement written three ways. The wish states a value nothing can falsify. The executable version states the outcome, bounds it with constraints and non-goals, and leaves the mechanism open. The dictated version prescribes a loop and a comparison — and in doing so promotes an omission (no re-arm semantics) from an oversight to a specified requirement, where review will not question it.

Module 03 The executable spec

This is the longest module in the course and the one the rest depends on. Module 1 established that ambiguity compiles; module 2 located the altitude at which a spec becomes actionable. Now you learn the anatomy: the four load-bearing parts of a spec that a capable executor can act on without asking, what failure class each part prevents, and the tightening pass that converts a real one-line request into a real executable spec.

The vehicle is the running example, taken seriously rather than gestured at. You will see the one-liner branch into three defensible implementations, watch each added sentence kill exactly one branch, and end with a canonical spec every later module refers back to. If you take one habit from this course, take the tightening pass — it is the highest-return twenty minutes in the loop.

The model fills every silence

Start from a property of the executor that is easy to state and hard to internalize: by default, ambiguity is not left open. Nothing in a single generation pass forces the model to pause on an unresolved decision and wait for you; unless the harness is set up to ask, every gap is closed at generation time, from priors, at the same confidence as everything else in the output, and with no marker distinguishing the resolved gaps from the parts you actually specified.

That property makes silence an active choice rather than an omission. Every decision your spec does not make is a decision you delegated — to whatever default the model finds most plausible, which is not the same as your intended answer and which you never inspected. Sometimes that default is fine. For currency handling in a European wine marketplace, it is not.

Compare it to the failure mode you already know from working with people. A junior engineer given an ambiguous ticket has three options: ask, guess and mention the guess, or guess silently. Good ones ask; average ones flag the guess in the pull request; only the worst guess silently. The executor here is not badly behaved — but its default is the third option: it has no persistent stake in the outcome and nothing forces it to surface a resolution it made mid-generation. Agent harnesses increasingly add a plan or clarify step that does ask up front, and you should use it — but you cannot rely on it to raise the specific silence that matters. You can partially recover the second behavior by asking for assumptions to be listed, and you should. You cannot rely on it.

The load-bearing idea

Silence is not an absence in a spec; it is an instruction that reads "choose for me, and do not tell me you chose." The craft of specification is deciding which silences you are willing to sign.

You will not close every silence, and you should not try; the useful question is which of the remaining ones you want reported back to you.

A cheap partial mitigation

End a generation request with: "before the implementation, list every assumption you had to make that the spec did not decide, and mark any you consider risky." You will get a useful list roughly as often as not, and the items it surfaces are usually the silences you did not see. Treat it as a smoke detector, not a substitute for the tightening pass — the assumptions it fails to mention are exactly the ones its priors made most confidently.

The fork

Take the one-liner literally: let a user set a target price and get alerted when a wine drops below it. Here are three implementations, each of which a careful engineer could defend as a faithful reading, and each of which is wrong in production.

Branch A — every-sample spam. Read "drops below" as a condition on the current price. The crossing check runs against each incoming sample and fires whenever the price is under target. The 2016 Margaux tracked at €80 against a €75 target settles at €72 and stays there; the nightly scrape produces eleven notifications in eleven days. The words never said "once". Nothing distinguishes a price that just dropped from a price that dropped a week ago, because the spec never introduced the concept of a crossing.

Branch B — wrong listings. Read "a wine" as any offer for that wine anywhere in the data. The system holds retail listings, auction lots, and merchant offers in several currencies. A Bordeaux auction lot opening at €40 is genuinely "a price below €75", so the user gets alerted about a lot they cannot buy at that price, and separately about a $78 US listing that converts to €72 today and €76 tomorrow. Every alert is technically true and none is actionable. The words never said which price series counts.

Branch C — one-shot. Read "get alerted when a wine drops below it" as scoped to the act of setting the target: check now, notify if already below, done. This is the reading that produces a feature which never fires for anyone and generates almost no support tickets, because users assume it is working. The words never said the check is continuous.

Branching diagram: one vague requirement forks into three defensible implementations A, B and C, while the tightened spec beside it collapses the fork to the single intended branch and shows the other two as dead dashed pathsThe one-liner forksThe tightened spec collapses it"alert when a winedrops below target"3 silencesA · every-samplefires on each scrape11 emails in 11 daysB · wrong listingsauction lots, foreigncurrency, unactionableC · one-shotchecks only at set timenever fires againtightened spec+ crossing, not level+ retail series only+ display currency+ at ingestion, alwaysA ✕C ✕intendedbranchB ✕ (currency)each branch is a faithful reading of the words as written — the fork is in the text, not the executor
Figure 3.1 — The ambiguity fork. One under-specified requirement branches into three implementations that are each legitimately derivable from its words: A fires on every sample below target, B alerts on listings the user cannot act on, C checks only once at the moment the target is set. The tightened spec adds four decisions — crossing rather than level, retail series only, the user's display currency, continuous evaluation at ingestion — and the fork collapses to the single intended branch. The dead branches are dashed: they were never model errors, they were permitted readings.
Anti-pattern — the one-line prompt as a spec

Symptom: the request fits in a chat box, reads complete to you, and contains at least one verb whose timing, scope, or repeat behavior is unstated ("when it drops", "if it changes", "after they sign up"). Corrective: for each such verb, write down which price series / which entity / at what moment / how many times. If you cannot answer one of them yourself, you have found a product decision you were about to delegate.

Anatomy: the four load-bearing parts

An executable spec has four parts. They are not sections of a template to be filled in dutifully; each exists to prevent a specific failure class, and you can tell whether a part is doing its job by asking which failure it currently blocks.

Intent states what must be true after the work. It prevents drift — the executor solving a nearby problem, or a later reader optimizing the implementation in a direction that abandons the purpose. Intent is also the tiebreaker when the other parts under-determine a decision, which they always will.

Constraints rule out otherwise-valid solutions, each with its reason attached. They prevent invalid solutions: implementations that satisfy the intent while violating something the intent does not mention — a latency budget, a compliance control, a schema convention, an interop contract.

Non-goals state what you are not building. They prevent scope invention: the executor's helpful additions, which arrive dressed as thoroughness and cost you review time, surface area, and sometimes correctness.

Acceptance criteria are falsifiable claims about observable behavior, each with the check that decides it. They prevent unverifiable done: the state in which the work is finished when someone feels finished. Module 4 is entirely about this part.

The tightened price-alert spec dissected into four labeled regions — intent, constraints, non-goals, acceptance criteria — each annotated with the failure class it prevents, beside the stripped one-line wish version for contrastAnatomy of an executable specThe wish, for contrastINTENTOne notification the first time a tracked wine'sretail price crosses at or below the user's target.prevents driftCONSTRAINTSretail series only · user's display currencyevaluated at ingestion · p95 ≤ 60 minvia NotificationService · audit-loggedprevents invalidsolutionsNON-GOALSno price-history charting · no digest emailsno push channel · no multi-target per wineprevents scopeinventionACCEPTANCE CRITERIA7 given/when/then claims, each with its oracleincl. price == target, no data yet, re-armpreventsunverifiable done"Let a user set a targetprice and get alerted whena wine drops below it."0 of 4 parts presentevery failure class open
Figure 3.2 — Anatomy of an executable spec. The tightened price-alert spec dissected: intent prevents drift, constraints prevent solutions that are valid-but-wrong, non-goals prevent scope invention, and acceptance criteria prevent an unverifiable notion of done. The one-line wish on the right contains none of the four, which is why it leaves all four failure classes open — it is not a shorter spec, it is a different kind of document.

Non-goals: the most-skipped move

Of the four parts, non-goals are the one teams omit, and the omission is systematic rather than random. Writing down what you are not building feels like padding — everyone knows we are not building a charting page, we are building an alert. Nobody in the room disagrees, so nobody writes it down, and the executor is not in the room.

Here is what the omission buys you on the price-alert unit. Generated diff, first pass, spec without non-goals: the alert feature, plus a price-history sparkline component on the wine detail page, plus a daily-digest option in user settings defaulting to on, plus a preferences table to store it. All of it is competent, tested, and reasonable — a thoughtful engineer building an alerts feature might well propose exactly that roadmap. None of it was asked for. It costs you a review of three times the intended surface, a schema migration you did not plan, a default-on email channel with compliance implications nobody assessed, and — the expensive part — a diff in which the two lines that actually matter are now buried.

From your other domain

You have drafted against this reader before. In litigation you learn to draft assuming the other side reads every clause looking for the space you left them, and you close those spaces explicitly even when the intent seems obvious to everyone at the table — because "obvious" is not a term of art and the person enforcing the document was not at the table. The executor here does the same thing without malice and at machine speed. A non-goal is the software equivalent of "for the avoidance of doubt": it exists precisely because a reasonable reader could otherwise reach a reasonable conclusion you did not intend.

Good non-goals share three properties. They name things a competent executor would plausibly volunteer — "no price-history charting" is useful because charting is exactly what an ambitious implementation adds; "no blockchain integration" is noise. They are specific enough to be checkable in a diff. And where the exclusion is temporal rather than permanent, they say so, because "not in this change" and "never" have different consequences for the interfaces the executor leaves behind.

Non-goals from the price-alert spec

No price-history charting or sparkline components — deliberately deferred; the alert must not depend on any new visualization. No digest or daily-summary emails — this change delivers one notification per crossing through the existing per-event channel only. No push or SMS channel — email only in this change; the notification service's channel abstraction may be used but no new channel is configured. No multiple targets per wine per user — one target per user per wine; a second set overwrites the first. No changes to the ingestion schedule or scraper — the crossing check consumes samples as they arrive and must not alter ingestion cadence.

Notice that the last two do double duty. "One target per wine, a second set overwrites" reads as a non-goal but also decides a real behavior, which means it will generate an acceptance criterion. That overlap is fine and common: the parts are a checklist for finding decisions, not disjoint categories to be policed.

The tightening pass

Now the pass itself. The input is the one-liner; the output is the canonical spec every later module in this course refers to. Watch each added sentence do one job.

PRICE-TARGET ALERTS — executable spec

INTENT
A user who has set a target price on a tracked wine receives exactly one
notification the first time that wine's retail price crosses from above the
target to at or below it. While the price remains below an already-alerted
target, no further notifications are sent.

CONSTRAINTS
- Only the retail price series counts. Auction lots and bid-only listings are
  excluded, because a user cannot transact at those prices.  [kills branch B]
- Comparison happens in the user's display currency, converting the listing
  amount at the rate stored with the sample; money is integer minor units plus
  an ISO-4217 code, matching price_samples.               [kills branch B]
- The check is evaluated on every price-sample ingestion, continuously, not
  only when the target is set or edited.                   [kills branch C]
- Alert state re-arms only when a subsequent retail sample closes strictly
  above the target, or when the user edits the target.     [kills branch A]
- p95 latency from sample ingestion to notification hand-off is 60 minutes,
  measured over a rolling 7 days.
- Delivery is via NotificationService.send(); three downstream consumers
  subscribe to its emitted events.
- Each delivery is written to the append-only audit log in the standard event
  envelope, per the customer-communications retention control.

NON-GOALS
- No price-history charting or sparkline components.
- No digest or daily-summary emails; per-event delivery only.
- No push or SMS channel in this change; email only.
- One target per user per wine; setting a second overwrites the first.
- No changes to ingestion cadence or the scraper.

ACCEPTANCE CRITERIA  (each with its oracle — see module 4)
AC1  Given a tracked wine at EUR 80 with target EUR 75, when a retail sample
     of EUR 74 is ingested, then exactly one notification is delivered within
     60 minutes.                                    oracle: integration test
AC2  Given AC1 has fired and the price stays at EUR 74, when three further
     samples are ingested, then no additional notification is delivered.
                                                    oracle: integration test
AC3  Given AC1 has fired, when a retail sample of EUR 81 is ingested and then
     one of EUR 73, then exactly one further notification is delivered.
                                                    oracle: integration test
AC4  Given a target of EUR 75, when a retail sample of exactly EUR 75 is
     ingested, then one notification is delivered (at-or-below is inclusive).
                                                    oracle: unit test
AC5  Given an auction lot at EUR 40 for a tracked wine with target EUR 75,
     when it is ingested, then no notification is delivered.
                                                    oracle: unit test
AC6  Given a tracked wine with no retail samples yet, when a target is set,
     then no notification is delivered and no error is raised.
                                                    oracle: unit test
AC7  Given a detected crossing not yet dispatched, when the user lowers the
     target, then the pending notification still delivers against the target
     in force at detection, and the alert re-arms for later crossings.
                                             oracle: integration test
AC8  Over the 7 days after release, p95 ingestion-to-hand-off latency for
     delivered alerts is <= 60 minutes.  oracle: SQL over notification_events

Four sentences did the structural work. "Crosses from above to at or below" killed branch A by replacing a level condition with a transition. "Retail series only" and the display-currency rule killed branch B. "Evaluated on every ingestion, continuously" killed branch C. The re-arm sentence closed the silence that branch A had exploited from the other side — what happens on the fifth consecutive qualifying sample — and AC4 decided the boundary that no branch had even raised: what happens at exactly the target.

The load-bearing idea

A tightening pass is not "add detail until it feels thorough." It is: enumerate the readings your text permits, then add the minimum sentence that eliminates each unwanted one. If an added sentence does not kill a branch, you have added length, not precision.

The repeatable checklist, extracted from that pass, is four questions per verb in your intent. Over what data? — which series, which entities, which tenancy scope. At what moment? — on write, on read, on a schedule, on demand. How many times? — once ever, once per occurrence, every time, and what re-arms it. At the boundary? — what happens at exactly the threshold, at zero, at absent data, at a concurrent edit. Nearly every silence that has cost me a regeneration was one of those four, and a spec that answers all four for every consequential verb is usually already executable.

Module 04 "Done" you can check

The fourth part of the anatomy deserves its own module, because it is the part that converts a specification from a description into an instrument. Criteria are what you review against, what you regenerate against, and what ends the loop in module 8. Without them, "done" is a feeling that arrives when someone stops looking.

Two ideas carry the module. First, an acceptance criterion is a falsifiable claim plus a decision procedure — the claim alone is half a criterion. Second, edge cases are not a testing concern to be discovered later; they are specification content, because every boundary you do not claim is decided by the executor's priors, silently, at generation time.

A spec you can't verify against is a wish

A criterion has two halves. The claim: a statement about observable behavior that some possible observation would contradict. The oracle: the specific check that makes that observation and returns pass or fail. Drop the first half and you have a task; drop the second and you have an aspiration.

The test for the first half is falsifiability, and it is brutally quick to apply: describe a system state in which the criterion is definitely violated. "Alerts are reliable" — try it. Reliable compared to what, measured over what window, failing at what threshold? There is no state you can describe that unambiguously violates it, which means there is no state that satisfies it either, which means it is decorative. "No more than 1 in 1,000 crossings goes un-notified, measured monthly over delivered-versus-detected crossings" fails cleanly at 2 in 1,000.

The test for the second half is the harder one, and it is the half most teams skip: name the artifact that decides it. Not "we'll test it" — which test, over what data, run where. An unowned criterion is verified by whoever remembers it at the moment of shipping, which in practice means the criteria that are easy to eyeball get checked and the ones that matter get assumed.

The load-bearing idea

Criterion = falsifiable claim + named oracle. If you cannot describe the observation that fails it, it is a wish; if you cannot name the check that makes that observation, it is an intention. Both feel like rigor while providing none.

Criteria that read as tests

The most reliable shape for a criterion is given/when/then, because the shape forces the three things vagueness hides: the starting state, the triggering event, and the observable consequence. "Given a tracked wine at €80 with a target of €75, when a retail sample of €74 is ingested, then exactly one notification is delivered within 60 minutes." Every noun is concrete, the trigger is a specific event in the system, and the consequence has a count and a deadline.

The shape also does something subtler: it forces you to say what state the system was in beforehand, which is where most under-specification hides. AC2 and AC3 from the canonical spec are the same trigger as AC1 with different givens — price already alerted, price recovered then dropped again — and those givens are precisely the re-arm semantics that branch A got wrong. You cannot write a given/when/then set for this feature without deciding re-arm behavior, which is exactly why the shape is worth the small ceremony.

Now the oracles. They are not all tests, and forcing them all into unit tests is a common way to end up with criteria that pass while production misbehaves.

CriterionRight oracleWhy not a unit test
AC4 — exactly €75 against a €75 target delivers one notificationUnit test on the crossing predicateIt is a unit test; pure boundary logic, no I/O
AC1 — €74 sample delivers one notification within 60 minIntegration test through ingestion to a fake notifierSpans ingestion, state, and dispatch; a unit test would mock away the thing being claimed
AC8 — p95 ingestion-to-hand-off ≤ 60 min over 7 daysSQL over notification_events joined to sample timestampsA statistical property of production traffic; no test can observe it
Audit-log constraint — every delivery recorded in the envelopeQuery comparing delivery count to audit-record count for a periodThe claim is about completeness across all deliveries, not one path
Notification renders correctly in the user's localeScripted manual probe with two seeded accountsHuman judgment on presentation; automating it costs more than it returns here
Cross-reference

Guide Nº 26 goes deep on what verification can and cannot establish, and Guide Nº 08 on verifying code you did not write. This module borrows one idea from each: a criterion is only as good as the oracle that decides it, and the oracle's coverage is a claim in its own right that deserves the same scrutiny as the criterion.

Flowchart mapping five price-alert acceptance criteria to their oracles — unit test, integration test, SQL query, manual probe — with one unverifiable criterion dead-ending with no oracle reachableCriteriaOraclesAC4 · price exactly equals targetAC5 · auction lot must not alertAC1–AC3 · crossing, hold, re-armAC8 · p95 latency ≤ 60 min / 7 daysLocale rendering of the email"alerts should be reliable"unit test · crossing predicateintegration test · ingest→notifySQL · notification_eventsscripted manual probeno observationcould fail it
Figure 4.1 — Criteria to oracles. Each acceptance criterion routes to the specific check that decides it, and the checks are deliberately heterogeneous: pure boundary logic to a unit test, cross-component behavior to an integration test, a statistical property of production traffic to a SQL query, presentation to a scripted manual probe. The dashed criterion at the bottom reaches no oracle at all — nothing could observe it failing, which is the operational definition of a wish.

The spec carries its own tests

Once criteria are written as given/when/then with named oracles, something useful becomes available: the same document drives generation and verification. You hand the executor the spec and ask for the implementation and the tests the criteria imply, then check the returned tests against the criteria you wrote rather than against your sense of coverage.

Two cautions make this safe rather than circular. First, review the generated tests as adversarially as the implementation — a test written by the same pass that wrote the code will happily assert the behavior the code has rather than the behavior the criterion claims. The check is mechanical: for each criterion, find the test, read its assertion, and confirm the assertion is the criterion's "then" and not a paraphrase of the implementation. AC2's test must assert that no notification is delivered after three further samples; a test that merely asserts the dispatcher was called once during the whole scenario passes for the wrong reason.

Second, keep the criteria in the spec as prose, not only as test names. Tests express claims in a language optimized for machines and for the person who wrote them; the spec is what a reviewer, a compliance auditor, or you-in-six-months reads to know what was promised. When they disagree, the spec is authoritative and the test is the defect — an inversion that only holds if the criteria stayed legible.

Anti-pattern — criteria you can't verify against

Symptom: the criteria section contains words like reliable, performant, intuitive, appropriate, or graceful; or every criterion has the same oracle ("unit tests"); or nobody can say which check decides criterion three. Corrective: for each criterion, write the sentence "this fails if …" and name the artifact that would notice. Criteria that survive both are real; the rest were intentions.

Edge cases as criteria

Here is where a litigator's instincts are worth more than an engineer's. The habit of enumerating boundary conditions before anyone has built anything — what happens at exactly the threshold, at zero, on the last day, when two parties act simultaneously — is the same habit that produces good specs, and it belongs in the criteria rather than in a testing phase.

The reason is specific to this way of working. In the old loop, an unenumerated boundary surfaced during implementation: you wrote if price < target, paused for half a second on whether it should be <=, and resolved it. In the new loop nobody pauses. The boundary is decided by whichever comparison operator the model defaults to, the tests generated alongside assert that same choice, review sees a consistent story, and the decision is never made by a human at all. Four boundaries on the price-alert unit are worth their sentences:

  • No price data yet. A user sets a target on a wine the scraper has never priced. Claim: no notification, no error. Without it, the plausible implementations include a null comparison that throws and a treat-null-as-zero that alerts immediately.
  • Price exactly equals target. Claim: at-or-below is inclusive, so €75 against a €75 target alerts once. This is a product decision — users read "target" as "alert me at this price" — and it has no technically correct answer to be derived.
  • Currency mismatch. A US listing at $78 against a €75 target. Claim: compare in the user's display currency using the rate stored with the sample, and if no rate is available, do not alert and record the skip.
  • Target edited while an alert is pending. The user lowers their target from €75 to €70 in the minutes between detection and dispatch. Claim: the pending alert is evaluated against the target in force at detection time and still delivers; the edit re-arms for subsequent crossings.

The last one is the kind that never appears in a first draft and always appears in production. It is also the one where the executor's default is least likely to match your intent, because it involves a concurrency decision that a training corpus has no consensus on.

The boundary sweep, in practice

For each criterion you have written, ask four questions and keep any answer that is not obviously implied: what happens at exactly the threshold; what happens with zero or missing data; what happens if the same trigger arrives twice; what happens if the user changes the inputs mid-flight. On the price-alert unit that sweep produced AC4, AC6, and AC7 directly, plus the currency rule that became a named constraint — three of the eight criteria, and one of the constraints, came from the sweep rather than from the feature description.

Two criteria sets compared against the same three-transition intent: a happy-path-only set covers the first transition and leaves the hold and re-arm transitions to the executor's priors, so every criterion passes while the intent fails; the AC1 to AC3 set covers all three transitions and the intent is metThe intent's transition spaceT1 · crosses to at-or-belowmust notify exactly onceT2 · holds below, more samplesmust stay silentT3 · recovers, crosses againmust notify once moreCriteria set A — written from the happy pathAC1 · covereddecided by youno criteriondecided by priorsno criteriondecided by priorsevery criterion passes · the intent still fails — the verification gapCriteria set B — derived from the transition spaceAC1 · notify onceintegration testAC2 · no further notificationintegration testAC3 · re-arm, notify onceintegration testevery criterion passes · the intent holds — coverage was designed, not hoped for
Figure 4.2 — The verification gap. The intent spans three transitions: cross, hold, recover-and-cross. A criteria set written from the happy path claims only the first, so the other two are decided by the executor's priors rather than by you — and because nothing claims them, nothing can fail. Every criterion passes and the feature is still wrong. Deriving criteria from the transition space instead closes the gap: AC1–AC3 leave no transition unclaimed, which is why coverage of the state space, not the count of criteria, is what makes a spec verifiable.

Module 05 Cutting the work

You now have a spec good enough to execute. The next question is how much of it to hand over at once, and it is not a question of the executor's capacity — a capable model will happily return two thousand plausible lines. It is a question of where errors become detectable. Inside a large unit, a mistake made early is elaborated consistently by everything after it, so the output arrives internally coherent and wrong in a way that reads like a design.

This module gives you a sizing rule, a sequencing rule, and the gate discipline that makes both worth having. The shape it produces is not novel — it is the same shape careful engineering organizations have always used — but the reason for it is different, and the reason determines where the cuts go.

Size to one-pass reliability

The sizing signal is one-pass reliability: the probability that a unit comes back correct without iteration. You want units where that probability is high — not because iteration is expensive, but because iteration on a large unit is where correlated errors hide.

Consider what actually happens when a unit is too large. The executor makes an early decision — say, that alert state lives in a new alert_state table rather than as columns on the target row — and then writes eight hundred lines that are all consistent with that decision: the migration, the repository, the dispatch path, the tests, the fixtures. If the decision was wrong, nothing in the output looks wrong. There is no local defect to find. The failure is architectural and uniform, and your review, which is optimized for spotting local anomalies, is exactly the wrong instrument.

The opposite failure is real too. Cut into twelve micro-units and you pay setup, context assembly, and a human gate for each one, on work that would have been correct in a single pass — plus you create integration seams that did not need to exist. When gate overhead exceeds the error cost it prevents, you have over-decomposed.

Curve chart showing one-pass success probability falling as unit scope grows while per-unit gate overhead cost rises as units shrink, with the practical band for cutting work highlighted between the two curvesWhere to cutunit scope →cost / probabilityone-pass success probabilitygate + setup overhead per unitthe band you cut inover-decomposedmonolithicone schema, one behavior,or one interface at a time
Figure 5.1 — The one-pass band. As unit scope grows, the probability of a correct single pass falls and errors become correlated rather than local. As units shrink, per-unit gate and context-assembly overhead rises until it exceeds the errors it prevents. You cut in the band between: roughly one schema change, one coherent behavior, or one interface per unit — small enough that a wrong early decision cannot be elaborated for eight hundred lines, large enough that the gate earns its cost.

Practical heuristics for the band, on a codebase you know: one unit changes one schema or introduces one interface or implements one coherent behavior — not three. A unit's spec slice fits on a page. And the strongest signal, if you have the history: if you cannot predict what the output will look like structurally, the unit is too large, because unpredictability means you have left an architectural decision inside it.

Sequence by dependency, not by story

Once units are sized, order them so each consumes only verified outputs of its predecessors. That is dependency sequencing, and it is different from the ordering instinct most teams bring, which is to sequence by user-visible story — build the settings screen first because it demos well.

Sequencing by story means a later unit changes an interface an earlier unit already built against, and the rework lands in code nobody wrote by hand and everybody half-understands. Sequencing by dependency means interfaces and schemas come first, consumers after, and each unit's ground truth is already verified when the next one starts.

The price-alert feature cuts into four units:

#UnitConsumesGate checks
1Target-price persistence and API: schema for one target per user per wine, plus set/clear endpointsExisting wines schema and API conventionsMigration applies and rolls back cleanly; overwrite-on-second-set behavior verified; money stored as minor units + ISO-4217
2Crossing detector: the predicate and the state that makes re-arm work, evaluated at ingestionUnit 1's schema, verifiedAC1–AC4 pass, including exactly-at-target; auction and bid-only rows excluded (AC5); no-data case clean (AC6)
3Notification dispatch: hand-off to NotificationService, audit-log envelope, latency instrumentationUnit 2's detector output, verifiedOne delivery per crossing under replay; audit record count equals delivery count; latency fields populated for AC8's query
4Settings UI: set, edit, and clear a target on the wine detail pageUnit 1's API, verifiedSet/edit/clear round-trip against the real API; no new preference surface introduced (non-goal check)

Notice unit 4 depends only on unit 1, so it could run in parallel with units 2 and 3 — dependency sequencing constrains order, it does not force a single file. Notice also that the riskiest unit is 2, because it carries the semantics that the whole tightening pass was about. That is where you spend your best model, your highest effort, and your most careful gate; module 8 makes that allocation explicit.

The human gate

The human gate is the deliberate stop between units: run the unit's oracles, review the output against its spec slice, and only then treat it as ground truth for the next unit. It is the single highest-value ritual in this whole way of working, and it is the one teams drop first, because early units usually look fine and the stop feels like ceremony.

Its function is arithmetic. If each unit is 90% likely to be correct and you gate between them, four units cost you an expected 0.4 corrections, each caught while it is local and cheap. Without gates, the errors compound: unit 2 builds on unit 1's mistake, unit 3 builds on both, and by unit 4 you are debugging an interaction between three wrong assumptions in code you did not write. Gates do not just catch errors; they stop errors from becoming premises.

A gate is only real if it names concrete checks. "Review it" is not a gate. A gate specifies: which oracles run and must pass; which spec-slice criteria are read against the diff; and which non-goals are checked for violation. That last one matters more than it sounds — scope invention arrives as helpful additions, and the gate is where you notice a preferences table that nobody scoped.

Anti-pattern — the sampled gate

Symptom: units one and two came back clean, so unit three is skimmed and unit four is merged on the strength of a passing test suite. Corrective: gates are not a sampling exercise — their value is concentrated exactly where quality has drifted, which is by definition where you were not looking. Keep the gate mechanical and cheap enough that running it every time is the path of least resistance: a fixed checklist per unit, five to ten minutes, always run.

The pattern, generalized

Step back and the shape is general: specify, decompose, generate a unit, gate, feed forward. It is the same shape whether the units are four pieces of a feature, four chapters of a document, or four stages of a content pipeline — spec, outline, approve, draft, verify. This course was itself produced that way, with a human approval gate after the outline stage that nothing automates past, for exactly the reason described above: the outline is where the architectural decisions live, so an error there is elaborated coherently through everything downstream. That is the extent of the self-reference; the pattern is what matters.

Flow diagram of a feature cut into four right-sized units sequenced by dependency with a labeled human verification gate drawn between each unit and the next, and the general pattern shown beneathFour units, three gates1 · target schema+ set/clear APIGATE1migration reversible ·minor units · overwrite2 · crossing detectorhighest-risk unitGATE2AC1–AC6 pass ·re-arm verified3 · dispatch+ audit + latency4 · settings UIparallel with 2–3depends only on unit 1 → gate 3 after itthe pattern, generalized:specifydecomposegenerate unitgatefeed forward
Figure 5.2 — Decomposition and gates. The price-alert feature cut into four units sequenced by dependency, with a human verification gate between each unit and the next and the concrete checks each gate runs written beneath it. Unit 4 depends only on unit 1, so it runs in parallel — dependency constrains order, not concurrency. Beneath, the same shape stated generally: specify, decompose, generate a unit, gate, feed the verified output forward as ground truth.

Module 06 Context is an input you engineer

A perfect spec generates wrong code if the executor does not know what your system looks like. Specification decides what must be true; context decides whether the executor can express it in your world rather than in the average one. The two failure modes are symmetric and both common: too little context produces invented abstractions and convention drift, and too much buries the two files that mattered.

This module treats context as an engineered input with a design process — an audit before generation, curated grounding rather than description, and an assumption pass over your own spec — instead of as something you paste until it feels sufficient.

The model's priors are the default codebase

Absent your context, an executor writes for the average public repository. That average is not incompetent — it is competent about a codebase that is not yours. It has a UserRepository, it uses floats for money, it throws on missing data, it names things the way tutorials do, and it structures a notification path the way most public examples do.

Model priors are exactly that default, and recognizing their signature saves you from misdiagnosing. When the output invents an abstraction layer your codebase does not have, that is not the executor failing to reason — it is the executor reasoning correctly about the only codebase it can see. Convention drift is a context failure, and its corrective is grounding, not a sterner spec.

The distinction has practical consequences at the gate. A defect caused by missing context recurs identically on regeneration and disappears the moment the right excerpt is attached. A defect caused by a genuine competence limit persists even with perfect grounding, and calls for a different model, more effort, or a smaller unit. Testing which one you have is cheap: attach the excerpt and regenerate.

The load-bearing idea

The executor always writes for some codebase. Your only choice is whether that codebase is yours or the statistical average of everyone else's.

The context ledger

Before generating a unit, audit context in three columns. The exercise takes five minutes and its entire value sits in the third column.

Knows. What the executor reliably has without you: the language and its standard library, common frameworks, the domain in general terms, SQL, HTTP semantics, and the shape of a notification system. You do not need to explain what a p95 is.

Must be told. What is specific to your system and unguessable: your schema, your interfaces, your conventions, your constraints, and the verified outputs of prior units. This column is where grounding excerpts come from.

Will wrongly assume. The danger column — places where the executor has a confident default that differs from your reality. These are worse than unknowns, because an unknown produces a question or an obvious gap while a wrong default produces working code with a wrong premise silently baked in.

Three-column context ledger for the price-alert task listing what the model already knows, what it must be told, and what it will wrongly assume, with the third column styled as the danger columnContext ledger — price-alert crossing detectorKNOWSPython, SQL, HTTPwhat p95 latency meanshow notification systemsare shaped in generalidempotency, retries→ do not spend context explaining theseMUST BE TOLDprice_samples DDLlisting_type enum valuesNotificationService ifaceunit 1's target schemaaudit envelope example→ attach as real excerptsWILL WRONGLY ASSUMEmoney is a float / Decimalall samples are retailone currency, no conversiona repository layer existsmissing data should raise→ each becomes working code with a wrong premisethe third column is the one worth the five minutes: unknowns show up as gaps, wrong defaults ship silently
Figure 6.1 — The context ledger. Three columns audited before generating the crossing detector. The first column tells you what not to waste context on; the second becomes your grounding excerpts; the third is the danger column, where the executor holds a confident default that differs from your reality — money as a float, every sample retail, a single currency, a repository layer that does not exist, missing data as an exception. Unknowns surface as visible gaps; wrong defaults surface as working code built on a false premise.

Every entry in the danger column has exactly two dispositions: close it with a constraint in the spec, or close it with an excerpt in the grounding. "Money is a float" is closed by attaching the price_samples DDL, where the integer minor-unit column is unmissable. "All samples are retail" is closed by a constraint, because the DDL alone shows a listing_type column without telling the executor which values qualify. Both dispositions are cheap; leaving an entry open is not.

Grounding beats describing

There is a reliable asymmetry: showing the real artifact outperforms describing it, at roughly equal length. Six lines of DDL beat a paragraph explaining that money is stored as integer minor units with a currency code — and the DDL also silently answers eight questions you did not think to describe, including nullability, defaults, index presence, and naming style.

Description fails for a specific reason. A prose statement of a convention competes with the executor's prior, and priors are strong; an excerpt is not a competing claim but an observation about the world it is generating into, and it also supplies the exact tokens to imitate. "Use snake_case for column names" is an instruction that can be followed inattentively. Three real table definitions in snake_case make the alternative feel wrong.

-- attached grounding for unit 2 (crossing detector)
CREATE TABLE price_samples (
  id             bigserial PRIMARY KEY,
  wine_id        bigint      NOT NULL REFERENCES wines(id),
  listing_type   listing_kind NOT NULL,   -- 'retail' | 'auction' | 'bid_only'
  amount_minor   integer     NOT NULL,    -- minor units, e.g. 7400 = EUR 74.00
  currency       char(3)     NOT NULL,    -- ISO-4217
  fx_to_eur      numeric(12,6),           -- rate at ingestion; NULL if unavailable
  observed_at    timestamptz NOT NULL,
  ingested_at    timestamptz NOT NULL DEFAULT now()
);

class NotificationService:
    def send(self, user_id: int, template: str, payload: dict) -> DeliveryReceipt:
        """Delivers via the user's configured channel and emits notification.sent."""

Curation is the other half, and it is where teams go wrong in the opposite direction. Attaching the whole repository does not maximize information; it dilutes it. The two artifacts that decide this unit are now surrounded by four hundred files of noise, the signal-to-instruction ratio collapses, and outputs get measurably more generic. The rule: every attached artifact should be one you can justify in a sentence naming the decision it informs. If you cannot, it is decoration with a token cost.

Anti-pattern — withholding context, then being surprised

Symptom: the diff invents an abstraction your codebase does not have (UserRepository, a MoneyFormatter, a config layer), or uses conventions from no file you own. Corrective: do not add a spec sentence forbidding it — attach the two or three real files that show how this codebase does that job, and regenerate. If the invention survives grounding, you have a competence problem rather than a context problem, and the response is a different tier or a smaller unit.

Swimlane diagram showing spec, schema excerpts, convention notes and verified prior-unit outputs converging into the generation input, with a whole-repository dump shown as a rejected dashed laneAssembling the generation inputunit 2 spec slice + criteriaprice_samples DDL (6 lines)NotificationService signatureunit 1 output (verified)whole-repo dump (412 files)rejected: dilutes the two files that decide the unitgeneration inputevery artifact justifiedunit 2 output
Figure 6.2 — Assembling the generation input. Four justified inputs converge: the unit's spec slice with its criteria, the schema excerpt that closes the money and listing-type assumptions, the interface signature that prevents a hallucinated call, and the verified output of the prior unit. The whole-repository dump is rejected, not for cost but for dilution — surrounding the two decisive files with four hundred others makes the output more generic, not more grounded.

The assumption audit

The last move is to read your own spec as a smart stranger — someone competent, well-intentioned, with no access to the conversation in your head — and note every place they would have to assume. This is the same discipline as the tightening pass in module 3, pointed at a different target: the tightening pass hunts readings your words permit, the assumption audit hunts facts your words presuppose.

On the price-alert spec the audit surfaced four presuppositions that no amount of tightening would have caught, because they were not ambiguities in the text — they were things the text assumed you both already knew. That a wine can have samples in several currencies. That fx_to_eur can be null. That the ingestion path is a batch job with a nightly burst, not a stream. That another team's service consumes notification.sent events and will react to a volume change. Each became either a constraint or an attached excerpt.

From your other domain

This is threat modeling with a different adversary. You already run this loop in security work: enumerate the assumptions a component makes about its environment, then ask what happens when each is false. Here the adversary is not an attacker but a confident guess — it has no goal, it never probes twice, and it will not adapt. That makes it a strictly easier adversary to model, and the same discipline works: list the trust boundaries, list what crosses them, and ask what the other side assumes.

One practical addition: context has a shelf life. If unit 1 changed the target schema, unit 3's grounding is stale, and stale grounding is worse than absent grounding — it is a confident false statement about your codebase, attached by you, in a form the executor will trust over its own priors. Refresh the ledger at every gate; it takes a minute and it is the most common way a well-run pipeline silently poisons itself.

Module 07 Reviewing code you didn't write

Review was invented for a world in which a human wrote the code, which means most review habits are tuned to human error patterns — fatigue, haste, incomplete knowledge of a subsystem, the copy-paste that missed one variable. Generated code fails differently. It is rarely careless, almost never inconsistent in style, and frequently wrong in ways that survive a careful linear read precisely because everything around the defect is coherent.

This module retools review for that. The core move is to stop reading the diff as a text and start interrogating it against the spec's criteria, and to check four specific error zones deliberately rather than hoping they catch your eye. It closes with the reject-versus-repair call, which is the decision most teams get wrong in the expensive direction.

Intent-match, not authorship

Intent-match review answers one question: does this do what the spec says, and nothing else? That is a narrower question than the one most reviewers actually answer, which is "is this how I would have written it?" — and the narrowing is the entire point.

When you review your colleague's code, authorship questions carry real information: an unusual structure often signals a misunderstanding, and asking about it is cheap. Generated code breaks that correlation. It will use a structure you would not have chosen for reasons that are neither misunderstanding nor insight — just a different path through the same space of correct solutions. Spending your review budget there is expensive, because review attention is the scarce resource in this loop and you have a finite amount of it per diff.

The rule: style objections are noise unless a constraint made them signal. If the spec constrained money representation and the diff uses floats, that is a criterion violation, not taste. If the diff uses a comprehension where you would have used a loop, and no constraint speaks to it, let it go — and notice that letting it go is what buys you the attention to catch the crossing predicate three files later.

The load-bearing idea

Your review budget is finite and the defects that matter are not where your habits look. Spend it on intent-match against named criteria; spend nothing on how you would have written it.

Where AI systematically errs

Four zones account for most of what gets through. They are worth memorizing as a checklist, because each is invisible to a different reviewing instinct.

Plausible-wrong logic. The structure is right, the naming is right, and one load-bearing detail is wrong: a comparison that should be a transition, an inclusive bound that should be exclusive, a filter applied after aggregation instead of before. This zone defeats reading, because reading is pattern-matching and the pattern is correct.

Silent scope creep. Additions nobody asked for that read as thoroughness — a caching layer, a retry wrapper, a settings toggle, a helpful default. This zone defeats reviewers because the additions are usually competent, and competence reads as reason to keep them.

Hallucinated interfaces. Calls to methods, fields, or options that do not exist but reads as though they should — notifications.sendBatch(), an SDK parameter from a neighbouring library, a config key from a different framework. This zone defeats review because the call site is perfectly idiomatic; only checking the referenced definition finds it, and mocks in the test suite will happily accept the invented name.

Unstated assumptions. Values hardcoded from priors: a currency, a timezone, a locale, a page size, single-tenancy. This zone defeats review because the value looks like a decision someone made, and nothing in the diff distinguishes an intentional constant from a guess.

A stylized diff panel from the price-alert dispatch unit with four systematic AI error zones highlighted and labeled in place: a plausible-wrong comparison, a silently added email digest, a hallucinated batch-send method, and a hardcoded currency assumptionReview-surface map — dispatch unit diffdef handle_sample(sample, target): if sample.amount_minor <= target.minor: receipt = notify(target.user_id, sample) notifications.sendBatch([receipt]) audit.record(receipt) fmt = format_money(sample.amount_minor, "EUR") return receipt+ def send_daily_digest(user_id):+ """Bundles the day's alerts. Enabled+ by default for new users."""+ 14 tests, all passingreads fluently end to end1 · plausible-wronglevel test, not a crossing → AC2 fails2 · hallucinated interfacesendBatch is not in the attached iface3 · unstated assumptionEUR hardcoded; display currency ignored4 · silent scope creepdigest is an explicit non-goal —and default-on is a compliance callwhere you look firstthe criteria list — not line 1 of the diff
Figure 7.1 — The review-surface map. One dispatch-unit diff carrying all four systematic error zones at once: a level comparison where the spec claimed a crossing (AC2 fails), a call to a batch method that appears nowhere in the attached interface, a hardcoded EUR that ignores the display-currency constraint, and a daily-digest function that is an explicit non-goal — shipped default-on, which is a compliance decision nobody made. The diff reads fluently and its fourteen tests pass, which is exactly why a spec-first reviewer starts at the criteria list rather than at line one.

Fluency is not correctness

There is a specific cognitive failure here worth naming, because knowing about it is most of the defense. Polished, consistent, well-named code lowers your vigilance — not through any conscious judgment, but because your reading system uses surface coherence as a cheap proxy for correctness, and that proxy was calibrated on human authorship where the correlation was real. A sloppy human diff signals a rushed author; a beautiful generated diff signals nothing at all about its correctness.

Fluency-correctness confusion is what turns review into rubber-stamping. The tell is the review that finishes fast and comfortable, with two nit comments and an approval, on a diff implementing subtle semantics. If a review of the crossing detector took four minutes and felt good, it did not happen.

Anti-pattern — rubber-stamping the fluent diff

Symptom: your review comments are about naming and formatting; you approved in under ten minutes; and you cannot say which acceptance criterion each part of the diff satisfies. Corrective: open the criteria list first and work down it, locating the code and the test for each criterion in turn. If a criterion has no identifiable implementation, that is the finding — and it is invisible to any linear read.

The countermeasure is structural rather than motivational. Do not read the diff top to bottom; traverse the criteria. For each one: find the code that implements it, find the test that decides it, and read that test's assertion against the criterion's "then." It takes about as long as a careful linear read and finds a different, more dangerous class of defect — including the criterion that nothing in the diff implements at all, which linear reading structurally cannot surface because absence has no line number.

Reject vs repair

Every finding ends in one of two decisions: patch this diff, or discard it and regenerate from an amended spec. Teams systematically over-choose repair, because the diff is in front of them and feels like progress, and because discarding work triggers loss aversion even when the work cost twenty minutes of compute.

Three questions decide it. Are the defects local? Independent, individually fixable problems favor repair; defects that recur across the diff in the same shape indicate a wrong premise, which repair cannot reach. Does the approach honor the intent? If the architecture contradicts a constraint — a scheduled sweep where the spec required evaluation at ingestion — no patch fixes it, because the patch would be a rewrite. Are the errors correlated? If three findings all trace to one early decision, you have one defect wearing three costumes, and it lives upstream.

The sunk-cost trap has a recognizable shape: you are on patch round four, each round fixes what the last round revealed, and the diff has drifted into something no spec describes. At that point regeneration is not a setback — it is the cheaper path, and it comes with a better spec, because the four rounds taught you exactly which sentences were missing.

Cross-reference

Guide Nº 08 covers review as a discipline in general — what review can establish, how to structure it, and how to give feedback that lands. This module is the AI-specific overlay: same craft, different error distribution, and one decision (reject versus repair) that has no equivalent when a colleague wrote the code, because you cannot regenerate a colleague.

Decision tree for reject versus repair: are defects local, does the approach honor intent, are errors correlated, leading to a repair path in success color and a reject-and-regenerate path in danger dashed, with the sunk-cost trap labeledfindings from the gatedefects local & independent?yesapproach honors intent?yesREPAIRpatch, re-run the oraclesno — sameshape repeatsno — architecturecontradicts a constraintREJECT — amend the spec, regeneratethe findings tell you which sentences were missingthe sunk-cost trappatch round 4 on a doomed diffeach patch revealsthe next defectcorrelated findings are one defect wearing several costumes — and it lives upstream
Figure 7.2 — Reject or repair. Two questions decide the call: are the defects local and independent, and does the approach honor the intent? Both yes means repair and re-run the oracles. Either no means the defect is upstream of the code — amend the spec and regenerate, using the findings to identify the missing sentences. The dashed path on the right is the sunk-cost trap: repeated patch rounds on a diff whose premise was wrong, each round revealing the next symptom of the same root cause.

The spec as review instrument

Put the pieces together and review becomes a procedure with a defined output rather than an act of judgment with a mood. It has four passes, and it should take fifteen to thirty minutes on a right-sized unit.

Pass 1 — criteria traversal. Work down the acceptance criteria. For each: locate the implementing code, locate the deciding test, read the assertion against the criterion's "then." Record any criterion with no identifiable implementation.

Pass 2 — non-goal check. Read the non-goals and search the diff for each. This is the pass that catches the digest function and the unrequested preferences table, and it takes two minutes.

Pass 3 — the four zones. Check each zone by name, and record "none found" explicitly. Verify every unfamiliar external call against its actual definition, not against its plausibility. Scan for hardcoded values that priors would supply — currencies, timezones, page sizes, locales.

Pass 4 — the call. Repair or reject, using the two questions, and route each finding forward: a defect that would recur on regeneration goes into the spec, not into a patch. That routing rule is module 8's subject, and it is where the review's findings turn into leverage rather than into a to-do list.

The dispatch diff, reviewed

Running the four passes on Figure 7.1's diff: pass 1 finds AC2 unimplemented — the level comparison cannot distinguish a crossing from a hold, and its test asserts a single call across a scenario that never re-samples. Pass 2 finds the digest function violating an explicit non-goal, shipped default-on. Pass 3 finds sendBatch absent from the attached interface and "EUR" hardcoded against the display-currency constraint. Pass 4: the AC2 failure is not local — the crossing-versus-level premise runs through the detector, the state model, and six tests — so this is a reject, and the amended spec gets one sentence making the transition semantics unmissable at the top of the unit's slice. Elapsed: twenty minutes. Fourteen tests passed throughout.

Module 08 The loop is a process

Everything so far has been a component: a spec, a decomposition, a gate, a review. This module assembles them into the loop you actually run, and the assembly matters because the loop's most important property is the one people skip — it has two distinct feedback paths, and choosing between them correctly is what separates a controlled process from a slot machine.

The other half of control is resource allocation. A loop that spends maximum effort on every unit is not careful, it is undifferentiated, and undifferentiated allocation reliably under-serves the units that carry the design while over-serving the ones that carry nothing.

The control loop

The loop has four elements and one exit. Spec: the executable document from module 3, sliced per unit. Generate: one unit, with the grounding from module 6. Verify: the gate from module 5 and the review from module 7, running the criteria's oracles. Route: send each finding down one of two feedback paths. Exit: every acceptance criterion passes its oracle — stated in advance, in the spec, not decided in the moment.

The exit condition is what makes this a process rather than a mood. Without it, iteration ends when someone is satisfied, which is a function of fatigue and calendar pressure and has no relationship to whether the thing works. "Keep improving it until it looks right" is indistinguishable from a slot machine: you pull, you evaluate the output against a private standard, you pull again, and there is no state in which you are done.

Control loop diagram: spec feeds generate, which feeds verify; verify branches into a fix-in-output path returning to verify and a fix-in-spec path returning to the spec, with the exit condition stated as all acceptance criteria passing their oraclesSPECunit slice + criteriaGENERATEone unit, groundedVERIFYoracles + 4-pass reviewEXITall AC pass oraclesfix in output — local defect, would not recurfix in spec — would recur on regeneration (the high-leverage edge)routing question: would it survive a regeneration?no exit condition = a slot machine with extra steps
Figure 8.1 — The generate-verify control loop. Spec feeds generation, generation feeds verification, and verification routes every finding down one of two feedback paths. The dashed inner path patches the output and re-verifies; the accent-coloured outer path amends the spec and regenerates, and is the high-leverage edge because it removes the defect's cause rather than its instance. The routing question is single: would this defect survive a regeneration from the current spec? The loop exits on a condition written in advance — all acceptance criteria pass their oracles — not on satisfaction.

Fix in spec vs fix in output

The routing rule is one question: would this defect recur if I regenerated from the current spec? If yes, the fix belongs upstream. If no, patch the output and move on.

Run it on the case that has been threading through this course. The alert fires on every nightly sample. You could fix the conditional in the generated code — five minutes, tests updated, done. Now apply the question: regenerate this unit tomorrow from the same spec slice, and does the same defect return? It does, because the spec said "drops below" and never said "crosses." So the fix is the sentence, and the patched conditional is at best a stopgap you will re-derive every time this unit is touched.

Contrast a genuine output-level defect from the same review: format_money is called with a hardcoded "EUR" when the spec's display-currency constraint is stated plainly in the unit slice. Regenerate, and this probably does not return — the spec already says the right thing, and the executor simply failed to apply it in one call site. Patch it, note it, move on.

DefectRecurs on regeneration?RouteThe fix
Alert fires on every sample below targetYes — spec never distinguished crossing from levelSpecAdd the crossing-and-re-arm sentence, regenerate
Auction lots trigger alertsYes — spec never scoped the seriesSpecAdd the retail-only constraint, regenerate
Hardcoded "EUR" in one call siteProbably not — the constraint is already statedOutputPatch the call, re-run the oracle
Hallucinated sendBatchSometimes — not a spec silenceOutput (+ grounding)Fix the call; keep the interface excerpt attached
Nothing implements AC3Yes — criteria under-covered the transition spaceSpecStrengthen the criteria set, regenerate
Anti-pattern — iterating on the output when the fix is upstream

Symptom: the same class of defect reappears after a regeneration, or you find yourself re-applying a patch you have applied before, or your fix cannot be expressed as a change to any sentence in the spec. Corrective: ask the recurrence question before every patch. It takes five seconds and it is the single highest-leverage habit in this course.

Iterate vs restart

Even correctly routed, iteration has a limit. Each hand-patch moves the artifact further from anything the spec describes, and past some point you hold code that no document explains, that nobody can regenerate, and whose accumulated fixes interact in ways no one has modelled. That is the restart threshold.

Three signals tell you that you have crossed it. Each patch round reveals a new defect rather than closing the last one, which means you are excavating a premise rather than fixing bugs. The diff no longer matches its spec slice structurally, so the spec has stopped being a description of the code. And you cannot say what the next regeneration would produce — the artifact has become path-dependent on a sequence of manual edits.

Crossing the threshold is not a failure, and it is much cheaper than it feels. The generation itself cost minutes; what you actually invested was the diagnosis, and diagnosis is exactly what the amended spec preserves. A restart after four patch rounds carries forward everything those rounds taught you, in the one form that regenerates correctly next time.

Keep a routing log

Two columns per unit — defect, route, and one line of reasoning — costs nothing and pays twice. It shows you at a glance when three consecutive findings all routed to the spec, which is the restart signal; and it becomes the amendment list itself, so "rewrite the spec" is a transcription job rather than a memory exercise.

Matching model and effort to the task

The last control is allocation. Model tier and effort are dials, and setting them by habit — always maximum, or always cheapest — reliably misallocates in one direction or the other.

Two variables set the dial. Difficulty: how much of the work is design rather than transcription — novel semantics, cross-cutting decisions, and anything where you cannot predict the output's structure. Stakes: what a wrong answer costs — money moved, data lost, a compliance control missed, a customer emailed.

Quadrant chart of task difficulty against stakes with a recommended model tier and effort in each quadrant and the two waste zones marked: over-provisioned trivia and under-provisioned design workdifficulty — how much of this is design? →stakes →high stakes · low difficultymid tier, medium efforte.g. audit-log wiring, a migrationspend the budget on the gatehigh stakes · high difficultytop tier, high efforte.g. the crossing detector,schema design, money handlinglow stakes · low difficultysmall/fast tier, low efforte.g. bulk rename, docstrings,mechanical test scaffoldinglow stakes · high difficultytop tier, medium efforte.g. a prototype spike, aninternal one-off analysiswaste zone if you max the dials herewaste zone if you skimp here
Figure 8.2 — Model-effort matching. Difficulty (how much of the task is design rather than transcription) against stakes (what a wrong answer costs). Top-right gets the strongest model at high effort — the crossing detector lives here. Bottom-left gets the cheapest fast tier, and maxing the dials there is pure waste. Top-left is routine work with expensive failure modes: a mid tier is fine, and the budget belongs in the gate rather than the generation. Both dashed zones are misallocation, and the expensive one is the top-right skimp, where a cheap pass produces a fluent wrong architecture you then pay to diagnose.

The two failure directions are not symmetric in cost. Over-provisioning a rename wastes money and a little time. Under-provisioning the detector produces a plausible wrong architecture that consumes review attention, gate time, and probably a full restart — the diagnosis alone costs more than the entire generation budget you saved. When uncertain, spend on the units where you cannot predict the output's structure, because that unpredictability is the definition of design content.

Anti-pattern — undifferentiated dials

Symptom: every task in your history uses the same model at the same effort, in either direction. Corrective: before each unit, place it in the quadrant out loud. The placement takes ten seconds; if you cannot place it, that itself is a signal the unit is too large and carries hidden design content.

Module 09 Beyond code, and the method

The leverage shift was never about programming. It is about delegation to a confident executor that resolves every ambiguity instantly and never asks — and that description covers commissioning a memo, a design, a market analysis, or a compliance brief just as well as it covers code. The anatomy transfers whole.

This module makes the generalization concrete on one worked non-code example, collects the course's anti-patterns into a single field guide you can scan before shipping, and closes with the working method: what you actually do on Monday morning.

The craft generalizes

Strip the anatomy of its software vocabulary and nothing about it was software-specific. Intent: what must be true when this is finished. Constraints: the boundaries that rule out otherwise-acceptable results, each with its reason. Non-goals: what this is not, so the executor stops inventing. Acceptance criteria: falsifiable claims about the artifact, each paired with the check that decides it.

The failure modes transfer too, in recognizable costume. "Write me a thorough analysis of our retention exposure" is the one-line prompt as a spec, and it produces the prose equivalent of branch A: fluent, comprehensive-seeming, confidently wrong at the joints, and impossible to review because nobody wrote down what it was supposed to establish. Scope invention in prose looks like an unrequested executive summary that overstates a finding, or three extra jurisdictions padded in for completeness. Fluency-correctness confusion is at its most dangerous here, because well-formed prose is even better at anesthetizing scrutiny than well-formed code — code at least has to run.

Three-column grid instantiating the same four-part spec skeleton — intent, constraints, non-goals, acceptance criteria — for a code feature, a written brief, and a data analysisOne skeleton, three artifactsINTENTCONSTRAINTSNON-GOALSCRITERIA+ oraclesCode featureone alert per crossingretail only · ≤60 minaudit envelopeno digests, no chartsAC1–AC8tests · SQL · probeCompliance briefenable a retentiondecision in 4 marketsprimary sources only2,000 words maxno legal advice, nopolicy rewritecites → section numbersspot-check · countData analysisdecide whether tokeep the free tier12 months, cohortednamed data sourceno forecasting, nopricing recommendationquery reproduces totalsre-run · reconcile
Figure 9.1 — One skeleton, three artifacts. The same four rows instantiate a code feature, a written brief, and a data analysis. Only the oracles change kind — tests and queries for code, citation spot-checks and word counts for prose, re-running the query and reconciling to a known total for analysis. The invariant is that every column has all four rows filled; an artifact commissioned without non-goals or without criteria fails the same way regardless of what it is made of.

Worked example: commissioning a compliance brief

Take a real task from the reader's world. You need a data-retention analysis covering four markets, to decide whether the wine-cellar product's current 7-year retention of customer communications is defensible or needs to change. The wrong commission is "research our data retention obligations in the EU, UK, US and Canada and write it up thoroughly." Here is the executable version.

Intent. Enable a specific decision: whether to keep, shorten, or vary by market the current 7-year retention period for customer communication records. The brief must state, for each market, the applicable retention floor and ceiling and whether 7 years sits inside it — or state explicitly that the answer is unresolved and why.

Constraints, with reasons. Primary sources only — regulation text, statutory instruments, and regulator guidance — because a secondary summary cannot be checked to a section number and this brief will be cited internally. Every claim carries a citation to the instrument and section. Scope is the four named markets and customer communications only, not transaction or tax records, because those sit under a different control owner. Maximum 2,000 words, because the audience is a decision meeting and length here trades against being read. Where sources conflict, both readings are stated with the conflict named, rather than one being silently chosen.

Non-goals. No legal advice or opinion on defensibility — this is an input to counsel, not a substitute. No proposed policy rewrite. No implementation plan or engineering estimate. No jurisdictions beyond the four named, even where a fifth looks relevant — flag it in one line instead. No executive summary that states a conclusion the body does not support with citations.

Acceptance criteria and their oracles.

CriterionOracle
Every market has a stated retention floor as a number of years, or an explicit "unresolved" with the reasonCount: four markets, four resolved-or-flagged entries
Every factual claim carries a citation to instrument and sectionSpot-check five citations at random against the primary source; any that does not resolve fails the brief
No claim rests on a secondary sourceScan the citation list for non-primary domains
The 7-year question is answered explicitly for each marketRead for the four sentences; their absence is a fail
Conflicts between sources are surfaced rather than resolved silentlyAt least one conflict is named, or an explicit statement that none was found
Under 2,000 wordsWord count

The citation spot-check is the load-bearing oracle, and it is the one that makes this brief different from an earnest one. Five random citations, each resolved to the actual instrument, is a fifteen-minute check that catches the failure mode this artifact is most prone to — confident, well-formed references to sections that say something adjacent to the claim, or do not exist. It is the prose equivalent of verifying a call against its definition.

What the criteria caught

First draft, run against the criteria: three of the four markets had a floor stated as a number; the fourth had a paragraph of discussion and no figure, which the count oracle caught immediately. Two of five spot-checked citations pointed to real instruments at wrong section numbers. And an executive summary had appeared — a non-goal — asserting that 7 years was "comfortably defensible across all four markets," a claim the body did not support for the market that had no figure. None of this was visible from reading; all of it was visible from the criteria.

The anti-pattern field guide

Eleven anti-patterns, collected from across the course. Each has a symptom you can observe in your own work and a corrective you can apply the same day. Read the symptom column before you ship; it is faster than remembering the principles.

Anti-patternSymptom in the wildCorrective
Blaming the executorYour postmortem says "the model got it wrong" and the originating ticket was one lineReconstruct the input; if the output is a defensible reading of it, the defect is your text (m1)
The one-line prompt as a specThe request fits in a chat box and contains a verb whose timing, scope, or repeat behavior is unstatedRun the four questions on each verb: over what data, at what moment, how many times, at the boundary (m3)
Omitting non-goalsThe diff contains competent work nobody asked for — a cache, a settings toggle, an extra sectionWrite three non-goals naming what a capable executor would plausibly volunteer (m3)
Unverifiable or absent criteriaCriteria contain reliable, performant, intuitive, graceful; or all share one oracle; or nobody can say which check decides criterion threeFor each: write "this fails if …" and name the artifact that would notice (m4)
The whole system in one passA fluent 2,000-line diff arrives with no seam you can verify independentlyCut to one schema, one behavior, or one interface per unit, with a gate between (m5)
The sampled gateEarly units looked fine, so later ones are merged on a passing suiteKeep the gate mechanical, five to ten minutes, and always run it (m5)
Withholding contextThe output invents an abstraction your codebase does not have, or uses conventions from no file you ownAttach two or three real files showing how this codebase does that job, and regenerate (m6)
Rubber-stamping the fluent diffApproved in under ten minutes; comments are about naming; you cannot say which criterion each part satisfiesTraverse the criteria, not the diff — and record any criterion nothing implements (m7)
Iterating on the output when the fix is upstreamThe same class of defect reappears after regeneration, or you re-apply a patch you have applied beforeAsk the recurrence question before every patch: would this survive a regeneration? (m8)
Over-specifying the howThe spec contains conditionals and variable names, and you cannot say what breaks if the executor did it differentlyPromote each line to a constraint with a reason, or delete it and state the outcome it protected (m2)
Undifferentiated dialsEvery task in your history runs at the same tier and effort — always maximum, or always cheapestPlace each unit in the difficulty × stakes quadrant out loud before generating (m8)

Two of these deserve a note about frequency. Omitting non-goals and rubber-stamping the fluent diff are the two that survive longest in otherwise disciplined teams, because neither produces an immediate failure — the first ships extra work that looks like value, and the second ships defects that surface weeks later attached to no decision anyone remembers making.

The working method

Here is the method, stated as what you do rather than what you believe. It is opinionated; adopt it as written for a month and then change what does not fit.

Before any delegated work, write the spec. Four parts, always, even for small tasks — for a small task it is six lines and takes four minutes. Intent as a claim about the world afterward. Constraints, each with its reason. Three or more non-goals naming what a capable executor would plausibly volunteer. Acceptance criteria as given/when/then claims, each with a named oracle, including at least two boundary conditions from the sweep.

Then run the tightening pass. List the readings your text permits; add the minimum sentence that kills each unwanted one. If a sentence kills nothing, delete it. Twenty minutes on a real feature, and it is the highest-return twenty minutes in the process.

Cut the work. One schema, one behavior, or one interface per unit. Sequence by dependency so each unit consumes only verified predecessor outputs. Name the gate check for each unit before generating anything — which oracles, which criteria, which non-goals.

Engineer the context once per unit. Three-column ledger, five minutes; close every danger-column entry with either a constraint or an excerpt. Attach only artifacts you can justify in a sentence. Refresh at every gate.

Place the unit in the quadrant, then generate. Ten seconds. If you cannot place it, the unit is too large.

Gate every unit, no exceptions. Four passes: criteria traversal, non-goal check, the four zones by name with "none found" recorded, then the call. Fifteen to thirty minutes on a right-sized unit.

Route every finding. Recurrence question before every patch. Keep the two-column log. Three consecutive spec-routed findings is your restart signal.

Exit on criteria. All acceptance criteria pass their named oracles, non-goal check clean. Not when it looks done.

The load-bearing idea

None of this is a tool you install. The whole method is a sequence of writing and reading disciplines, and the only entry cost is the first spec you write properly for work you actually have to deliver.

Two of the disciplines are treated at greater length in other guides, and one of them you have been practising for years under a different name.

Where this connects

Module 4's criteria are the same instrument Guides Nº 26 and Nº 08 build out for verification generally. Module 7's review discipline extends Guide Nº 08's craft to an author who cannot be asked what they meant. And the whole method is what the reader's litigation training already does — draft against a literal reader, close every silence, and define done before starting — pointed at a new counterparty.

The first Monday move is not to adopt all of it. Take the next real piece of work you were going to delegate — a feature, a brief, an analysis — and write its four parts before you write the request. Then run the tightening pass and count how many readings your first draft permitted. That number is the argument; nothing in this course will convince you as efficiently as finding three defensible wrong readings in a sentence you thought was clear.

Concept index

Leverage shift
The move of scarcity from writing code to specifying it, once generation is cheap.
Underspecification
A silence in the spec that delegates a real decision without meaning to.
Confident guess
The model's resolution of an ambiguity: fluent, plausible, and unflagged.
Intent
What must be true after the work, stated without prescribing how.
Implementation dictation
Prescribing the how; legitimate only when written as a constraint with a reason.
Specification altitude
Where a spec sits between wish and pseudocode; executable specs occupy the middle band.
Executable spec
A spec precise enough that a capable executor produces the intended result without asking.
Wish
A requirement with no constraints, non-goals, or checkable done — decorative, not executable.
Constraint
A stated boundary that rules out otherwise-valid solutions, with its reason attached.
Non-goal
An explicit statement of what is out of scope, closing the silence a model would fill.
Ambiguity fork
The set of divergent implementations all legitimately derivable from one vague requirement.
Acceptance criterion
A falsifiable claim about observable behavior that defines part of "done."
Oracle
The concrete check — test, query, probe — that decides a criterion pass or fail.
Verification gap
Criteria that all pass while the intent still fails, because coverage was thin.
Unit of work
A chunk sized so a model completes it correctly in one pass with high probability.
One-pass reliability
The probability a unit comes back right without iteration; the sizing signal.
Human gate
The deliberate verification stop between units where compounding errors die.
Dependency sequencing
Ordering units so each consumes only verified outputs of its predecessors.
Model priors
What the model assumes by default — the average public codebase — absent your context.
Grounding
Real excerpts of schema, interfaces, and conventions attached in place of descriptions.
Context ledger
The pre-generation audit: knows / must be told / will wrongly assume.
Assumption audit
Reading your own spec as a smart stranger to find what they would assume.
Intent-match review
Reviewing generated work against the spec's claims, not against your taste.
Plausible-wrong
Code whose structure and style are right while a load-bearing detail is wrong.
Silent scope creep
Unrequested additions that read as helpful and ship as liabilities.
Hallucinated interface
A call to an API, method, or field that does not exist but reads as if it should.
Fluency-correctness confusion
Letting polish stand in for verification during review.
Reject-vs-repair
The call between patching a diff locally and regenerating from an amended spec.
Generate-verify loop
Spec → generate → verify → route the fix → exit on criteria; the controllable process.
Fix-in-spec
Routing a defect upstream because it would recur on regeneration.
Restart threshold
The point where accumulated patches make clean regeneration cheaper than iteration.
Model-effort matching
Proportioning model tier and effort to a task's difficulty and stakes.
Commissioning brief
An executable spec for a non-code artifact: intent, constraints, non-goals, criteria.
Spec-first delivery
The working method: author the spec, cut and gate the work, run the loop to criteria.

This is the complete text of the course. With JavaScript enabled, this same page runs the interactive edition — a self-diagnostic that reorders the syllabus around your gaps, knowledge checks, applied worksheets with model answers, and a spaced-repetition review queue — with progress saved locally in your browser.