Chapter 13 · Week 13

Documentation Is a Deliverable

What does it mean to entrust your work to another?

Chapter 13 — Documentation Is a Deliverable

“Instead of imagining that our main task is to instruct a computer what to do, let us concentrate rather on explaining to human beings what we want a computer to do.” — Donald E. Knuth, “Literate Programming,” The Computer Journal (1984)

“…what you have heard from me in the presence of many witnesses entrust to faithful men, who will be able to teach others also.” — 2 Timothy 2:2 (ESV)


Why This Matters

Last week you froze a release candidate. The code compiles, the tests are green, the change log tells the story of what moved and why. If this were a class assignment, you would be done. It is not a class assignment. It is a project you are about to hand to somebody else — and right now, nobody else can run it.

This week you wear the hat almost every student underestimates: technical writer. Not “the person who writes comments.” The person who decides what a stranger needs to know, in what order, and proves it by watching a stranger try. In industry this hat sits on a real chair with a real salary, and on your capstone it sits on your head, next to all the others. Where you are in the life cycle has a name too: this is the transition phase — the stretch between construction and deployment where a working system becomes a transferable one. Verification asked “does it do what we said?” Transition asks “can anyone but you keep it doing that?”

Here is the number that should get your attention. The final submission is 50% of your grade — and the milestones that build it are another 25% — and a large share of both is documentation: the README, the architecture overview, the runbook, the onboarding guide, the decision records, the change log, the ai-usage log. Not “documentation about the code.” Documentation as the code’s other half. A grader who cannot clone your repository and run it does not grade your architecture generously and dock you a few points for a thin README. They grade what they can observe, and what they can observe is nothing. The full line-by-line accounting is in Appendix C; read it this week if you have not lately.

The AI thread runs hard through this chapter from both sides. As a tool, an assistant will draft a README from your repository in ninety seconds, and it will be fluent, well-organized, and confidently wrong in the exact places that matter — the install command that was true three commits ago, the environment variable it inferred from a variable name, the port it guessed. Generated setup steps fail silently, because prose does not throw exceptions. As a workload, the AI features inside your own system now have to be documented for a successor: which model, which version, what it costs, what happens when the vendor changes it under you, and which behavior a successor must never assume is stable. Both sides land on the same spine rule this course has repeated since Week 1 — the assistant accelerates the draft, and you run every command it wrote before it reaches a reader.

Which brings us to the week’s question, and it is not a small one: what does it mean to entrust your work to another? Not to publish it. Not to submit it. To entrust it — to put a thing you made into hands that are not yours, knowing it will be changed, and to prepare those hands on purpose. Paul’s instruction to Timothy is a transfer protocol: what you received, hand on, to people who can hand it on again. We will take that seriously in 13.13, because it is the actual subject of this week, and everything else — the README, the runbook, the clean-machine test — is machinery in service of it.

One promise before we start. By Friday, you will not believe your documentation works. You will have watched a person you did not coach clone your repository on a machine you have never touched, and reach a working system with a stopwatch running. That is a different kind of confidence, and it is the only kind worth having.


13.1 — Documentation Is Graded Because Documentation Is the Deliverable

Students hear “documentation” and think tax — something you pay after the real work, in a hurry, to satisfy a rubric. That framing is why most capstone documentation is bad, and it is worth killing outright. Try this instead. Your repository has two halves. One half is executable by a machine. The other half is executable by a person — a set of instructions that, run correctly by a human, produces a working system and a mind that understands it. Both halves can have bugs. Both can be tested. Only one of them tells you when it is broken, and it is not the prose. A README is a program whose runtime is a human being.

So the discipline is one you already have. You do not ship code you have not run. You do not ship documentation you have not run either — and “running” documentation means a person following it on a machine that is not yours. Here is the set this course requires, and what each piece is actually for. Every one answers exactly one question for exactly one reader. When a document tries to answer two, it answers neither.

DocumentRepo pathIts one readerIts one questionBuilt in
READMEREADME.mda stranger, right now”Can I run this in fifteen minutes?“this week
Architecture overviewdocs/architecture.mdan engineer sizing up the system”How is this put together, and why?“this week
Runbookdocs/runbook.mdwhoever is on the hook when it breaks”It’s broken. What do I do?“this week
Onboarding guidedocs/onboarding.mdthe next developer, day one”How do I make my first change safely?“this week
Decision record indexdocs/adr/README.mdsomeone about to reverse a decision”Why is it like this?“this week (records from Week 5)
AI usage notesdocs/ai-usage.mda successor and a grader”What is the AI doing, and is it stable?“extended this week
Requirements specdocs/requirements.mdyour future self, in a dispute”What did we agree to build?”Weeks 3–4
Test plan, defect logdocs/test-plan.md, docs/defect-log.mda reviewer judging quality”What works, and how do you know?”Week 11
Change logCHANGELOG.mdanyone upgrading”What changed between versions?”Week 12
Handoff guidedocs/handoff.mdthe next owner”How do I take custody of this?”Week 14

Two of those are easy to confuse, so separate them now. The onboarding guide is for a developer joining the project: how the code is laid out, how to make a change, how a change gets merged. The handoff guide, which you build next week, is for the next owner: accounts, credentials, hosting, domain, cost, license, and the responsibilities that come with the keys. One is about the codebase. The other is about the custody. You need both, and this week you write the first.

Coach’s Note — If you can only fix one document this week, fix README.md. It is read by everyone — grader, employer, classmate, stranger — and it is the only document whose failure is instantaneous and total. A perfect architecture overview under a broken README is a beautiful room behind a locked door.


13.2 — The README That Gets a Stranger Running in Fifteen Minutes

Fifteen minutes. That is the bar, and it is not arbitrary — it is roughly how long a motivated stranger will fight your project before deciding it does not work. Past fifteen minutes they do not conclude “this needs more setup.” They conclude “this is broken,” close the tab, and, if they are grading you, start writing.

Eleven sections get them there. This is the standard the README Grader widget scores against, the standard the Milestone-13 rubric uses, and the standard a working engineer applies without knowing they are applying it.

  1. What it is and who it is for — two to four sentences, plus the non-goals.
  2. Demo — a screenshot, a GIF, or a pasted terminal session.
  3. Prerequisites — with versions and a command to check each one.
  4. Install — the commands, and what success looks like.
  5. Configure — every variable, what it means, what happens without it.
  6. Run — the command, the expected output, and the action that proves it works.
  7. Test — the command, the expected count, the known failures.
  8. Project layout — the map.
  9. Troubleshooting — real symptoms, real fixes.
  10. License — what a reader may do with this.
  11. Contact and further reading — you, and the doors to the other documents.

Copy the skeleton from code/readme-template.md and fill it against your actual repository. Now the part this book promised you: the bad version next to the good one. Here is the top of a real-shaped student README for our running example, PantryPilot — the household food-tracking app introduced in Chapter 2. PantryPilot’s stack is an example, not a prescription; your commands will differ, but the shape of what follows will not.

Bad:

# PantryPilot

A pantry app for tracking food. Built with modern technologies.
Still a work in progress — some features coming soon!

## Setup
Clone the repo and install dependencies, then start the dev server.
Should be pretty straightforward.

Count the failures. “Modern technologies” tells a reader nothing and signals that the author could not be bothered. “Some features coming soon” is a promise, not a status, and it leaves the reader unable to tell whether the thing they need works. “Install dependencies” is not a command. “Should be pretty straightforward” is the author’s opinion about the reader’s next twenty minutes, and it will be wrong.

Good:

# PantryPilot

Track what food your household has, what expires soon, and what you can cook tonight.
Built for a shared house of four; three people use it weekly.

**Status:** Working. Inventory, expiry alerts, and barcode lookup are complete.
Recipe suggestions are behind a feature flag and require an API key (see Configure).

**What it does not do:** no mobile app, no multi-household support, no nutrition data.

## Prerequisites

Node.js 20.11.1 (`node --version`) · PostgreSQL 16.2 (`psql --version`) · a Barcode Lookup
API key (see Configure). Node 16 and below will not work — the code calls the global `fetch`.

The good version is longer. That is not why it is better. It is better because a stranger can answer three questions in twenty seconds: is this for me, does it work, and what will it cost me to try? And notice the last clause — “Node 16 and below will not work, because the code calls the global fetch.” That single sentence is worth a page of prose, because it converts a mysterious failure into a known one.

Coach’s Note — Write the What it does not do section first, before anything else. It is the hardest paragraph in the document, it takes ten minutes, and it is the paragraph that makes an experienced reader trust you. Everyone overstates what their project does. Almost nobody states what it doesn’t. Be the exception.


13.3 — Prerequisites, Versions, and the Assumption You Did Not Know You Made

Every one of us is running a machine that has been quietly configured over years — your global toolchain, your language runtime, your database, the environment variable you exported in a shell profile in September and forgot. Your project depends on that machine. Your README describes a different, imaginary one: the machine you think you have. The gap between them is where every failed setup lives.

You cannot reason your way out of it, because you cannot remember what you installed. The only reliable method is mechanical: stand outside your machine and observe your project from there. That is the clean-machine test in 13.10, and it is why this chapter builds toward it. Before that, three passes close most of the gap.

Pass one — pin what you actually have. Not “Node” but the version string your machine prints, today, from the directory where the project works.

node --version && npm --version && psql --version && git --version

Paste the real output into the prerequisites table. Write “tested on,” not “requires” — you know what worked; you do not know the full range that works.

Pass two — hunt the hard-coded and the personal. These are the tells that a document was written from inside one machine:

grep -rnE "/litman-books/Users/|/home/[a-z]|C:\\\\Users|localhost:[0-9]{4}|127\.0\.0\.1" README.md docs/

Every hit is either a path only you have or a port you never told the reader about.

Pass three — find the invisible account. Any external service is a prerequisite even though nothing gets installed for it: an API key, a database you created by hand in a GUI six weeks ago, an OAuth application registered under your personal account, a table you seeded manually and never scripted. These cost the most, because a stranger cannot even guess at them. Compare:

Weak prerequisiteStrong prerequisite
”Node and Postgres""Node.js 20.11.1 (node --version); PostgreSQL 16.2 (psql --version)"
"You’ll need an API key""A Barcode Lookup API key. Free tier as of this writing; verify current limits with the vendor. Without it, barcode scan returns 503 and manual entry still works."
"Set up the database""createdb pantrypilot then npm run migrate — expect 12 migrations applied"
"Requires Python""Python 3.10 or newer (python3 --version); 3.9 fails on the match statement in parser.py

Notice what the strong column does that the weak one does not: it names the failure. A prerequisite without a failure mode is a suggestion; one that says what breaks and how you will recognize it is documentation. And do not invent a range you have not tested — “tested on 20.11.1; other versions untested” is honest and useful, while “requires Node 18+” when you have only ever run 20 is a claim you cannot support, and the first reader on Node 18 will discover that before you do.


13.4 — The Architecture Overview: One Page, One Diagram, Five Paragraphs

You already wrote a technical specification in Week 6. It is long, it is detailed, and almost nobody will read it. The architecture overview is different: the one page that lets a competent engineer hold your system in their head in five minutes and then decide whether they need the long version. One page, one diagram, five paragraphs — that constraint is the whole design.

The diagram shows components and the arrows between them — not classes, not every file. Four to eight boxes; every box a thing that runs or stores; every arrow labeled with what flows across it. Mermaid in Markdown is a good default: GitHub renders it, and it lives in version control next to the code, so it can be updated in the same commit as the change it describes. Check that yours renders in the GitHub preview, not just in your local editor.

flowchart LR
  U[Browser client] -->|HTTPS / JSON| A[API server]
  A -->|SQL| D[(PostgreSQL)]
  A -->|HTTPS, cached 24h| B[Barcode Lookup API]
  A -->|prompt + inventory| L[LLM recipe service]
  S[Nightly expiry job] --> D
  S -->|email| U

Then the five paragraphs, each answering exactly one question:

  1. What the system does, in technical terms, for someone who has not read the README.
  2. The components — what each box owns, and what it deliberately does not.
  3. The data — the three or four entities that matter, where they live, what is authoritative.
  4. One end-to-end flow — trace a single real request from the user’s click to the response, through every box.
  5. The consequential decisions and their consequences — the two or three choices that shaped everything else, each linked to its decision record, plus the one thing that would surprise a new reader.

That last clause is the one students skip and reviewers reward. Every real system has a surprise: a table that is intentionally denormalized, a cache with a strange expiry, a synchronous call that should be a queue but isn’t because it is a one-semester project. Name it. A surprise you disclosed is a tradeoff; a surprise a reader discovers alone is a bug.

Bad paragraph 2 — the version that gets written when you are tired:

The system uses a modern three-tier architecture with a frontend, a backend, and a database. The backend is organized into controllers, services, and models. The frontend calls the backend via REST endpoints. This design promotes separation of concerns and maintainability.

Nothing there is false. Nothing there is about your project. You could paste that paragraph into ten thousand repositories without editing it, which is exactly the test it fails.

Good paragraph 2:

The API server owns all business rules, including the expiry calculation, which is deliberately not in the database as a computed column — see ADR 0006. The client is a thin rendering layer with no logic beyond form validation; if you find a rule in the client, it is a bug. PostgreSQL is the single source of truth for inventory. The barcode service is treated as untrusted and optional: its responses are cached for 24 hours and every code path that uses it has a manual-entry fallback, because it was down for six hours during Week 10 and the app stayed usable.

Same length. One of them is about a system.


13.5 — The Runbook: How to Operate It, and What to Do When It Breaks

The README gets a stranger to a running system. The runbook keeps it running — and tells whoever inherits it what to do at an hour when you are not answering messages. This is the document with the clearest audience in the whole set: a competent person, under time pressure, who does not know your system. Write for that person. Short sentences. Commands, not descriptions of commands. Expected output for everything, because in a crisis the operator’s most valuable information is whether the thing they just ran worked.

Start from code/runbook-template.md. Eight sections: where it runs, start/stop/restart, the health check, routine operations, normal readings, the failure catalogue, escalation, and last-verified.

The heart of it is the failure catalogue, and here is the rule that makes it real: write it from your defect log, not from your imagination. You spent Week 11 building docs/defect-log.md. Every defect you fixed already happened once in this system, which makes it far likelier than any failure you could invent. Mine that log — five entries minimum, and the best ones come from the bugs that embarrassed you.

Bad runbook entry:

Database issues If the database has problems, check the connection and restart the service if needed. Make sure your credentials are correct.

Read that as somebody whose app is down. “Has problems” — which problems? “Check the connection” — with what command? “If needed” — how would I know? Every clause pushes the decision back onto a reader who came here precisely because they did not know what to do.

Good runbook entry:

F-03 — App returns 500 on every request; log shows ECONNREFUSED 127.0.0.1:5432

  • Symptom: every request returns 500. docker compose logs api shows ECONNREFUSED 127.0.0.1:5432 repeating.
  • First check: pg_isready -h localhost -p 5432 → healthy is localhost:5432 - accepting connections.
  • Likely cause: the database container is not running, or started after the API and the API did not retry. This happened twice in Week 12 (see defects D-14, D-19).
  • Fix: docker compose up -d db, wait for pg_isready to pass, then docker compose restart api.
  • Verify: curl -s localhost:3000/health returns {"status":"ok","db":"connected"}.
  • If that does not work: the data volume may be corrupt. Stop. Do not run docker compose down -v — that deletes the volume. Restore from the latest backup (section 4) and escalate.

That last line separates a runbook from a wiki page: it tells a frightened operator which action is irreversible. A runbook that does not name the destructive commands is worse than no runbook, because it moves fast and confident right up to the edge of data loss. Two more disciplines and the document is done:

  • Normal readings. State what healthy looks like: startup time, typical response time, log lines that are noisy but harmless. An operator cannot recognize abnormal without a baseline, and you are the only person who has ever seen this system healthy.
  • Last verified. Put a date and a name at the bottom, and mark anything you did not personally run as UNVERIFIED. An unverified runbook step is a trap with a friendly face.

Coach’s Note — Test the runbook the cruel way. Break your own system on purpose — stop the database, revoke the API key, fill the disk with a junk file — then follow your own entry to recover, without improvising. Three deliberate breakages will teach you more about your runbook than three hours of writing it.


13.6 — The Onboarding Guide and the Decision Record Index

The onboarding guide

The README gets a stranger running. The onboarding guide gets a developer productive. It answers one question — “how do I make my first change safely?” — and it is the document a new teammate opens. Six parts, and it can be short:

  1. The ten-minute orientation. What the system does, the three files that matter most, and the vocabulary. If your domain has words with specific meanings — in PantryPilot, an item is a physical thing in a pantry, a product is a barcode-identified type of thing — define them here. Nothing costs a newcomer more time than a word they think they already understand.
  2. Environment in one command. Point at the README; do not duplicate it. Duplicated setup instructions drift, and the copy that drifts is always the one the newcomer reads.
  3. The feature-to-code map. A short table: “expiry alerts” → src/jobs/expiry.js + src/services/expiry.js + tests/expiry.test.js. Five rows. This one table saves more time than every other section combined.
  4. Your first good first issue. Pick a real, small, genuinely useful task from your backlog — a copy fix, a missing validation, one more test — and write it up with the file to open, the test to run, and what “done” looks like. Then do not do it yourself. Leave it there.
  5. How work moves. Branch naming, commit style, what CI runs, what must be green before merge.
  6. Where decisions live, and how to add one.

The decision record index

Since Week 5 you have been writing architecture decision records — docs/adr/0001-choose-the-stack.md and its siblings. By now there are four to a dozen, and a folder of numbered files is an archive, not a resource. The index turns it into one. It is a table, and it lives at docs/adr/README.md so GitHub renders it when someone opens the folder:

ADRDecisionStatusConsequence you would notice
0001Node + PostgreSQL over Python + SQLiteAcceptedDeployment needs a real database host
0004Server-rendered pages, no SPA frameworkAcceptedNo offline mode; page loads on every action
0006Expiry computed in the app, not in SQLAcceptedBusiness rules stay testable; queries are chattier
0007Sessions in Postgres, not RedisSuperseded by 0011
0011Signed cookies, no session storeAcceptedLogout does not invalidate other devices

Status is the field that carries the weight, and it is the field students leave stale. An ADR is Proposed, Accepted, Superseded by NNNN, or Deprecated. A superseded record is never deleted — deleting it destroys exactly the history the index exists to preserve. The fourth column is a gift to the successor: it tells them, without reading five documents, which decision to reopen when a requirement changes.

Now the harder exercise, and it is the one worth your time this week. Look at your system and find the decision with no record. There is always one — a thing you chose in Week 9 under time pressure that has shaped everything since, and never got written down. Write it now, dated honestly, with the context you actually had. That record is the most valuable page in your docs/adr/ folder, because it is the one nobody could reconstruct.


13.7 — Writing for the Successor Who Cannot Ask You a Question

Everything above is craft. This is the mindset, and it changes how you write every line of the rest. Imagine one specific person. Competent — as good an engineer as you, maybe better. They have your repository open. They have never met you, and they cannot reach you: not by email, not by Slack, not ever. They have to make a change by Friday. Every question they will ask, you must have already answered. That is the standard.

The failure mode is not laziness. It is the curse of knowledge — the difficulty of imagining not knowing something you know. You cannot un-know your own port number, your own naming convention, the reason the retry loop runs three times. Your brain fills those gaps silently as you read your own prose, which is why re-reading your own documentation never finds the holes. Only another reader finds them. Three techniques work anyway.

Write the question, then answer it. Draft each section by first writing the reader’s question as a literal sentence — “Wait, where does the API key go?” — then answering it. Delete the questions at the end if you like; the order they force is the reader’s order, not yours.

Answer “why,” not just “what.” The what is recoverable from the code. The why is only in your head, and it is what a successor most needs, because it tells them which parts are safe to change.

What only (recoverable from code)Why (only you have it)
“The retry loop runs three times.""Three retries, because the barcode API returned 502s in bursts of two during Week 10; four made the request feel slow."
"Results are cached for 24 hours.""24 hours because product names never change; the cache also keeps us under the free-tier request budget."
"user_id is a string, not an integer.""The auth provider issues opaque string subjects; converting them lost leading zeros. See ADR 0009.”

Name the traps. Write down what will bite a successor: the test that is flaky on slow machines, the migration that must run before the seed, the variable that fails silently when unset. Each is one sentence, and each saves a stranger an afternoon.

Coach’s Note — Here is the test I actually apply as a grader. I open a repository and ask myself: if the author vanished today, could this project continue? That question has a name — the bus factor — and for almost every capstone the answer is one. Documentation is how you raise it above one. Next week (Chapter 14) we make it structural; this week we make it written.


13.8 — The Assistant Drafts. You Verify. Every Command.

Let us be honest about the tool in both directions. An assistant is genuinely good at documentation: give it your repository and it will produce a well-structured README with all eleven sections, sensible headings, and clean prose, in less time than it takes you to make coffee. Refusing that help is not rigor; it is waste. Use it.

Now the failure mode, precisely stated, because the general warning is not enough. A model generating setup instructions is inferring, not observing. It sees a file named docker-compose.yml and infers a docker compose up. It sees PORT in your config and infers the default. It sees a dependency and infers an install command. Those inferences are usually right, which is what makes them dangerous: a document that is 90% correct reads exactly like one that is 100% correct, and the reader who hits the wrong 10% cannot tell whether the error is in your docs or in their machine. They will assume it is their machine. They will burn an hour.

And prose has no compiler. A hallucinated function call fails loudly at runtime. A hallucinated install step fails in a stranger’s terminal three weeks from now, silently, in a way you never see. So the division of labor is fixed:

Give the assistantNever take from the assistant without running it
Structure and section orderAny command
Turning your rough notes into clear proseAny version number
Rewriting a paragraph for a reader who lacks your contextAny file path
Generating candidate troubleshooting entries from your defect logAny environment variable name
Finding gaps: “what would a new user still not know?”Any port, URL, or expected output
Tightening a wordy runbook stepAny claim about free tiers, pricing, or licenses

The right prompts are the gap-finding ones, because that is where a model beats you: it does not share your curse of knowledge.

“Here is my README. You are a competent engineer who has never seen this project. List every question you still cannot answer, and every step where you would have to guess.”

That prompt is worth an hour of your own re-reading, because the model genuinely does not know what you know. The verification protocol, meanwhile, has exactly one rule: every command in your documentation gets executed by you, in order, from a clean state, and its real output pasted in. Not skimmed. Not “that looks right.” Executed. Capture it honestly:

# keep a transcript you can paste real output from
script -q /tmp/doc-verify.log
# ...run every command from the README, in order...
exit

Then log the use. Your docs/ai-usage.md gets a row for this week naming the tool, what it drafted, and — the part a grader actually reads — what you found wrong when you ran it. Do not round that number down. If the assistant drafted eleven commands and three were wrong, “3 of 11 commands required correction” is the most credible sentence in your entire submission, and it demonstrates the exact judgment this course grades. The course-wide policy is in Appendix C.

Coach’s Note — The trap is not that the assistant lies. It is that it writes your documentation in a confident voice you did not earn. When you paste a generated section unverified, you are vouching for it with your name. Verification is not paranoia; it is the price of the byline.


13.9 — Documenting the AI Parts a Successor Must Not Trust

If your project calls a model — a recipe suggester, a classifier, a summarizer, an embedding search — you have shipped a component with a property none of your others have: it can change without your code changing.

Every other dependency is pinned. Your lockfile fixes your libraries; your container fixes your runtime. A hosted model behind an API is pinned by none of that. The vendor updates it, deprecates a version, adjusts a default, and your system’s behavior moves while your repository sits untouched. Your successor will not discover this from your code, because it is not in your code.

So docs/ai-usage.md gets a successor-facing section this week, alongside the log of your own AI use. Six things:

  1. What the feature does, and what it does when it fails. “Suggests up to three recipes from current inventory. On API error or timeout, the panel is hidden and the rest of the page works normally.”
  2. The exact model and version string, and the date you pinned it. Not “GPT-class model.” The identifier your code sends, quoted from your config, plus the date. Model identifiers and their availability change; that is precisely why the date matters.
  3. Where the prompt lives. A file path, not a description. Prompts are source code — they belong in version control, reviewable in a diff, not embedded in a string literal three call frames deep. prompts/recipe-suggest.txt is a good answer.
  4. The parameters that change behavior. Temperature, max tokens, whether responses are cached. If you set temperature to zero for reproducibility, say so and say why.
  5. Cost and limits, hedged honestly. What one call costs you and roughly what a month of normal use costs, as measured by you, on the date you measured it — with a plain sentence that prices and free tiers change and must be verified with the vendor before anyone relies on your number. Never state a vendor’s current pricing as a fact in a document that will outlive the week.
  6. What must not be assumed stable. This is the section that earns its place. Spell out that the output is non-deterministic, that upgrading the model may change results, and what a successor should run to find out whether it still behaves. If you have a small set of fixed inputs with acceptable outputs — even five — say where it lives and how to run it.

Bad: “The app uses AI to suggest recipes.”

Good:

### Recipe suggestions (AI feature)

- Sends the user's current inventory to a hosted chat model and asks for up to 3 recipes.
- Model identifier and version are set in `config/ai.json`; pinned 2026-03-14. Verify the identifier
  is still offered before assuming this runs — hosted model availability changes without notice.
- Prompt: `prompts/recipe-suggest.txt`. Edit it there, never inline. Changes must be reviewed like code.
- temperature 0.2, max 400 tokens, responses cached 1 hour per inventory hash.
- Cost: measured under our own account on 2026-03-14 at roughly $0.002 per suggestion, about $0.60/month
  at our usage. Pricing changes — re-measure before relying on this figure.
- **Not stable:** output is non-deterministic and differs between runs and between model versions. Nothing
  downstream parses it beyond splitting the numbered list. Five reference inputs and their acceptable
  outputs are in `tests/fixtures/recipe-samples/`; run `npm run test:ai` after any model change.
- **Not a safety system.** Suggestions are not checked for allergens or food safety. The UI says so.
  Do not remove that notice.

That last bullet is the one an experienced reviewer looks for. A documented limitation is engineering. An undocumented one is a liability you handed to somebody who did not know they were taking it.


13.10 — The Clean-Machine Test: Prove It, Do Not Claim It

Here is the week’s centerpiece, and it is not a document. It is an experiment. The clean-machine test: a person who is not you, on a machine that has never run your project, clones your repository and — using only the documentation in it — reaches a working system while you sit silently and take notes. Six rules, and every one exists because students break it:

  1. A clean machine. A fresh container, a fresh cloud development environment, a classmate’s laptop, or a brand-new user account on your own machine. If you are unsure what counts, Appendix A lays out the options including the no-admin browser path.
  2. A real tester who is not you. A classmate, a roommate, a sibling who codes. Competent, uncoached.
  3. You are silent. You may watch. You may not speak, point, type, or fix. The instant you help, the run is void — because the help you gave is exactly the documentation you are missing.
  4. The docs are the only input. They get the repository URL. Nothing else. No verbal preamble, no “oh, you also need to…”.
  5. The clock runs from opening the repository to completing the acceptance action. Stop at sixty minutes and record the failure honestly.
  6. Every stumble gets logged, including the ones you think are the tester’s fault. Especially those — “the user did it wrong” is a documentation finding wearing a disguise.

Rule 3 is the one that hurts. Watching a competent person fail to run software you wrote, in silence, while a clock runs, is genuinely uncomfortable. Sit in it. That discomfort is the most efficient documentation feedback that exists, and it is why this is a graded milestone instead of a suggestion.

Define your acceptance action before the run: the one concrete thing that proves the system works, not that it started. “The server started” is not it. “Added an item with an expiry date and saw it appear in the Expiring Soon list” is. For a CLI like TraceLens — our second example, a log-parsing tool with no UI at all — it is “ran the tool against the sample log in the repo and got the expected anomaly count.” Same test, very different project; the protocol does not care about your stack.

The full protocol, the stopwatch table, and the friction log are in code/clean-machine-protocol.md. Copy it into your repository as docs/clean-machine-test.md and fill it in during the run. What you will find, roughly in this order, because everyone finds these:

RankThe findingWhat it actually means
1A prerequisite you never wrote downYou have had it installed since sophomore year
2A configuration step that lives only in your fingersYou have typed it so often you stopped seeing it
3A command whose output looks like an error but isn’tYou learned to ignore it; they cannot
4A step that assumes the previous step’s directoryYou never cd because your terminal is already there
5A seeded database with no seed scriptYou made the data by hand in Week 9
6A variable that fails silently when unsetEmpty string, no error, wrong behavior three screens later

Then the part that converts an experiment into a grade: fix, re-run, record. Every friction entry becomes a documentation change; then you run the test again — new environment, ideally a second tester — and record the second time-to-acceptance. Two numbers, before and after, in docs/clean-machine-test.md. That delta is the evidence. Milestone 13 grades the honest record of a real run far more generously than a suspiciously clean one, and a run where the tester needed help is a finding, not a failure. Hiding it is the only actual failure available to you this week.

Automate what you can while you are here — a make setup target, a script/bootstrap, a dev container definition; anything that turns six documented steps into one command removes six chances to be wrong. But automate after the first clean-machine run, never before. The run tells you which steps are worth scripting, and next week’s chapter takes reproducibility the rest of the way.

Coach’s Note — When your tester gets stuck, resist the sentence forming in your mouth. Write it down instead. That exact sentence — the one you wanted to say out loud — is the sentence missing from your README, and you now have it in the reader’s own words. Every silent minute buys you a line of documentation you could not have written alone.


13.11 — Interactive Lab: The README Grader

Below this chapter on the website is The README Grader. Do the lab before you rewrite your own README, not after. You will build one section by section — what it is and who it is for, screenshot or demo, prerequisites with versions, install, configure, run, test, project layout, troubleshooting, license, contact — filling or pasting each one. The widget scores it live against the standard a grader applies, flagging the exact failures this chapter has been naming: unstated prerequisites, commands with no expected output, “just run it” hand-waving, hard-coded personal paths, missing configuration steps. Then it projects a time-to-first-successful-run for a stranger.

Do it twice. First paste in your README exactly as it stands right now — no cleanup, no cheating — and take the score and the projected time honestly; that is your real starting position. Then fix what it flags and run it again. It teaches one idea, and it is the idea of the whole week: documentation quality is not a matter of taste. It is measurable, mechanically, against a checklist a stranger’s success depends on — and the gap between your first score and your second exists in your repository right now, invisible to you.

Then run code/doc_lint.py over your actual file for the mechanical pass the widget cannot do on your local machine:

python3 doc_lint.py README.md

It checks the eleven sections, hard-coded home directories, hand-waving phrases, unpinned prerequisites, anything that looks like a real secret, and whether any command anywhere has a stated expected output. It exits non-zero on errors, so you can wire it into CI next week. A clean report is necessary and nowhere near sufficient — no linter can tell you whether a person can follow your instructions. Only a person can.


13.12 — Where Your Hours Went This Week

Fifteen hours, honestly budgeted. Log them in your hours log as you go; Week 16 asks for the record.

ActivityHours
Reading this chapter, the README Grader lab, the linter1.0
README rewrite: eleven sections, every command run and its output captured3.5
Architecture overview: diagram plus five paragraphs2.0
Runbook: operations, normal readings, five failure entries mined from the defect log2.5
Onboarding guide, ADR index, the AI section of docs/ai-usage.md2.0
The clean-machine test: recruiting, setup, the run itself, the friction log2.0
Fixing what the test exposed, re-running, committing1.5
Weekly quiz0.5
Total15.0

If your README rewrite takes 45 minutes, you did not rewrite it — you tidied it. Most of those three and a half hours go to running commands and pasting real output, which is exactly where the value is.


13.13 — What Does It Mean to Entrust Your Work to Another?

“…what you have heard from me in the presence of many witnesses entrust to faithful men, who will be able to teach others also.” (2 Timothy 2:2, ESV)

Read that verse as an engineer for a moment, because it is, among other things, a transfer specification. Paul is writing to Timothy near the end of his own life, and he is not asking Timothy to keep what he received. He is asking him to hand it on — and note the chain: Paul to Timothy, Timothy to faithful people, those people to others still. Four links. The thing being entrusted is designed from the beginning to survive the person carrying it. That is a remarkable engineering property, and it is not an accident of the sentence; it is the point of it.

Now hold that next to what most students do with a capstone. We build something, submit it, and imagine the story ends at the grade. The repository goes quiet. Nobody clones it. The knowledge that lived in your head for sixteen weeks stays in your head and slowly evaporates, and within a year even you cannot run it. That is not a transfer; that is a project that ended when its author’s attention did.

There is a real distinction worth naming here. To publish is to make something available. To entrust is to place something into another’s care, on purpose, prepared for them, with their capacity in mind. Publishing is one-way and costs the publisher nothing. Entrusting is a relationship, and it makes demands: you have to know something about who is receiving it, you have to prepare it for hands that are not yours, and — this is the hard part — you have to let go of controlling what they do with it.

Every piece of work you do this week is that second thing rather than the first. The README is written for someone whose knowledge you had to imagine. The runbook is written for someone under pressure whose mistakes you had to anticipate. The ADR index preserves the reasoning rather than the conclusion, because the successor may need to reverse your decision and cannot do that responsibly without knowing why you made it. Notice what that last one means: you are documenting your decisions so that someone can undo them well. That is not the instinct of an author protecting a work. It is the instinct of someone who cares more about the thing continuing than about being right.

The verse names a quality in the receiver — faithful, and specifically able to teach others also. Paul is not entrusting to anyone at all. He is entrusting to people who will keep the chain going. And that reframes the successor you are writing for. They are not a consumer of your documentation; they are the next author of it. Which means the standard is not “can they run it” but “can they carry it” — extend it, fix it, explain it to someone else. The onboarding guide’s “first good first issue” is exactly this in miniature: you are not just letting them read the code, you are handing them the first act of ownership and getting out of the way.

There is a humility in this that runs against a strong instinct, and it is worth saying plainly. Sixteen weeks in, your project feels like yours. Documenting it well means admitting it will outlive your involvement, that someone else’s fingerprints will end up on it, that the parts you were proudest of may be replaced by someone who understood the problem better later. Scripture’s frame for that is stewardship rather than ownership — “it is required of stewards that they be found faithful” (1 Corinthians 4:2, ESV) — and a steward’s success is measured by the condition of what they hand on, not by how tightly they held it.

Which is where the clean-machine test stops being a rubric line and becomes something more like an examination of conscience. Sitting silent while another person struggles with what you built is a small, exact picture of the whole thing: your work, in someone else’s hands, your intentions counting for nothing next to what you actually prepared. You cannot explain. You cannot help. All that reaches them is what you wrote down. Every stumble is a place where you served your own convenience instead of theirs — not maliciously, just by not imagining them. The remedy is not guilt; it is the friction log, and then the fix.

So this week’s craft has an older name. Faithfulness in a small thing. You are not entrusting the gospel; you are entrusting a pantry app, and the difference in weight is enormous. But the habit is the same habit, and the habit is what you are actually building here — because the person who writes an honest README for a class project is the person who, later, hands over a production system carefully, tells a successor the truth about what is fragile, and does not leave landmines for whoever comes next. “One who is faithful in a very little is also faithful in much” (Luke 16:10, ESV). This week is the very little. Be found faithful in it.


13.14 — Common Pitfalls

Pitfall: The README documents the project’s ambitions instead of its current state. Example: “PantryPilot suggests recipes, tracks nutrition, and syncs across devices” — when two of those three do not exist. Fix: Write a Status line and a What it does not do section, both above the fold. Describe today’s build. Aspirations belong in the backlog, not the front door — and a reader who trusts your non-goals will trust everything else you wrote.


Pitfall: “Just run the install command and you’re good to go.” Example: A Setup section that is three imperative sentences with no commands, no expected output, and no failure path. Fix: Every step gets a literal command in a fenced block, a stated expected result, and — for the steps that commonly fail — what the failure looks like. Run code/doc_lint.py; it flags this class of hand-waving by name.


Pitfall: Prerequisites that assume your machine’s accumulated toolchain, and personal paths pasted from a working terminal. Example: “Requires Node and Postgres,” written on a laptop where both have been installed since sophomore year — followed by cd /Users/jordan/projects/pantry && export API_KEY=sk_live_7f3a.... Fix: Pin versions from real output, give a check command for each, and name the failure mode. Relative paths from the repository root, placeholder secrets only, .env.example in version control and .env in .gitignore. If a real key ever reached a commit, rotate it — deleting the line does not remove it from history.


Pitfall: An architecture “overview” that restates the file tree. Example: Four paragraphs walking through src/, src/controllers/, src/models/, telling a reader what any ls would tell them. Fix: Components and the arrows between them; the data and what is authoritative; one traced end-to-end flow; the consequential decisions with links to their ADRs; and the one thing that would surprise a new reader. If a paragraph would be true of ten thousand other repositories, delete it.


Pitfall: A runbook written from imagination rather than from the defect log. Example: Generic entries — “if the service is slow, check resource usage” — for failures that have never once occurred, while the two outages that actually happened in Week 12 are absent. Fix: Open docs/defect-log.md and convert real defects into entries: symptom, first check, likely cause, fix, verification, escalation. Then break your own system deliberately and follow your own entry to recover.


Pitfall: A clean-machine test where you helped. Example: “It mostly worked — I just had to tell them about the database step and fix one path for them.” Fix: That run is void, and the two things you said out loud are your two highest-priority documentation fixes. Write them down, patch the docs, and run the test again with a fresh environment and, if possible, a fresh tester. The record in docs/clean-machine-test.md should show the honest first run, the fixes, and the re-run. Graders trust that record far more than a flawless one.


13.15 — Reps

The reps for this week are in the exercises, and they are not warm-ups. Each one produces a piece of the documentation set that Milestone 13 grades. Preview:

  • Rep 1 — score your current README against the eleven-section standard, honestly, before you touch it.
  • Rep 3 — hunt your hidden assumptions: real version output, hard-coded paths, invisible accounts.
  • Rep 6 — convert five real entries from your defect log into runbook failure entries.
  • Rep 9 — run code/doc_lint.py over README.md and fix everything it flags.
  • Rep 10 — have an assistant draft your troubleshooting section, then run every command it produced and count the wrong ones.

Then do the on-page Check Your Reps quiz — the ungraded rehearsal for Week 13 Quiz in Canvas, and your early warning if this week’s ideas have not landed.


13.16 — This Week’s Milestone

Milestone 13Milestone 13: Documentation Set & the Clean-Machine Test. You will ship the full set — README.md, docs/architecture.md, docs/runbook.md, docs/onboarding.md, docs/adr/README.md, and the successor-facing section of docs/ai-usage.md — then prove it with a real clean-machine test, recorded in docs/clean-machine-test.md, fixing what it exposes and running it again.

These artifacts are not extra work stacked on top of the final submission. They are lines in the Week-16 rubric, produced in the week that has time for them. The milestones are graded twice for exactly this reason: skipping this week does not save you points, it forfeits the milestone and moves the rest to Week 16, where the hours have already been spent.


13.17 — Coach’s Final Word

For thirteen weeks you have been the only person who could run this thing. That was fine — necessary, even. You were building. This week that ends, and it should. A project only one person can run is not finished software; it is a private skill with a repository attached. The moment a stranger clones your work and gets it running without you in the room, something changes about what you made: it stops being a demonstration of what you can do and becomes a thing that exists on its own.

You will not enjoy the clean-machine test. Nobody does. You will sit on your hands while a competent person fails at something you find trivial, and every instinct will scream at you to reach over and type. Don’t. That hour of silence is worth more than the ten hours of writing that preceded it, because it is the only hour that tells you the truth. Then fix what it found, and run it again. That is the whole job.

Next week we make it reproducible and hand over the keys. This week, we make it explainable. Write it down. Prove it works.

See you on Monday.


Up next: the exercises builds the documentation set rep by rep · Milestone 13 is Milestone 13 · then Chapter 14 — deployment, reproducibility, and the handoff package. Reference: Appendix A (workbench and clean environments), Appendix B (document kit — templates and worked good-versus-bad examples), Appendix C (grading contract and AI policy), Appendix E (glossary). Previous: Chapter 12.

Interactive Lab — Week 13
The README Grader

Paste your README below. The widget looks for the eleven sections a grader checks, then looks inside each one for evidence it would actually work — a version number in the prerequisites, a real command under Run, an expected output under Test. Change the reader to see what your unstated assumptions cost someone who does not share your machine.

Nothing is saved or sent anywhere. Editing rescores instantly.

README score out of 100
Projected time to first successful run target: 15 min
Verdict

Section by section

    Grader's flags

      Weights, minute costs, and reader multipliers here are a teaching heuristic, not a measurement — your instructor's rubric governs. No amount of green here replaces the clean-machine test.

      Check Your Reps

      Week 13 Knowledge Check

      Question 1 of 5
      Your clean-machine tester has been stuck for eight minutes on a database step your README.md never mentions. You know the one command that would unblock them. What does Chapter 13 say to do?
      Why: Rule 3 is silence: you may watch and take notes, but you may not speak, point, type, or fix. The instant you help, the run is void — because the help you gave is the documentation you are missing. So the coach's instruction is to write that sentence down instead of saying it. You now have the missing line in the reader's own words, which is something you could never have written alone. Every silent minute buys you a line of documentation.
      Question 2 of 5
      This paragraph appears in a student's docs/architecture.md. What is the chapter's objection to it?
      The system uses a modern three-tier architecture with a frontend, a
      backend, and a database. The backend is organized into controllers,
      services, and models. The frontend calls the backend via REST endpoints.
      This design promotes separation of concerns and maintainability.
      Why: That is the exact test the chapter applies, and it is worth keeping: if a paragraph would be true of ten thousand other repositories, delete it. The good version of the same paragraph names what each component owns and deliberately does not own, cites the decision record behind a specific choice, and reports a real event — a third-party service being down for six hours in Week 10 — that shaped the design. Same length; only one of them is about a system.
      Question 3 of 5
      A student's docs/ai-usage.md row for this week reads: "Used an assistant to draft the troubleshooting section. Reviewed the output and it looked good." What is missing — the part a grader actually reads?
      Why: "Reviewed and it looked good" is not verification. A model generating setup instructions is inferring, not observing — it sees a file and infers a command, sees a config key and infers a default — and prose has no compiler, so a wrong step fails silently in a stranger's terminal three weeks later. The protocol has exactly one rule: every command gets executed by you, in order, from a clean state, with the real output pasted in. Then report the correction rate without rounding it down; that number is the most credible sentence in your submission.
      Question 4 of 5
      Chapter 13 says a hosted model API needs its own successor-facing section in docs/ai-usage.md. What property makes it unlike every other dependency in your project?
      Why: Every other dependency is pinned — the lockfile fixes your libraries, the container fixes your runtime. A hosted model behind an API is pinned by none of that: the vendor updates it, deprecates a version, or adjusts a default, and your system's behavior moves while your repository sits untouched. Your successor will never find that in the code, because it is not in the code. Hence the six required items, ending with the one that earns its place: what must not be assumed stable — plus the fixture set and the command to re-run after any model change.
      Question 5 of 5
      You ran this week's solo dress rehearsal — clean environment, repository as your only input — and it took thirty-five minutes to reach your acceptance action. What do the reps tell you to do next?
      Why: The thresholds are explicit: over twenty minutes solo means the graded run will fail, so fix the top three friction entries first; under fifteen minutes using nothing but the repository means you are ready for a real tester. And note the trap in the third option — automation comes after the first clean-machine run, never before, because the run is what tells you which steps are worth scripting. Automating first hides the friction you were trying to measure.
      YOU FINISHED. NICE WORK.