Appendix C

The Document Kit

Every artifact this course requires, as a fill-in template and a worked example — with the bad version shown next to the good one

Appendix C — The Document Kit

“Programs must be written for people to read, and only incidentally for machines to execute.” — Harold Abelson and Gerald Jay Sussman, Structure and Interpretation of Computer Programs (1985)

“And the LORD answered me: ‘Write the vision; make it plain on tablets, so he may run who reads it.’” — Habakkuk 2:2 (ESV)


This is the reference you will open most often, and it is deliberately shorter than the sixteen-week edition’s kit. That is not because eight weeks demands less professionalism. It demands exactly the same artifacts — the Week-8 rubric in Appendix D has the same twelve lines the long edition grades. What eight weeks cannot afford is ceremony: nine-section test plans, four-page decision records, three documents where one will carry the load. So every template here is sized to the hours you actually have, and nothing professional has been removed.

Every deliverable appears three times.

  1. A blank template. Most of these also ship as a real starter file in a chapter’s companion code — the table below says which week hands you which. Copy the chapter’s file; read this appendix when you are stuck on a section.
  2. A worked example, carried all the way through from one Fast-Start brief. Your project will look nothing like it. The shape transfers exactly.
  3. A deliberately bad version, annotated — not a strawman, the actual draft I read every term — with each defect named and priced. Not “this is weak.” This costs you these points, in this week, for this reason.

The bad versions are the point. Anybody can recognize good writing sitting in front of them. What you need is the ability to look at your own draft — which feels fine, because you wrote it and you know what you meant — and see the failure in it.

Coach’s Note — Skim this appendix once in Week 1 so you know what is in it. Then open one section on the Monday of the week that needs it, before you write a line. Read it like a cookbook: on the night you are cooking the dish, not front to back.


How to Use This Appendix

The thirteen artifacts, and when each one lands

§ArtifactHanded to you inStarter file in that chapter’s companion codeLands in your repo at
C.1Charter and scoping decisionWeek 1 — Chapter 1charter-template.md, scoping-decision-memo.mddocs/charter.md, docs/scoping-decision.md
C.2Requirements specification (functional + non-functional)Week 2 — Chapter 2srs-template.md, adaptation-worksheet.md, check_requirements.pydocs/requirements.md
C.3Acceptance criteriaWeek 2, inside srs-template.md(inside the SRS template)inline with every requirement
C.4Architecture decision recordWeek 3 — Chapter 3adr-one-page.mddocs/adr/NNNN-*.md
C.5Work plan and risk registerWeek 3 — Chapter 3design-pack-template.md, Parts 2 and 3docs/plan.md, docs/risk-register.md
C.6Test planWeek 6 — Chapter 6test-plan-lite.md, traceability-matrix.csvdocs/test-plan.md, docs/traceability.csv
C.7Defect report and defect logWeek 6 — Chapter 6cut-kit.md §1docs/defect-log.md
C.8Hours logStarted Week 1; the sample and the report script arrive Week 5 — Chapter 5hours-log.csv, hours_report.pydocs/hours-log.csv
C.9READMEWeek 7 — Chapter 7readme-template.mdREADME.md
C.10RunbookWeek 7 — Chapter 7no starter file — the seven-section shape is taught in §7.4 and templated heredocs/runbook.md
C.11Handoff guide (onboarding folded in)Week 7 — Chapter 7handoff-template.mddocs/handoff.md
C.12Change log and change requestsWeek 4 — Chapter 4, used hard from Week 6baseline-and-change-control.md, cut-kit.md §3CHANGELOG.md, docs/change-requests/
C.13RetrospectiveWeek 8 — Chapter 8retrospective-template.mddocs/retrospective.md

Two rows of that table land in a directory, not a file — docs/adr/ holds one record per decision, docs/change-requests/ one CR-nnn-*.md file per request — and getting either wrong is a real defect, because the sixteen-week edition uses the identical layout and the two books must not disagree.

Three further documents obey every convention here and have no form of their own, because they are not forms: docs/definition-of-done.md (Week 5, from that chapter’s definition-of-done.md), docs/architecture.md (Week 3, from Part 1 of the design pack), and docs/ai-usage.md (Week 1 onward, one row the day each use happens).

About the running example

The worked examples follow PantryPilot — brief #1 in Appendix B — as one student actually adopted it. The student is M. Alvarez, sole developer, the same person who wrote the ADOPT record in Chapter 3. The household is their own four-person apartment; the named user is S. Okafor, who buys most of the groceries, and the housemate who will not type is T. Bell. Replacing the brief’s Dana and Marcus with two people you can name, on a date you can write down, is the first thing brief #1 tells you to do.

Three Must feature slices, five Must requirement identifiers, one deployment. That is the two-thirds rule made concrete: 160 hours buys about two-thirds of a sixteen-week capstone, so three or four Must features, not five or more.

Dates come from one representative term with Week 1 beginning 2026-08-31 and Week 8 ending 2026-10-25. A chapter’s own sample may carry a different term’s stamp — nothing in this course turns on the calendar, it turns on the ordering.


C.0 — Conventions Every Document Here Obeys

Identifiers

KindFormExampleRule
Functional requirementFR-<AREA>-<nn>FR-EXP-02Assigned once. Never reused, never renumbered. Retire by marking Withdrawn.
Non-functional requirementNFR-<CATEGORY>-<nn>NFR-PERF-01Same rule.
Constraint / assumption / dependencyCON-nn / ASM-nn / DEP-nnASM-01Assumptions get an owner and a verify-by week.
Decision recordADR NNNNADR 0004Four digits from 0001. Immutable once Accepted.
Work package / taskWP-n / T-n.mT-3.4Task carries the requirement it serves, or - for enabling work.
RiskR-nnR-02Stable forever. Retired risks stay, marked retired.
DefectDEF-nnnDEF-009Never reused, even for a duplicate.
Test caseTC-nnnTC-018Maps to at least one requirement.
Change requestCR-nnnCR-004One file per request in docs/change-requests/.

Two identifier styles are legal and mixing them is the error. The area-scoped FR-<AREA>-<nn> is recommended, is what Appendix B ships, and is what this appendix uses throughout. The flat form — FR-007, FR-014 — is equally defensible and appears in several of the book’s chapter samples. Pick one in the first hour of Week 2 and never revisit it.

What breaks if you mix: check_requirements.py, in the Chapter 2 companion code, accepts either style and reports mixed styles as an error the moment both appear in one document — and a linter error costs you the Milestone 2 rubric line that pays for a clean run. The deeper cost is that FR-014 and FR-EXP-14 are the same requirement to you and two different rows to every tool you will write, which is how a traceability matrix grows orphans that are not really orphans.

If you adopted a brief, restate its identifiers in your scheme before anything references them. First hour of Week 2. After that it is a rename across five documents.

The document control block

Every document opens with the same four facts:

**Author:** <name>  ·  **Version:** <x.y>  ·  **Date:** <YYYY-MM-DD>  ·  **Status:** Draft | In review | Baselined

and closes with a change-log table: date, version, what changed, why. Three rows in that table by Week 8 is a document somebody maintained. Zero rows is a document written once and never opened again, and a grader can tell in four seconds.

Status values

Draft — being written; nothing depends on it; you may change it freely. In review — frozen for the length of a review; nobody changes it until the review closes. Baselined — frozen at the Week-4 design review; changes only through a request filed in docs/change-requests/. Superseded by ADR NNNN — decision records only; the record stays, the decision does not, and nobody edits it. Ever.

The four tests every document must pass

  1. The stranger test. Could someone who has never met you and cannot ask you a question act on this?
  2. The number test. Every quantity has a number. Fast, soon, robust, user-friendly, and scalable are not numbers. They are places where a number should have been.
  3. The failure test. Have you said what happens when the input is bad, the network is down, the list is empty, the date is missing?
  4. The date test. Every time-varying claim — a price, a version, a rate limit, a license term — carries a source and the date you checked it. Without that it is not a fact; it is a rumor you committed to version control.

Coach’s Note — Twelve minutes with those four tests, run over a document you think is finished, will find more real defects than an hour of re-reading it sympathetically. Sympathy is the enemy of review, and in an eight-week course you do not get a second review.


C.1 — The Charter and the Scoping Decision

Handed to you in: Week 1 — charter-template.md and scoping-decision-memo.md in the Chapter 1 companion code. Lands at: docs/charter.md and docs/scoping-decision.md · Hat: project manager · Time: 3 h and 2 h, both in Week 1.

In the long edition the charter is written before there is a project and the scope arrives a week later. You do not have a second inception week, so both are signed together in Week 1 — the charter says what you are committing to, the scoping decision says how you got there and what you refused. Keep them in step. Never delete revised text in either; strike it through or keep a superseded block. A charter with a visible history is worth more than one that has always been right.

Charter — template (abridged; the full form ships in Week 1)

# Project Charter — <your name>

**Owner:** <name> · **Course:** Accelerated Capstone · **Started / last revised:** <YYYY-MM-DD>

1. Purpose        two or three sentences; verifiable, not aspirational
2. Project        (FILLED IN THIS WEEK) brief adopted · one-sentence description ·
                  primary user (a person, not "users") · the one thing it must do ·
                  the one hard part · Must features (3-4 ONLY, the two-thirds rule)
                  each with hours · feature total <N> h (plan 30 h, ceiling 40 h)
3. Capacity       hours/week · total budgeted 160 · the week already broken and where
                  those hours move (FORWARD) · machine + admin rights · money ·
                  technologies I know · technologies I will learn (max two)
4. Definition of finished    3-6 bullets a stranger could check
5. Non-goals                 at least five, specific enough to point at in Week 6
6. Risks to me finishing     L / I / early warning sign / what I will do
7. Working agreement         sessions · logging · WIP limit · commit convention ·
                             cut order · scope-cut trigger · AI policy
8. Signature

Charter — worked (PantryPilot)

Owner: M. Alvarez · Started: 2026-08-31 · Last revised: 2026-09-05

1. Purpose. By the end of Week 8 I will have shipped one small system a stranger can clone, run from README.md on a machine I have never touched, and extend by picking up the next card — with the requirements, design, test, and handoff documents a professional team expects to find beside it.

2. Project. Brief #1, PantryPilot, adopted. A shared pantry for the four people in my apartment: one person enters items, everybody sees what is about to spoil. Primary user: S. Okafor, who buys most of the groceries and keeps a whiteboard that is right for about four days (interviewed 2026-09-02, 25 minutes). The one thing it must do: answer what is about to go bad correctly. The one hard part: the expiry model — half a real pantry has no printed date at all.

Must sliceRequirementsHours
Join the household with a six-character codeFR-ACC-019
Keep the pantry current — add, list, mark consumedFR-INV-01, FR-INV-03, FR-INV-0412
See what is about to spoilFR-EXP-026
Feature total27

3. Capacity. 20 h/week. Week 6 is already broken — Networks midterm, −5 h, and those hours move forward into Week 5, not backward into Week 7. Personal laptop, admin rights yes. Money: $0; everything free-tier and confirmed in writing before I depend on it. I know JavaScript, Node, and SQL. Technologies I will learn: one — running and deploying PostgreSQL myself. That is the whole novelty budget.

5. Non-goals. (1) No mobile client; one browser target, a second best-effort. (2) No accounts, passwords, or email — the join code is the identity model and its cost is accepted in writing. (3) No price, spend, nutrition, or allergen data; health claims carry a duty of care this project cannot meet. (4) No admin interface; administration is SQL, documented in docs/runbook.md. (5) No rewriting anything that works to make it elegant after Week 6.

7. Scope-cut trigger. If at the Week-4 halfway audit the calibrated plan exceeds the plannable hours by more than 5, I cut barcode entry first, then editing. Decided now, while it is cheap.

Why this one works:

  • The purpose names an observable end state, not a feeling.
  • The broken week is named in Week 1 and the hours move forward. In an eight-week course that direction is the whole trick: hours moved backward into a week that has not happened yet are hours you have already spent twice.
  • The novelty budget is capped at one, in writing, before there is any temptation.
  • Non-goal 2 refuses a whole feature area and names what it costs. That is a decision, not an omission.
  • The scope-cut trigger is a checkable condition with a date. It ends a Week-4 argument in ten seconds.

Charter — bad, and what it costs

# Project Charter

I'm going to build a really useful app that helps people waste less food. It will
be modern and scalable with a great user experience. I'll use whatever technologies
are best and learn some new ones, which is part of the point of a capstone.

Timeline: 8 weeks, I'll work on it consistently.
Goal: finish on time and get a good grade.
Risks: running out of time, technical difficulties.
#What is wrongWhat it costs
1No capacity number, and no broken week. “Consistently” is not hours.Nothing for the Week-3 plan to check itself against, so it will be optimistic by roughly the amount you wanted it to be — and every student has one broken week in eight, which now surfaces in the burn-down as an unexplained cliff.
2”Modern,” “scalable,” “great user experience.”Three adjectives, no metric. None can be tested, so none can be finished — and each is a non-functional requirement you write in Week 2 anyway, under more pressure.
3Uncapped novelty budget.The single most reliable predictor of an accelerated capstone that does not ship. Every new technology costs 10–20 unplanned hours, and the student who writes this sentence usually picks three.
4No non-goals.Nothing is refused, so everything is in scope by default, and no document can stop a Week-6 feature idea.
5No Must list and no feature total.The two-thirds rule was never applied. This student will attempt a sixteen-week project in half the calendar, which is the classic way to fail this course.
6Risks with no trigger and no response.”Running out of time” is a feeling. It cannot be detected, only discovered — in Week 7, as a catastrophe.

The deepest problem is that this charter would have been true of any project by any student in any year. A document that could have been written before you thought about anything has not helped you think about anything.

Scoping decision — template

docs/scoping-decision.md is two pages, and every sentence in it must be checkable by somebody who is not you: a number, a date, a quote, or a named condition.

# Scoping Decision — <Project Name>        Author · Date · Course week: 1

1. The decision      adopt / adapt / bring my own · brief #n · briefs I shortlisted
2. Problem           the user, what goes wrong, the cost WITH A NUMBER, the existing
                     workaround and why it fails. No technology nouns in this paragraph.
3. Evidence a user exists   who, when, how long, three verbatim quotes
4. ADAPTATION RECORD — the section for this edition
   | # | what I changed | why | drafted requirements it touches | repair due |
   + requirements carried over unchanged · requirements withdrawn, one line each
5. Chosen scope      3-4 Must features, one vertical slice each, with hours.
                     Plan 30 h of net feature construction. Hard ceiling 40.
6. Should features   7. Out of scope (>= 8 named items)   8. Accepted tradeoffs
9. Rejected briefs   the gate each failed, with the number or the quote that killed it
10. Feasibility screen | build (novelty <= 2) | get | ship | show | verdict + dated evidence |
11. Hour budget reconciled against 160    12. The one hard part
13. Risks and the SCOPE-CUT TRIGGER: if <checkable condition> by <real date>, I cut
    <feature> first, then <feature>. Decided now, so I do not decide it while panicking.

The failure this memo prevents is specific: keeping a drafted requirement that quietly describes somebody else’s project. If you adopted a brief unchanged, §4 says “none” and you sign that too. The Chapter 1 file walks every prompt; do not rebuild it from here.


C.2 — The Requirements Specification

Handed to you in: Week 2 — srs-template.md, adaptation-worksheet.md, and check_requirements.py in the Chapter 2 companion code. Lands at: docs/requirements.md · Hat: business analyst · Time: about 13 h across Week 2 — roughly 8 on the functional half, 3.5 on the non-functional half, 1.5 on the adaptation sweep.

This is the contract. Everything downstream points at these identifiers: the design serves them, the plan estimates them, the test plan verifies them, the change log closes them, the retrospective counts them. Get them stable in Week 2 and the next six weeks have a spine.

Target for this course: twelve to twenty-two functional requirements. Fewer than twelve usually means you wrote features. More than twenty-two means you wrote tasks — or you did not apply the two-thirds rule.

The section that does not exist in the long edition is §4, the Adaptation Record, and it is graded. Every requirement inherited from a brief gets one of four verdicts — Keep, Adapt, Split, Withdraw — with a reason. An inherited requirement that quietly describes somebody else’s project is the signature failure of this edition, and Week 2 exists to catch it.

SRS — template (section skeleton; the full form ships in Week 2)

# Software Requirements Specification — <Your Project Name>

**Author:** <name>  **Version:** 1.0  **Date:** <YYYY-MM-DD>  **Status:** Draft
**Adapted from:** <Fast-Start brief #n, or "own project, brief #n used as template">

1. Purpose and Scope    what it is for; what is outside this release
2. Stakeholders and Personas | who they are | what they need | evidence they exist
                        (a DATE, not "from the brief"). Include the next maintainer.
3. Definitions          every term your requirements use in a project-specific sense
4. Adaptation Record  | Brief ID | Keep/Adapt/Split/Withdrawn | My ID | what changed, why |
                        + the stale-term list: nouns that belong to the brief, not to you

5. Functional Requirements   (repeat this block; group by area; 12-22 total)
   ### FR-<AREA>-<nn> — <short imperative name>
   **Priority:** Must | Should | Could | Won't (this release)
   **Requirement:** <Actor> shall be able to <action> <object> <under what condition>.
   **Rationale:** why this exists, and which persona asked for it
   **Adaptation:** Kept | Adapted | Split | Withdrawn | New — <one line>
   **Acceptance criteria:** see C.3 — at least two, one of them a failure
   **Source:** interview date · observation · brief #n · your own recorded decision

6. Non-Functional Requirements | ID | metric · threshold · condition | Priority | method |
                        Five categories minimum; one sentence for each you skip.
7. Out of Scope (the Won't-Have list)   five rows, with reasons and revisit conditions
8. Open Questions       question · who can answer · needed by
9. Document Change Log
10-13. Constraints · Assumptions · Dependencies · Obligations

SRS — worked (PantryPilot)

§4 Adaptation Record — three rows of ten:

Brief IDVerdictMy IDWhat changed, and why
FR-INV-01KeptFR-INV-01Actor, object, and acceptance data all still true for a four-person apartment
FR-INV-03AdaptedFR-INV-03The brief’s criterion said “items entered by two different members.” In my household exactly one person enters and three read — that is the whole premise of the brief’s own hard part. Rewritten against one writer and three readers
FR-SCAN-01WithdrawnBarcode entry cut in the Week-3 scope decision: 14 h against a plannable 53.2. FR-SCAN-02 is withdrawn with it, because its entire subject is a service this project no longer calls

Stale-term list: household member, barcode, Dana, Marcus — swept with python3 check_requirements.py docs/requirements.md --stale-terms "household member,barcode,Dana,Marcus". It found two I had already caught and one I had not: FR-EXP-02’s rationale still said “the whole reason Dana would open the app.”

§5, one of the project’s sixteen functional requirements:

FR-EXP-02 — Expiring-soon view Priority: Must Requirement: A household member with an active session shall be able to view every pantry item whose expiry date falls within seven days, ordered soonest first, with already-expired items marked and undated items counted. Rationale: This is the whole reason S. Okafor would open the application. If this screen is wrong, nothing else in the project matters. Adaptation: Adapted — the brief’s window was household-configurable; fixed at seven days in the Week-3 scope decision (3 h of settings screen this budget does not have). The undated count is New, and it exists because of ADR 0004. Acceptance criteria: see C.3. Source: Interview with S. Okafor, 2026-09-02.

§6, three rows of six:

IDRequirement (metric · threshold · condition)PriorityHow it is measured
NFR-PERF-01The pantry list renders at p95 under 1.5 s with 200 seeded items on a throttled Fast 3G profile, cold cacheMust20 loads in browser dev tools with throttling applied; p95 recorded with the date and the commit
NFR-SEC-02A join code is drawn from a cryptographically secure source, stored only as a salted hash, never logged; entry limited to 10 attempts per hour per clientMustUnit test that the stored value is not the plaintext; test that the eleventh attempt in an hour is refused; grep of the captured log fixture for a known code, returning nothing
NFR-ACC-01Every interactive control is reachable and operable by keyboard alone with a visible focus indicator; body text meets a 4.5:1 contrast ratio; no information conveyed by color aloneMustMouse physically unplugged, three core tasks completed, date and tester recorded; contrast checker over every text color pair; grayscale pass

§11 Assumptions, one row of four: ASM-01 — the free hosting tier keeps the deployed app reachable for a live Week-8 demo · owner: me · verify by Week 3 · if false: demo from a local run and record a fallback video in Week 7, and NFR-REL-01’s 13-of-14 target is measured against whatever window the tier actually gives.

Why this one works:

  • Every requirement names the actor, the action, and the condition. Strip any one and it stops being checkable.
  • The adaptation record’s second row is the money row: an inherited acceptance criterion that assumed two writers, in a project with one. Caught in Week 2 it costs twenty minutes. Caught in Week 6 it is a rebuilt screen.
  • NFR-SEC-02 carries a method with three separate checks, one of them a grep of a log fixture. “We will follow security best practices” cannot be run.
  • ASM-01 names the fallback and the requirement whose measurement changes if the assumption fails. That is what makes it an assumption instead of a hope.
  • The stale-term sweep is reported honestly, including the one it caught that a careful human read did not — because a careful read is exactly what makes an inherited sentence look normal.

SRS — bad, and what it costs

## Requirements
1. The system should be fast and easy to use.
2. Users can add food items and the app tracks them and notifies them when things
   are expiring soon so they don't waste food.
3. The app will use React and a REST API.
4. Handle errors gracefully.
5. Household members can invite each other by email.
6. Barcode scanning would be nice to have.
#DefectName for itWhat it costs
1”Fast and easy to use”unmeasurableTwo adjectives, no metric, no method. You cannot pass it and you cannot fail it, so it is not a requirement — and it hides two real NFRs you write later anyway, under pressure.
2”add and track and notify” · “expiring soon” · no actorcompound + ambiguousThree obligations under one identifier: ship two of three and the traceability matrix says done. And soon has no number — you implement three days, your user assumed a week, and the disagreement surfaces in front of the person grading you.
3”will use React and a REST API”solution-biasedA design decision wearing a requirement’s clothes. It belongs in an ADR (C.4), with options and consequences; written here it removes your right to change your mind before you have evaluated anything.
4”Handle errors gracefully”unverifiableWhich errors? Handled how? This line is where every unhandled exception in the demo comes from.
5”invite each other by email”inheritedThe fatal one for this edition. This project has no accounts and no email addresses — the join code was chosen precisely to avoid identity. The sentence came from a brief for a different design and nobody questioned it. Built correctly, at full price, in Week 5, for nobody.
6”would be nice to have”no priorityMoSCoW exists so Week 4 has a defensible cut list. “Nice to have” is a way of avoiding the decision.
No identifiers, no acceptance criteria, no Won’t-have list, no adaptation recordfatalNothing downstream can point at anything: the traceability matrix has nothing to trace, the plan nothing to reference, the test plan nothing to cover, the change log nothing to close. One missing convention breaks four documents.

Six lines, twelve distinct defects, and four documents that cannot be written correctly on top of them. That is why Week 2 is thirteen hours and not three.


C.3 — Acceptance Criteria

Handed to you in: Week 2, inside srs-template.md. Lands at: inline with every requirement in docs/requirements.md · Time: about ten minutes per requirement — if you write them first.

A requirement without acceptance criteria is an opinion. The criteria are the part that makes it checkable by someone who is not you, which is the entire point, because in Week 8 someone who is not you decides whether you built it.

House form: Given a specific starting state / When a named actor does one specific thing / Then one observable result. Every Must and Should needs at least two — the happy path, and one thing going wrong.

Acceptance criteria — template

**Acceptance criteria for FR-<AREA>-<nn>:**

- AC1  Given <a specific starting state, including the data that exists>,
       when <one named actor performs one specific action>,
       then <one observable result — with a number if there is one>.

- AC2  Given <the input is bad / the dependency is down / the list is empty>,
       when <the same trigger>,
       then <the defined behavior, as something a stranger can see>.

- AC3  Given <a boundary: zero, one, exactly-at-the-threshold, maximum>,
       when <trigger>, then <result>.

Acceptance criteria — worked (PantryPilot)

For FR-EXP-02 — Expiring-soon view:

  • AC1 Given a seven-day window and a pantry containing items expiring in 2, 6, and 20 days, when a member opens the expiring-soon view, then exactly the 2-day and 6-day items are listed, in that order.
  • AC2 Given a pantry with no items expiring inside the window, when a member opens the view, then the system shows an explicit empty state naming the next action, not a blank screen.
  • AC3 Given an item whose expiry date is today, when a member opens the view, then that item is listed first. (Boundary. Added after DEF-014.)
  • AC4 Given a pantry containing four items with no printed expiry date, when a member opens the view, then those items do not appear in the list and the view states “4 items have no date” with a link to the full pantry. (The visible half of ADR 0004.)

For FR-ACC-01 — Join a household with a code, where the interesting criteria are all failures:

  • AC1 Given a valid, current join code, when a person submits it, then they receive a session bound to exactly one household and see that household’s pantry.
  • AC2 Given an invalid code, when a person submits it, then the system returns a single generic error that does not reveal whether the code exists, and the attempt counts against the NFR-SEC-02 rate limit.
  • AC3 Given ten failed attempts from one client inside an hour, when an eleventh is submitted, then it is refused with the same generic message and no further processing occurs.
  • AC4 Given a session older than fourteen days, when the person opens the application, then they are returned to the join screen with the household’s pantry data intact.

Why this one works:

  • AC2 on FR-ACC-01 decides a real security question — do not confirm the code exists — in Week 2, for free, instead of in Week 6 under pressure.
  • AC4 on FR-EXP-02 makes a design decision user-visible and checkable. Excluding undated items is defensible; excluding them silently is a defect nobody would find until a user did.
  • AC3 on FR-EXP-02 carries a note saying it was added after a defect. That is a document being maintained, and a grader reads it as evidence that the specification and the code stayed in the same universe.
  • Every criterion becomes a test name in Week 6, nearly verbatim. Criteria written first take ten minutes; criteria written afterward take an hour, because you will unconsciously write them to describe whatever you happened to build.

Acceptance criteria — bad, and what it costs

Acceptance criteria:
- The feature works correctly.
- Users can successfully add items.
- Performance is acceptable.
- The join code is secure.
- Tested and no bugs.
#What is wrongWhat it costs
1”Works correctly” restates the requirement’s name with an adverb.Fails the stranger test outright. In Week 6 you invent the criteria in order to write the test, and you invent them to match code you already wrote. That is a photograph of the implementation, not verification.
2”Successfully add items” — no starting state, no data, no observable result.Passes trivially on your machine with your seed data. Fails on the grader’s clean clone, because nothing said the item had to be visible to the other housemates.
3”Performance is acceptable.”Belongs in a non-functional requirement with a number. Here it is a placeholder for a conversation nobody had.
4”The join code is secure.”Secure against what? Guessing? Log leakage? Reuse after fourteen days? Three separate obligations collapsed into an adjective — and this is the one that ships an unlimited-attempt login.
5”Tested and no bugs” — a claim about the future by the person who benefits from it.Your own defect log will publicly contradict it in Week 6. Zero known defects on a real project means you did not look.
No failure criterion anywhere.Every line is a happy path. You will ship a system that works exactly once — in the demo, on your machine, with the data you seeded — and the first bad input in front of an audience takes it down.

C.4 — The Architecture Decision Record

Handed to you in: Week 3 — adr-one-page.md in the Chapter 3 companion code, with worked ADOPT and DEVIATE records. Lands at: docs/adr/NNNN-kebab-case-title.md, indexed from docs/adr/README.md · Hat: architect · Time: about 30 minutes each. You write three or four this term, not eight.

An ADR captures one decision, the forces that produced it, and what it cost. The format follows Michael Nygard’s 2011 write-up, Documenting Architecture Decisions; a community collection lives at https://adr.github.io/.

Two rules matter more than the format. An ADR is immutable once accepted — if the decision changes you write a new record and mark the old one Superseded by ADR NNNN. You never delete one. And an ADR with no negative consequences is not an ADR. If you cannot name the cost, you did not decide; you agreed with a suggestion.

This edition adds one field: Adopt / deviate. Adopting a brief’s suggested stack is a legitimate decision, and you still have to write the record in your own words with your own reasons. Deviating with a reason gains marks. Deviating by default loses them.

ADR — template

# ADR NNNN — <a short noun phrase naming the DECISION, not the technology>

- **Status:** Proposed | Accepted | Superseded by ADR NNNN
- **Date:** YYYY-MM-DD  **Decider:** <you>  **Brief:** <#n, or "own project">
- **Requirements affected:** <FR-…, NFR-… from docs/requirements.md>
- **Adopt / deviate:** ADOPT the brief's suggestion · DEVIATE from it · N/A

## Context
Two short paragraphs, maximum. The forces, not the answer. What in YOUR adapted
requirements pushes on this? What have you actually shipped with before? How many
hours are left, and what does this cost out of them? A stranger should be able to
predict your decision from this section alone.

## Options considered
| Option | Have I shipped with it? | The one fact that decided it |
Two real options is the bar. One you were never going to take is a decoy.

## Decision
One paragraph. Active voice, present tense: "I will …". Name the thing precisely.

## Consequences
**Positive** — what gets easier, tied to a requirement identifier.
**Negative** — what gets harder, the new thing you must learn and the hours you
budgeted for it in docs/plan.md, and the mitigation with its cost.

## Revisit trigger    the measurable event that would make you supersede this. A number.
## Verification       | Claim | Source (the vendor's own page) | Checked on |

ADR — worked (PantryPilot)

docs/adr/0004-represent-items-with-no-printed-expiry-date.md

Status: Accepted · Date: 2026-09-24 · Decider: M. Alvarez · Brief: Fast-Start #1 Requirements affected: FR-EXP-02, FR-INV-01, FR-INV-03 · Adopt / deviate: DEVIATE · Related: ADR 0001 (stack)

Context. FR-EXP-02 is the requirement this project exists for, and it assumes every item has an expiry date. Brief #1 says plainly that this is the genuinely hard part, and the Week-2 interview confirmed it: I photographed our own shelves and 31 of 74 items had no printed date at all — rice, flour, oil, a bag of onions. The brief’s drafted FR-INV-01 makes expiry required. That would force S. Okafor to type a guess into the one field the whole product is about.

I have about 6.6 hours in the expiry work package and I have never made this decision before. Whatever I choose has to be testable against a committed fixture and explainable in one sentence to somebody standing at a shelf.

Options considered

OptionHave I shipped with it?The one fact that decided it
Require an expiry date on every item (the brief’s assumption)yesPuts a fabricated number into the field the expiring-soon view trusts. A wrong date is worse than no date, because the user believes it
Nullable date; undated items are excluded from the expiring-soon view and counted on ityesOne nullable column, one filter, one count. NULL means “no date printed,” which is a fact rather than a guess
Estimated shelf life by category, defaulted on entrynoA category table, a default policy, and a user who cannot tell an estimate from a fact. About 6 h and a duty of care I cannot meet

Decision. I will deviate from the brief. expires_on is nullable, and NULL means no expiry date is printed on this item — never unknown, never far future. Undated items appear in the pantry list (FR-INV-03) and are excluded from the expiring-soon window query (FR-EXP-02), which displays a count of them with a link to the full list. Nothing is silently invisible.

Consequences. PositiveFR-EXP-02 becomes one query with one WHERE expires_on IS NOT NULL clause, and its edge case is a fixture row rather than a policy argument; FR-INV-01 gets simpler, because an optional expiry field makes the fastest path through the form also the honest one. Negative:

  • The expiring-soon view is silent about 40% of a real pantry unless the reader notices the count line. Genuine, permanent, user-visible — and disclosed in README.md under what the system does not do.
  • The count line is interface that no requirement asked for until this record existed. Budgeted 0.75 h inside T-3.3.
  • A later “best by” versus “use by” distinction cannot be layered on this column; it needs a superseding record and a migration.

Revisit trigger. If more than 30% of items entered during the Week-6 trial are undated and a user reports missing something that spoiled, the count line is demonstrably not enough. Also revisit if any requirement ever needs a time of day on an expiry.

Verification

ClaimSourceChecked on
The chosen database’s date type stores no time componentthe database’s own type documentation2026-09-24
Nullable columns are supported by the migration tool, forward-onlythe tool’s own documentation2026-09-24

Why this one works:

  • The context is written so a stranger can predict the decision before reaching it. That is the test for a context section — and it contains a number the student went and got: 31 of 74 items, photographed. That single measurement is what turns an opinion into a decision.
  • Three real options, one of which is the brief’s, each with the concrete fact that killed it. Not a slogan.
  • The negative consequences are specific, priced, and one is permanent and user-visible — disclosed in the README rather than hidden.
  • The revisit trigger is a percentage plus a condition, not a mood.

ADR — bad, and what it costs

# ADR 4 — Use PostgreSQL

Status: Accepted

## Context
We need a database.

## Decision
We will use PostgreSQL because it is the most popular and powerful open-source
database and it is what most companies use. It is also very scalable.

## Consequences
This will make the app robust and scalable and easy to maintain.
#What is wrongWhat it costs
1The title names the technology, not the decision.docs/adr/ becomes a list of products instead of a list of choices. Nobody can find the record that answers “why does storage work this way?“
2”We need a database” is not a context.No forces, no requirement identifiers, no hours, no novelty assessment. A stranger cannot predict the decision because there was not one. This was a preference.
3No options considered, and no adopt/deviate field.You cannot demonstrate a decision without an alternative — and in this edition the missing field hides whether you thought about the brief’s suggestion at all.
4”Most popular,” “most companies,” “very scalable.”Three unsourced, undated claims, which is exactly what the verification table exists to prevent. On a project with four users, scalable is not a reason; it is a word that sounds like one.
5No negative consequences.The tell that no evaluation happened. Every choice costs something — a service to run locally, a database in CI, a restore procedure to write. Naming none of them means naming none of the risks either.
6No requirement identifiers, no revisit trigger, no date, no verification.The record cannot age. In Week 8 you cannot say what you knew when you decided, which is precisely what the design-review panel and the interviewer both ask.

You write three or four of these all term. This one takes as long as a real one and earns almost nothing on the decision-records line in Appendix D. Fifteen more minutes of honest thinking produces an artifact you could show an interviewer.


C.5 — The Work Plan and the Risk Register

Handed to you in: Week 3 — design-pack-template.md, Parts 2 and 3, in the Chapter 3 companion code. Lands at: docs/plan.md and docs/risk-register.md · Hat: project manager · Time: 4 h in Week 3, then twenty minutes every Monday.

One document decomposes the work; the other names what could stop it. In the long edition these are a whole week apart. Here they are written the same afternoon, because the plan’s verdict is what makes the risk register honest.

Two levels are enough: work packages (4–20 h) containing tasks (1–6 h). Under an hour is noise; over six hours means you do not understand it — split it or spike it. Every task carries four things or it is not a task: an identifier, a requirement trace (or - for enabling work), a three-point estimate, and a done-when somebody else could check.

The purpose of Part 2 is to find out on paper, in Week 3 that the plan is too big, while that is still an addition problem, rather than in Week 6 when it has become a subtraction problem with no solution.

Plan — template (the arithmetic that matters)

## Capacity — Weeks 4 to 8      | Week | course overhead | available for your project |
   4 design review + checkpoint    9    11        7 document, deploy, hand off   3   17
   5 build the core                3    17        8 deliver + presentation      11    9
   6 test, integrate, cut          3    17        TOTAL                         29   71

Available Weeks 4-8 .. 71.0 h    Declared buffer at 25% .. 17.8 h    PLANNABLE .. 53.2 h

## Work packages and tasks
| Task | Name | Req | O | M | P | E | Done when | Depends on |
E = (O + 4M + P) / 6.    P / O over 4 means: split it or spike it.

## Calibration
calibration factor = hours actually spent / hours expected, over Weeks 1-3
raw total ___ h  x  factor ___  =  calibrated ___ h
VERDICT: fits with ___ h to spare  |  OVER by ___ h — cut today

## Scope decision   | Cut or deferred | Req ID | Was | Now | Why | Decided |
## Buffer ledger    | Date | Hours drawn | Which task overran | Why |
You may spend the buffer. You must record the draw. You may not silently refill it.

Plan — worked (PantryPilot)

WP-3 — Expiring-soon view · FR-EXP-02, NFR-PERF-01 · owner: me

TaskNameReqOMPEDone whenDepends on
T-3.1Expiry window query against the 200-item fixtureFR-EXP-021.01.53.01.7Returns exactly the 2-day and 6-day rows from the 2/6/20 fixture; undated rows excludedT-0.3
T-3.2The view: soonest first, expired markedFR-EXP-021.52.03.52.2AC1 and AC3 acceptance tests green on the fixtureT-3.1
T-3.3Empty state + undated count lineFR-EXP-020.50.751.50.8Empty pantry shows the empty-state copy naming the next action; AC2 and AC4 greenT-3.2
T-3.4pantryToday() boundary; no direct clock calls anywhere elseFR-EXP-020.751.01.751.1A test proves no module outside the boundary reads the system clockT-3.1
T-3.5p95 pass against NFR-PERF-01NFR-PERF-010.50.751.250.8p95 under 1.5 s over 20 throttled loads at 200 items, recorded with date and commitT-3.2
6.6

Roll-up, Weeks 4–8, in raw E hours. WP-1 join with a code (FR-ACC-01, NFR-SEC-02) 9.0 · WP-2 keep the pantry current (FR-INV-01, -03, -04) 12.0 · WP-3 expiring-soon view 6.6 · WP-4 edit an item (FR-INV-02, Should) 4.0 · WP-5 barcode entry (FR-SCAN-01, -02, Should) 14.0 · WP-6 verification 8.0 · WP-7 document, deploy, hand off 10.0 · WP-8 deliver 4.0. Raw total 67.6 h.

Calibration factor from docs/hours-log.csv, Weeks 1–3: 1.24×. Calibrated total: 83.8 h against 53.2 h plannable.

Verdict: OVER by 30.6 hours, in Week 3, on paper.

The scope decision

ActionItemReqsCalibrated h recoveredMoSCoWWhy
CutWP-5, barcode entry, entire packageFR-SCAN-01, FR-SCAN-0217.4Should → Won’tThe brief’s own cut list puts this second, and T-5.2’s spread came out O 2.0 / P 12.0 — a ratio of 6.0, far past the split-or-spike line. I do not understand this task, which is exactly why it is not close
CutWP-4, editing, down to delete-and-re-addFR-INV-025.0Should → Won’tWorse for the user, and the Won’t row in §7 says so plainly rather than pretending it was never wanted
CutConfigurable expiry window; fix it at seven daysFR-EXP-023.1unchangedA settings screen, its persistence, and its validation, for one number that nobody asked to change
Re-estimate downWP-1, after the Week-3 skeleton proved the session pathFR-ACC-013.7unchangedThe skeleton already carries a session end to end; three tasks re-estimated with the skeleton as the evidence
Re-estimate upWP-7, documentation−2.5unchangedHonest correction upward. Week 7 is bigger than I first wrote, and it carries a large share of the rubric

Recovered: 17.4 + 5.0 + 3.1 + 3.7 − 2.5 = 26.7 h. New calibrated total: 83.8 − 26.7 = 57.1 h against 53.2 h plannable.

That plan draws 3.9 of the 17.8 hours of buffer, declared here, in writing, in Week 3 — not spent silently in Week 7. Buffer remaining: 13.9 h. Signed: M. Alvarez, 2026-09-19.

Coach’s Note — Thirteen point nine hours of buffer across five weeks is not comfortable, and it is not supposed to be. That is roughly one bad week. It is also exactly why the Monday review is twenty non-negotiable minutes for the rest of the term. A plan that ends its arithmetic with a large surplus has almost always hidden work rather than cut it.

Coach’s Note — Look at the two re-estimate rows. One goes down, one goes up. The upward correction on documentation is the line that tells a grader this is an honest document rather than an hours-recovery exercise — and the fastest way to lose that credibility is a scope decision where every number happens to move in your favor.

Risk register — worked (PantryPilot)

Scales, so your numbers mean the same thing in Week 7 as they did in Week 3. Likelihood: 1 rare · 2 unlikely · 3 even odds · 4 likely · 5 near certain, before you do anything about it. Impact, scored in hours you would lose: 1 = under 2 h · 2 = 2–5 h · 3 = 5–12 h · 4 = 12–25 h · 5 = over 25 h or it cannot ship. Exposure = L × I. Response is one of avoid · mitigate · transfer · accept. Six rows. Fewer than four means you have not looked.

R-01 — Because I have never deployed to this host, the Week-7 deployment may take far longer than the 4 h estimated, eating the documentation hours in the same week. · technical/novelty · L 4 · I 3 · E 12 · Trigger: the Week-3 skeleton deploy (T-0.6) takes more than 2 h. · Owner: me. · Response: avoid — deploy the walking skeleton in Week 3, not Week 7, so the unknown is retired four weeks early. · Contingency: the alternative host in ADR 0003. · Status: retired 2026-09-19 — the skeleton deploy took 1.5 h. The unknown is now known.

R-02 — Because the expiry model has no policy for items with no printed date, the Week-6 acceptance pass may find that a third of a real pantry cannot be represented, and FR-EXP-02 would be untestable against real data. · technical · L 3 · I 4 · E 12 · Trigger: any fixture row that needs a null expiry, or any interview photo showing an undated item. · Owner: me. · Response: mitigate by Week 4 — decide the policy in an ADR and put it in the data model before construction (T-3.1, T-3.3). · Status: became an issue 2026-09-24 → ADR 0004. The trigger fired exactly as written, on 31 of 74 photographed items. Residual L 2, E 8, until the Week-6 trial.

R-05 — Because I am the only person who has ever run this deployment, if I lose the final week nobody can produce the release. · schedule/personal · L 2 · I 5 · E 10 · Trigger: any Monday review I do not complete. · Owner: me. · Response: transferdocs/runbook.md written in Week 7 to a standard a stranger can execute, and one classmate actually runs the deploy section end to end without my help. · Status: open.

Why these work:

  • Every row is cause → uncertain event → consequence. “The deploy might be hard” is not a risk. “Because I have never deployed here, Week 7 may overrun, and documentation loses the hours” is one you can act on. Every trigger is observable by somebody other than you: a deploy past two hours, a photograph, a missed Monday.
  • R-01 is retired with a date and evidence. A register where nothing ever changes status was not being reviewed.
  • R-02 became an issue and says so, with the decision it produced and the residual likelihood. That single row is the most credible thing in the document, because it proves the register was live.
  • R-05 covers the person, not the software. Bus factor is real on a solo project and almost nobody writes it down.

Plan and register — bad, and what it costs

## Work breakdown
1. Backend        — 25 hours
2. Frontend       — 25 hours
3. Testing        — 10 hours
4. Deployment     —  5 hours
5. Documentation  —  5 hours

## Risks
| Risk | Likelihood | Impact | Mitigation |
| Running out of time | High | High | Work harder |
| Technical difficulties | Medium | High | Research solutions |
#What is wrongWhat it costs
1”Backend” is not a deliverable; it is a neighborhood.You cannot estimate a neighborhood, so you guessed — look at the list, every number is round. That is the tell.
2Nothing traces to a requirement.When Week 4 tells you to cut, you will not know which requirements you just broke, so docs/traceability.csv becomes a lie and no change request can name what changed.
3No done-when, no capacity, no buffer, no calibration, no verdict.On a solo project nobody disagrees, so “done” drifts toward whatever you have finished — and there is nothing for the total to exceed, so the plan can never be shown to be too big. It still is. You find out in Week 6, with two weeks left.
4Testing is a phase at the end with 10 hours; documentation gets 5.Documentation carries a large share of the rubric in Appendix D. Budgeting five hours for it is a decision to lose those points, made in Week 3 without noticing you made it. Realistic is 10–12.
5All the invisible work is invisible.No seed data, no error handling, no accessibility pass, no secrets and configuration, no time for reading and fixing generated code. Conservatively 12–18 hours that exist and are not in the plan.
6Single-point estimates, so no spread, so no signal.Three-point estimates do not just produce better numbers — the spread tells you which tasks you do not understand. Nothing gets spiked, so the unknowns stay unknown until they detonate.
7”Work harder” is not a response; “running out of time” is not a risk.No cause, no observable trigger, no week, no cost. It cannot be detected, only discovered. And if the plan is 30 hours over, there are no 30 extra hours to find.
8Every risk is generic.Not one row mentions this project — no dependency, no novel technology, no broken week from the charter, no bus factor. A register that could belong to any project has told you nothing about yours.

C.6 — The Test Plan

Handed to you in: Week 6 — test-plan-lite.md and traceability-matrix.csv in the Chapter 6 companion code. Lands at: docs/test-plan.md, with the matrix at docs/traceability.csv · Hat: tester · Time: 2 h, on Monday of Week 6, before you know how the week is going.

Six sections instead of the long edition’s nine, three sentences per section instead of three paragraphs. Nothing professional is missing; the ceremony is.

The most valuable section is the one students leave out: out of scope. A plan that claims to test everything is a plan nobody believes. A plan that says plainly “I am not load-testing beyond five concurrent sessions, because there are four users and no requirement asserts more” is a plan a grader trusts — and trust is the currency of the verification line.

The second most valuable is exit criteria, because those are the numbers you will be held to in Week 8. Write them Monday. You may not edit them on Thursday so the build passes.

Test plan — template

# Test Plan — <Project Name>
**Version:** 1.0 · **Author:** <you> · **Date:** <YYYY-MM-DD> · **Baseline commit:** <sha>

1. Scope           the build (commit SHA or tag) + the requirement IDs covered.
                   "The system" is not a scope. Name your 3-4 Musts.
2. Out of scope    what is NOT under test. Each line gets a reason, not an apology.
3. Test levels   | Level | what it covers here | how it runs | roughly how many |
                   Unit · Integration (Tier 1 seams only) · Acceptance (one per Must)
4. Environment     runtime version · OS · storage + the one seed command · third-party
                   services real/sandbox/stubbed (stub by default) · fixture path · CI
5. Exit criteria   numbers, not adjectives. Written MONDAY. Not editable on Thursday.
6. Approval        "I am accountable for the exit criteria above." Signed, dated.

Test plan — worked (PantryPilot)

§1 Scope. Covers FR-ACC-01, FR-INV-01, FR-INV-03, FR-INV-04, FR-EXP-02 and NFR-PERF-01, NFR-SEC-01, NFR-SEC-02, NFR-ACC-01 — five functional and four non-functional requirements — at commit 3f9a0c1, tagged v0.8.0. FR-SCAN-01, FR-SCAN-02, and FR-INV-02 were withdrawn in the Week-3 scope decision, are not in the build, and are not covered.

§2 Out of scope, and why.

Not testedWhy not
Concurrent load beyond 5 sessionsFour real users; no requirement asserts more. NFR-PERF-01 is a single-session latency target and is tested as one
Browsers beyond the one named in the READMEThe requirement names one tested target and one best-effort. Testing a third would be testing something nobody promised
Screen-reader announcement qualityNFR-ACC-01 is a keyboard-and-contrast requirement, scoped that way in Week 2 because I cannot test screen readers competently. Stated as a limitation in README.md rather than claimed and unverified
Database restore under corruptionThe runbook documents restore, and I have executed it once from a clean dump. Corruption recovery is out of scope and named as a known gap in docs/handoff.md

§3 Test levels

LevelWhat it coversHow it runsCount
Unitexpiry window arithmetic, pantryToday(), quantity/unit validation, join-code hashingscript/test34
Integrationthe repository module against a real ephemeral database; the rate limiterscript/test6
Acceptanceone per acceptance criterion on every Must; three run manually — keyboard, contrast, throttled p95 — recorded with date and testerscript/test + script/smoke14 automated, 3 manual

§4 Environment. Runtime pinned in .tool-versions; database version pinned in the committed compose file; seed data generated by script/setup from a deterministic committed fixture of 200 items, 31 of them undated; no third-party service is called by any automated test; runs locally and in CI on every push.

§5 Exit criteria — verification is done when:

  • 100% of Must requirements have at least one passing acceptance test, listed by ID in docs/traceability.csv.
  • 0 open defects at severity S1 or S2.
  • Every open S3/S4 defect appears in docs/defect-log.md with a stated workaround.
  • Every defect fixed this week names a regression test verified to fail on the pre-fix commit.
  • The full suite runs from script/test and passes in CI on a clean clone.
  • NFR-PERF-01 measured within the last 7 days at the current commit; the keyboard and contrast passes (NFR-ACC-01) completed with date and tester; the secret scan over full history returns zero findings.

Signed: M. Alvarez, 2026-10-05.

Why this one works:

  • The out-of-scope table is longer than most students’ entire plan, and every row gives a reason rather than an apology. Two rows admit a limit of the author’s competence and route it into the README and the handoff guide instead of hiding it.
  • The scope names a commit and a tag. A test plan against “the code” is a plan against a moving target.
  • The environment makes the suite reproducible by somebody else: pinned versions, a deterministic fixture, and an explicit statement that nothing automated touches the network.
  • The regression criterion requires the test to fail on the old code. Without that check, a regression test is a test that would have passed anyway.
  • Every exit criterion is a number or a yes/no. None is “adequate coverage.”

Test plan — bad, and what it costs

# Test Plan
We will test the application thoroughly to make sure everything works.
Types of testing: unit testing, integration testing, user testing.
We will aim for high code coverage and fix any bugs that we find.
Testing will be done in week 6.
#What is wrongWhat it costs
1”Thoroughly” and “everything.”Fails the number test in the first sentence. Nothing can be shown to be complete, so the verification line has nothing to award against.
2No build under test — no commit, no tag — and no requirement identifiers.In Week 8 you cannot prove the passing run corresponds to the code you submitted, and you cannot answer the question a grader asks first: which requirements are verified, and which are not?
3No out-of-scope section.Implies you tested everything, which is false, and a false claim in a signed document is worse than an admitted gap. Reviewers go looking for the gap, find it, and then doubt everything else.
4”High code coverage” as a target.Coverage is a diagnostic, not a goal. Chasing a percentage produces tests that execute lines without asserting behavior. State requirement coverage and let line coverage be a hint about where you have not looked.
5No environment and no test data.The suite passes on your laptop, and that is all anybody will ever be able to say about it.
6No exit criteria, and “fix any bugs that we find.”Testing is “done” when you run out of week, not when the software is verified — and with no severity, priority, or triage, that sentence is exactly where a missing defect log comes from.

C.7 — The Defect Report and the Defect Log

Handed to you in: Week 6 — cut-kit.md §1 in the Chapter 6 companion code. Lands at: docs/defect-log.md · Hat: tester · Time: eight minutes per defect. Every time.

A defect report has seven required fields: ID and one-line title; environment; steps to reproduce; expected versus actual; evidence; severity and priority, assigned separately; and traceability plus status. A report missing a field is a note to yourself.

Two ladders, and the distinction between them is one of the genuinely professional habits this course teaches. Severity is a fact about the product — S1 data loss, security exposure, or it will not run; S2 a Must not met with no workaround; S3 partly met or a real workaround exists; S4 cosmetic. Priority is a decision about your remaining hours — P1 fix now; P2 fix this week; P3 fix if hours remain, otherwise ship as a known defect; P4 will not fix, recorded with the reason. In industry two different people set them. Here it is the same person wearing two hats, so write down which hat you were wearing — an S2/P3 is a decision you will defend in Week 8.

Defect report — template

ID:          DEF-nnn
Title:       <one line: the symptom, in the observable world>
Reported:    <YYYY-MM-DD> by <who>
Environment: commit <sha>, <OS>, <runtime version>, seed data set <name>
Requirement: <FR-…> (<AC id>)      Test case: <TC-nnn>
Steps to reproduce:   1. <numbered, minimal, from a known starting state> 2. 3.
Expected:    <what the acceptance criterion says, QUOTED>
Actual:      <what happened, observably>
Evidence:    <failing test name, log excerpt, source location, screenshot path>
Severity:    <S#> — <one line about the product>   (Hat: tester)
Priority:    <P#> — <one line about the calendar>  (Hat: project manager)  Est: <n> h
Status:      <Open | Fixed — commit <sha> | Deferred | Won't fix — reason>
             Regression test <TC-nnn>, verified to FAIL against the pre-fix commit.
Owner:       <you>

And the log table, pasted into docs/defect-log.md: | ID | Title | Req | Sev | Pri | Est | Found by | Status | Fix commit | Regression test |.

Defect report — worked (PantryPilot)

ID:          DEF-009
Title:       Join-code entry accepts unlimited attempts; the rate limit never fires
Reported:    2026-10-07 by M. Alvarez
Environment: commit 3f9a0c1, macOS 15, Node runtime pinned in .tool-versions,
             seed set `small`, one private browser session
Requirement: NFR-SEC-02 (AC3)      Test case: TC-018

Steps to reproduce:
  1. script/setup, then script/test -- --seed small.
  2. Open the join screen in a private window.
  3. Submit the wrong six-character code eleven times inside one minute.

Expected: NFR-SEC-02 AC3 — "given ten failed attempts from one client inside an
          hour, when an eleventh is submitted, then it is refused with the same
          generic message and no further processing occurs."
Actual:   All eleven are processed and return the same generic error. TC-018
          asserts a refusal on attempt eleven and fails.
Evidence: TC-018 fails. Log shows eleven `join.attempt` lines and zero
          `join.rate_limited` lines. The limiter is registered AFTER the join
          route in src/app.js line 41, so it never runs for that path.

Severity: S1 — a six-character code with no attempt limit is guessable by a
          script, and the code is the only thing between a stranger and a
          household's data. (Hat: tester.)
Priority: P1 — a Must NFR on the demo path; the fix is a two-line reordering.
          Estimate ~0.5 h. (Hat: project manager.)
Status:   Fixed — commit 7b21d40 (limiter registered before the route).
          Regression test TC-018b, verified to FAIL against 3f9a0c1.
Owner:    M. Alvarez

The defect log at the Week-7 release — paste this table into docs/defect-log.md:

IDTitleReqSevPriEstFound byStatusFix commitRegression test
DEF-004Undated items appear in the expiring-soon listFR-EXP-02S2P10.5acceptance testFixeda8c1f30TC-011b
DEF-009Join-code entry not rate limitedNFR-SEC-02S1P10.5test plan §3Fixed7b21d40TC-018b
DEF-014Items expiring today dropped from the viewFR-EXP-02S2P11.0acceptance testFixede4f5a6bTC-021b
DEF-016Keyboard focus lost after marking an item consumedNFR-ACC-01S2P21.0manual keyboard passFixed9a7c204TC-027
DEF-019Quantity of 0 saved instead of rejectedFR-INV-01S3P20.5four-pass self-reviewFixedc0d5f19TC-014b
DEF-022Pantry list unpaginated; slow past ~800 itemsNFR-PERF-01S3P32.5p95 passOpen — shipping
DEF-025Empty-state copy says “no items” on a filtered viewFR-EXP-02S4P40.3self-reviewWon’t fix — recorded

DEF-022, the one that ships. S3/P3. Workaround: use the expiring-soon view, which is filtered and fast. The pantry that triggers it is roughly four times the largest real pantry measured in the Week-6 trial. It appears in README.md under known limitations, in CHANGELOG.md under known issues, and in docs/handoff.md §4 with an effort estimate for the next owner.

Why this one works:

  • The steps start from a known state produced by a committed command, so somebody else can run them — and expected is quoted from the acceptance criterion, not paraphrased from memory. That is the payoff of writing criteria in Week 2.
  • The evidence names a failing test, a log absence, and a source location. Three kinds of evidence, none of which is “trust me.”
  • Severity and priority carry separate reasoning and name which hat assigned which. The regression test was verified to fail against the pre-fix commit; one that passes on the broken code is decoration.
  • One defect ships. It is severity-rated, has a workaround, appears in three documents, and carries an estimate. That is far stronger than a log claiming zero open defects.

Defect report — bad, and what it costs

Bug: expiry not working

The expiring soon page isn't showing everything it should. Maybe a date thing?
Will look into it.

Fixed it.
#What is wrongWhat it costs
1No ID.Nothing can reference it — not the change log, not the requirements, not the retrospective, not the handoff guide. One missing identifier breaks four documents.
2No environment and no steps to reproduce.Which build, which data, which machine? A defect that is not reproducible is not fixed — it is unobserved — and future-you, in three weeks, cannot confirm the fix. Neither can a grader.
3No expected versus actual.”Isn’t showing everything it should” hides the whole question of what it should show. That is what the acceptance criterion was for, and it is not cited.
4”Maybe a date thing?”A guess where evidence belongs. Guesses get fixed by changing things until the symptom stops, which is how you get a second defect.
5No severity, no priority, no requirement, no test case.Nothing can be triaged, and nothing is traceable in either direction. With fifteen of these you fix whichever annoyed you most recently rather than whichever one is S1 — and Week 6 has room for perhaps six fixes.
6”Fixed it.” No commit, no regression test.No proof it was fixed and nothing stopping it from coming back. Defects that come back always come back in the last week, because that is the week you change the most code fastest.

C.8 — The Hours Log

Handed to you in: Week 1 (start the file) — Chapter 5 ships a realistic sample as hours-log.csv and the report script as hours_report.py, which runs the same honesty checks a grader runs. Lands at: docs/hours-log.csv · Every hat · Time: ninety seconds per session, before you close the laptop.

The smallest document in the kit and the one that does the most work. It is the evidence behind your calibration factor in Week 3, your burn-down every Monday, your estimate-versus-actual analysis in Week 8, and — quietly — the credibility of everything else. A grader who believes your hours log tends to believe your defect log.

It is also the only document here that cannot be reconstructed, and the report script will say so out loud.

Hours log — template (the schema is the whole document)

date,start,end,hours,phase,hat,task,estimate_hours,blocked_hours,notes,ai_assisted
2026-09-24,19:00,21:15,2.25,design,architect,ADR 0004 no-date policy,1.50,0.00,photographed the shelves - 31 of 74 undated,no
ColumnWhat goes in it
date · start · endReal clock times. They are what stop you rounding.
hoursDecimal, to the quarter hour. Not “about 3”.
phaseinception · requirements · design · planning · construction · verification · documentation · transition
hatproduct-owner · analyst · architect · project-manager · developer · tester · writer · release-engineer
taskThe task ID from docs/plan.md where there is one, and a specific description always.
estimate_hoursWritten before you start. Blank here means no calibration factor later — and in eight weeks this is the only calibration data you will ever get.
blocked_hoursHours inside the session where you were stuck on something outside your control.
notesOne clause. Especially: why it took longer than you thought.
ai_assistedyes or no. This edition’s column, and it must agree with docs/ai-usage.md.

Run python3 script/hours-report docs/hours-log.csv for the weekly rollup, the phase mix, the burn against 160, the calibration factor, and the honesty checks.

Hours log — worked (PantryPilot)

Six consecutive rows from Week 3, when the plan was written and the arithmetic went bad.

2026-09-17,18:45,21:30,2.75,design,architect,technical spec 1-5; named the error cases,2.00,0.00,,yes
2026-09-18,19:00,22:15,3.25,planning,project-manager,WBS to task level WP-1..WP-8,2.50,0.00,remembered seed data and the a11y pass - added two tasks,no
2026-09-19,09:00,11:00,2.00,planning,project-manager,three-point estimates; calibration from weeks 1-3,1.50,0.00,1.24x - worse than I expected and I checked it twice,no
2026-09-19,13:00,15:15,2.25,planning,project-manager,capacity + verdict: 83.8 calibrated vs 53.2 plannable,1.50,0.00,over by 30.6. bad morning,no
2026-09-19,15:30,17:15,1.75,planning,project-manager,scope decision - cut WP-5 barcode and WP-4 edit,1.00,0.00,argued with myself about barcode for 35m before the spread settled it,no
2026-09-20,10:00,10:45,0.75,planning,project-manager,risk register 6 rows with triggers; re-baselined plan v1.1,0.75,0.00,,no

Six sessions, 12.75 hours against 9.25 estimated — a local ratio of 1.38, which is what pulled the term-to-date calibration factor to 1.24.

Why this one works:

  • The hours are not round. 2.75, 3.25, 0.75. Real work is not round, and a log of whole numbers is the first thing a reviewer notices.
  • estimate_hours is filled on every row, which is the only reason a calibration factor exists. Fill that column after the fact and the number it produces is a lie you will plan on.
  • The notes explain the overruns specifically: “remembered seed data and the a11y pass,” “argued with myself for 35m.” Those clauses are the raw material of the Week-8 retrospective and they cannot be recovered later.
  • A 0.75-hour session appears. Students routinely fail to log short sessions, and short sessions are a sixth of a term.
  • ai_assisted is yes on exactly one row, and that row has a matching entry in docs/ai-usage.md dated the same day.

Hours log — bad, and what it costs

date,hours,description
2026-09-05,3,worked on project
2026-09-12,4,worked on project
2026-09-26,12,catching up
2026-10-10,10,catching up
#What is wrongWhat it costs
1Every value is a whole number, and two days run to 12 and 10 hours.The report prints both: real work is not round, and any day over 10 hours is a backfill tell. One twelve-hour day is possible; two clustered after silences is a reconstruction.
2”Worked on project” twice.Flagged for repetition, and it destroys the log’s only real value — you cannot tell what took the time, so you learn nothing about your own estimating.
3No estimate_hours.No calibration factor is computable, so the Week-3 plan rests on raw estimates from a person who has never estimated this work. In this course that is the difference between finding the overrun in Week 3 and finding it in Week 6.
4No phase, no hat, no ai_assisted.You cannot answer the question the retrospective asks — where did 160 hours actually go? — and the disclosure column silently disagrees with docs/ai-usage.md.
5A two-week silence, then “catching up.”Either work went unlogged, in which case the total is wrong and so is every number derived from it, or you did nothing for two weeks and the burn-down never showed it. Both are bad; the second is worse. In an eight-week course that silence is a quarter of the term.
6Four rows for six weeks.A real log has three to five rows a week. This was written in one sitting and it reads that way.

The cost is not just the hours-log line in Appendix D. It is that a grader who catches a reconstructed hours log reads your defect log, your test results, and your AI-usage log with a completely different set of eyes. “One who is faithful in a very little is also faithful in much, and one who is dishonest in a very little is also dishonest in much” (Luke 16:10, ESV). Your hours log is the very little.


C.9 — The README

Handed to you in: Week 7 — readme-template.md in the Chapter 7 companion code, alongside the clean-machine protocol. Lands at: README.md at the repository root · Hat: technical writer · Time: 3 h, and it is the highest-leverage block in Week 7.

Nine sections, every one graded. The README has exactly one job: get a stranger from a URL to a running system, and then prove to them that it is running. If a grader cannot run your software they do not grade your architecture generously and dock you a little on the README. They grade what they can observe, and what they can observe is nothing.

The test is not “does it look complete.” The test is the clean-machine test: a person who is not you, on a machine that is not yours, following only this file, with the result recorded in docs/clean-machine-test.md.

README — template

# {{PROJECT NAME}}
> {{ONE SENTENCE: what it does and who it is for — what it does TODAY.}}
**Status:** {{Working / Partially working — say plainly what does not work yet}}
**Version:** {{v1.0.0}} · **Built by:** {{name}} · {{Course, term}}

1. What this is, who it is for, and WHAT IT DOES NOT DO  (rewrite the brief's non-goals)
2. Prerequisites   | requirement | version tested on | check it with | what breaks |
3. Install         one path, not two. + the last line of successful output, + N minutes
4. Configure       | variable | required? | what it is | where you get it | default |
5. Run             + exact success line, THEN "Verify it actually works": one concrete
                   action a stranger can take and the result they should get
6. Test            + expected counts and duration; known skips and why
7. Project layout  8. Troubleshooting (exact error text -> cause -> fix)  9. License

README — worked (PantryPilot), sections 1–6 condensed

PantryPilot — Know what is in the kitchen and what is about to go bad.

Status: Working. Editing an item is not implemented (FR-INV-02, cut in Week 3) — delete and re-add. The pantry list is unpaginated and slows past roughly 800 items (DEF-022, workaround below). Version: v1.0.0 · Built by: M. Alvarez · Accelerated Capstone, Autumn 2026

1. What this is. A small web application for one shared apartment: four people who buy groceries together and throw food away because nobody remembers what is at the back of the shelf. It replaces a whiteboard. One person adds items as they are unpacked; everybody sees what expires this week, soonest first; anyone marks something consumed. There are no accounts — you join a household with a six-character code, and the session lasts fourteen days.

What it does not do: no prices or spending, no nutrition or allergen data, no mobile application, and no expiry tracking for items with no printed date — those are listed in the pantry and counted on the expiring-soon view, but they never trigger a warning (ADR 0004). Each of those was refused deliberately and the reasons are in docs/requirements.md §7.

2. Prerequisites

RequirementBuilt and tested onCheck it withWhat breaks without it
Node runtimethe version pinned in .tool-versionsnode --versionSetup fails at dependency resolution
PostgreSQLthe version pinned in the committed compose filepsql --versionMigrations fail with role does not exist
Container runtimeoptional, for the local database onlydocker --versionPoint DATABASE_URL at any reachable PostgreSQL instead

3–5. Install, configure, run. One path, not two:

git clone https://github.com/<owner>/pantrypilot.git && cd pantrypilot
script/setup                 # ends: migrations: 6 applied, 0 pending — seeded 200 items, join code DEMO42
cp .env.example .env         # SESSION_SECRET required; generate with: openssl rand -hex 32
script/start                 # ends: pantrypilot listening on http://localhost:3000

About 4 minutes on a first run, most of it the database image.

Verify it actually works. Open that URL, enter the join code DEMO42 printed by script/setup, then add an item named milk, quantity 1, unit carton, expiring two days from today. Open Expiring soon. milk must be at the top of that list, and the heading must read 31 items have no date. If the count is missing, the seed did not run — see Troubleshooting.

6. Test. script/test — expected: 54 tests, 54 passing, 0 skipped, about 19 seconds. Three acceptance checks are manual (keyboard operability, contrast, throttled p95) and are recorded in docs/test-results.md with dates.

Why this one works:

  • The status line says what does not work, in the first three lines, with identifiers. Every reader trusts the rest of the document more because of it.
  • Section 5 contains a specific verification action with a specific expected result — including the undated count, which is the one number that proves the seed and the expiry policy both ran. “It started” is not “it works,” and this is the only part of a README that can tell the difference.
  • Prerequisites say what breaks without each one, including the exact error text for the common failure. A stranger who hits role does not exist finds their answer without opening an issue.
  • Version pinning points at files in the repository instead of restating numbers that will drift, and the join code is obviously a demo value obviously printed by the seed.

README — bad, and what it costs

# PantryPilot

A full-stack web application for managing your pantry, built with modern
technologies for my senior capstone project.

## Features
- Add and manage items
- Expiration tracking
- Barcode scanning
- Modern responsive UI

## Setup
1. Clone the repo
2. Install dependencies
3. Set up the database
4. Add your API keys to .env
5. Run the app
#What is wrongWhat it costs
1A feature list where the install instructions should be.The feature list is for a reader deciding whether to care. A grader has already decided. What they need is the next command, and it is not there.
2”Install dependencies” is not a command, and there is no expected output anywhere.Every step is a category. A stranger cannot follow one of these five lines without guessing — and when step 5 fails they have no idea which earlier step actually worked. This is the number-one cause of a failed clean-machine test.
3”Add your API keys to .env” — which keys, called what, from where, and is it usable without them?The grader with no key concludes the software cannot be run. It probably could have been.
4No prerequisites and no versions.It works on your machine because your machine already has the right runtime. On theirs it fails at a version boundary with an error naming nothing in this document.
5”Barcode scanning” is listed — and it was cut in the Week-3 scope decision.The README claims something the software does not do. A grader who tries it stops trusting the whole document, and your retrospective’s honesty is undermined before it is read.
6No status, no known limitations, and no verification step.Implies everything works; two minutes of use disproves it. And even if it starts, nothing says what success looks like — the reader clicks around, sees an empty screen because they did not seed, and concludes it is broken.

This is the file every rubric line is read through. A thin README does not cost you the README line. It puts every other line at risk, because the grader could not observe the thing those lines describe.


C.10 — The Runbook

Handed to you in: Week 7 — no starter file; the seven-section shape is taught in Chapter 7 §7.4 and templated here. Lands at: docs/runbook.md · Hat: release engineer · Time: 2.5 h, plus the time to break your own system on purpose.

The README is for the person who wants to run your software. The runbook is for the person who has to operate it — possibly at an inconvenient hour, possibly without you, possibly while it is on fire. Different reader, different document, different tone: procedures, not explanations.

In this edition the runbook absorbs the deployment record, so there is no separate deployment document.

Runbook — template

docs/runbook.md
  1. Where it runs          host, URL, region, which account owns it
  2. Deploy                 numbered commands, always FROM THE TAG, never a working copy
  3. Start / stop / restart + the health check, with healthy AND unhealthy output
  4. Normal readings        startup time, typical response time, harmless noisy log lines
  5. Failure catalogue      >= 4 entries, mined from docs/defect-log.md
  6. Rollback               numbered, rehearsed once, with a wall-clock time next to it
  7. Escalation + last verified (date, name, and UNVERIFIED on anything you did not run)

The rule that makes a runbook real: every command in it has been run by a human, on a machine that is not the author’s laptop, on a date recorded at the bottom of the file. Anything not verified on that date is marked UNVERIFIED in the text.

Runbook — worked (PantryPilot)

§3 Health check

curl -sS https://<host>/health

Healthy: {"status":"ok","db":"connected","migrations":"6/6"} — HTTP 200, under 500 ms. Unhealthy: HTTP 503 with {"status":"degraded","db":"unreachable"}, or no response at all. → §5.

§4 Normal readings. Cold start about 8 s — the free tier suspends when idle, and the first request after a suspend takes 10–20 s. This is expected, not a fault. Pantry list typically 180–350 ms warm at 200 items. seed: skipped (data present) on every boot is normal. Known limit: the pantry list is unpaginated (DEF-022) and gets slow past roughly 800 items; the list stays correct, it just takes several seconds.

§5 Failure catalogue — two of five:

F-01 — “Everything says no date” or the expiring-soon view is empty

  • Symptom: the expiring-soon view is empty and the count line reads a suspiciously large number.
  • First check: psql "$DATABASE_URL" -c "select count(*) from items where expires_on is null;"
  • Likely cause: a bulk import wrote empty strings instead of NULL, or the import skipped the date column entirely. NULL means no printed date and nothing else (ADR 0004).
  • Fix: re-run the import with the corrected column mapping; there is no in-place repair, because the system cannot distinguish a lost date from an absent one.
  • Verify: reload the view; an item expiring today appears first. Or script/test -- -t "expiry boundary".
  • If that does not work: the server date may be wrong. date -u on the host, then §7.

F-02 — Every request returns 500; /health reports db: unreachable

  • First check: pg_isready -h <host> -p 5432 — healthy is accepting connections.
  • Likely cause: the managed database suspended for inactivity, credentials rotated, or the allow-list changed.
  • Fix: resume the instance from the provider console; if credentials changed, update the secret in the host’s secret store and redeploy. Do not edit .env on the running instance — it is rebuilt on deploy and your change will vanish. Verify: /health returns 200 with db: connected, then add and delete one test item.
  • If that does not work: stop. Do not restore from a backup to fix a connectivity problem. §7.

§7 Escalation. Data loss suspected → stop, do not restart, do not restore. Take a dump of the current state first; a restore over live data destroys the evidence and possibly the only good copy. A secret may be exposed → rotate first, investigate second. Third-party outage → the system has no third-party dependency by design (FR-SCAN-01 was cut), so this cannot be the cause.

§7 Last verified. Executed end to end on 2026-10-16 by A. Reyes — a classmate, not the author — on Windows 11 + WSL2, against the deployed instance. Deploy, health check, rollback, and restore-into-a-scratch-database all run. Secret rotation UNVERIFIED — documented but not executed, because rotating the live secret would have signed out the demo household; the procedure was read aloud and checked by the same classmate.

Why this one works:

  • The health check has exact output for both healthy and unhealthy, so an operator can tell in three seconds which one they are looking at. And §4 describes normal, including a slow cold start that would otherwise be diagnosed as an incident — most runbooks describe failure and never describe normal, which makes every anomaly look like a fire.
  • Every failure block came from the defect log or a decision record; F-01 came from ADR 0004 and DEF-004. Catalogues written from imagination describe failures that never happen and miss the ones that do.
  • F-01’s fix says plainly that there is no in-place repair, which is the honest consequence of the nullable-date decision. Telling an operator that something is unrecoverable is worth more than a procedure that does not work.
  • It was executed by somebody else, on a different operating system, on a dated occasion — and the one unverified procedure is marked UNVERIFIED rather than quietly presented as tested.

Runbook — bad, and what it costs

# Runbook
## Deployment
Push to main and CI deploys automatically.
## Monitoring
Check the logs if something goes wrong.
## Backups
The database is backed up automatically by the hosting provider.
## Troubleshooting
Most issues can be resolved by restarting the application.
#What is wrongWhat it costs
1No health check.The operator’s first question — “is it alive?” — has no answer, and everything after it is guesswork.
2”Check the logs” with no path, no command, and no idea what to look for.Logs on a managed host are often not on disk. And nothing says what a normal log line looks like, so the operator cannot tell signal from noise.
3”Backed up automatically” — no verification, no retention, no restore command.A backup you have never restored is not a backup; it is a belief about a backup. The first restore attempt should not happen during an incident.
4”Restarting usually fixes it.”Sometimes true and catastrophic when it is not. Restarting during suspected data loss destroys the evidence and the recovery path. That is why the good version says stop in bold.
5No failure catalogue and no rollback.The failures that actually happen to this system are already written down in docs/defect-log.md. The information existed; nobody moved it. And a release you cannot roll back is a release you cannot safely make in Week 7.
6Deploys from main, not from the tag — and nothing here was ever executed.What shipped is whatever was on the branch at deploy time, which is not necessarily what you submitted. With no “last verified” line, the whole document is a set of assumptions about your own system, and assumptions are what a runbook exists to replace.

C.11 — The Handoff Guide

Handed to you in: Week 7 — handoff-template.md in the Chapter 7 companion code, with handoff_audit.py to check it. Lands at: docs/handoff.md · Hat: release engineer, then technical writer · Time: 3 h.

In this edition one file carries three jobs the long edition splits: the onboarding ramp for the next developer (§2), the custody transfer for the next owner (§3–§6), and the license inventory (§5). There is no separate onboarding document. Nine sections, and if one does not apply you write a line saying why — an empty section is information, a deleted section looks like an oversight.

Audience, stated at the top of the file: the engineer who inherits this project and cannot ask you a question. Every sentence in it is written against that constraint.

Handoff — template

# Handoff Guide — <Project Name>

0. Thirty-second orientation   what it is · who uses it · status + version · deployed
                               at · start here
1. Day one — get it running (target: under 30 min) | step | command | expected | if it fails |
2. Week one — the ramp that ends in a MERGED change: read the architecture §0 and the
   ADR index · vocabulary (2-3 domain words) · trace one request end to end ·
   feature-to-code map (five rows, real paths) · the starter issue you deliberately
   left undone · branch, test, pull request, CI, merge
3. Accounts, keys, and who pays  | thing | where | owned by TODAY | cost (dated) | transfer step |
4. Landmines (>= 5) + known defects deliberately shipped, with why each is still open
5. Licensing — out (your SPDX id) and in (every dependency's real obligation)
6. AI and volatile-vendor dependencies — or one line saying there are none
7. What only I know (bus-factor register) | knowledge | consequence | now closed by |
8. If nobody picks this up — write the deprecation notice now

Handoff — worked (PantryPilot)

§3 Accounts, keys, and who pays for what

ThingWhere it livesOwned by TODAYCost (measured 2026-10-16)Rotation / transfer step
Source repositorygithub.com/<owner>/pantrypilotM. Alvarez (personal account)free tierTransfer ownership in settings, then re-add the two CI secrets by hand — they do not transfer
Deploy targetthe host named in docs/runbook.md §1M. Alvarezfree tier as of the date above; verify with the vendor, tiers changeInvite the new owner, promote to owner, then remove me. Do not delete the service — the URL is in README.md
Databasethe managed instance named in docs/runbook.md §1M. Alvarezfree tier as of the date above; verify with the vendorTake a dump first (docs/runbook.md §2), transfer, restore, verify with /health
SESSION_SECREThost secret store onlynoneRotating it signs out every household. Do it in a quiet hour and say so in CHANGELOG.md
Domainnone — the host’s default URL is usednoneIf a custom domain is ever added it becomes the highest-risk row in this table

Every row above with a person’s name in it is a liability. The handoff is not finished until every one of those names changes. Prices and free tiers change; this table is dated, not permanent.

§6 AI and volatile-vendor dependencies. None. The cooking-suggestion feature (FR-REC-01) was never adopted, so this system makes no model calls, holds no provider key, and has no prompt files. Stated here in one line rather than left blank, so the successor does not go looking.

§7 What only I know — the bus-factor register

KnowledgeConsequence if lostNow closed by
NULL in expires_on means no printed date, never unknownA future import writes a far-future default and the expiring-soon view silently stops warning about anythingADR 0004; a NOT NULL-free column comment in the migration; docs/runbook.md F-01
Nothing outside pantryToday() may read the system clockOff-by-one expiry defects that pass every test written on one machine in one time zoneA test that fails the build; docs/architecture.md §4
The rate limiter must be registered before the join routeThe join code becomes brute-forceable — this was DEF-009A comment at the registration site; docs/test-plan.md §5; TC-018b
The 200-item fixture contains 31 undated items on purposeSomebody “fixes” the fixture, and the undated-count acceptance test loses its only dataHeader comment in the fixture; docs/test-plan.md §4

Why this one works:

  • The accounts table names a rotation order — new owner promoted before the old one is removed. Getting that backwards is how a handoff takes the system down.
  • Free-tier claims carry a measured date and an instruction to verify with the vendor, because this document will outlive the term.
  • §6 says “none” in a full sentence with a reason. The successor now knows the absence was a decision, not an omission.
  • The bus-factor register names the knowledge, the consequence, and — critically — where it is now closed, in the order the chapter teaches: make it impossible, make it visible, make it findable. Two of the four rows are closed by a failing test, which is the strongest form.

Handoff — bad, and what it costs

# Handoff

The code is well-commented and should be easy to understand. The architecture
follows standard patterns so any developer familiar with the stack can pick it up.

Everything is in the GitHub repo. My keys are in the .env file (not committed).
Let me know if you have questions — my email is in my GitHub profile.

Future work: add tests, improve the UI, maybe a mobile app.
#What is wrongWhat it costs
1”Well-commented and easy to understand” · “standard patterns.”Assertions about your own work by the person least able to judge them, naming nothing. No orientation, no vocabulary, no feature-to-code map — the successor starts at the repository root with no idea which of forty files matters.
2No accounts table.The single most expensive omission in the document. Nobody knows what exists, who owns it, what it costs, or how to move it. Six months later a free tier ends, a service is deleted, and no one can tell which.
3”My keys are in the .env file.”The keys belong to you. When your student account is deactivated the software stops working and nobody knows why. No rotation procedure, no transfer order.
4”Let me know if you have questions.”The premise of the entire document is that the successor cannot ask you a question. This is a promise that will not be kept, offered instead of the writing that would have made it unnecessary.
5No known defects and no deferred work.DEF-022 ships, and the successor finds it themselves after trusting the document. A gap you disclose is professionalism; a gap they discover is a reason to distrust everything else.
6”Future work: add tests, improve the UI, maybe a mobile app” — and no bus-factor register.Not a plan: no identifiers, no estimate, no priority. Compare “DEF-022, 2.5 h — paginate the pantry query, add a page control, extend TC-041.” And the four things only you know stay only known to you, which is the definition of a failed handoff.

C.12 — The Change Log and the Change Requests

Handed to you in: Week 4 — baseline-and-change-control.md in the Chapter 4 companion code; the worked accept/reject/withdraw records are in cut-kit.md §3, Chapter 6. Lands at: CHANGELOG.md at the repository root, and docs/change-requests/ — a directory, one CR-nnn-*.md file per request · Hat: release engineer · Time: twenty minutes per release, plus one page per change request.

Two artifacts, one system. docs/change-requests/ is the record; the change table at the top of CHANGELOG.md is the index. A request with no file did not happen. A file with no row is invisible.

The release sections follow the widely used Keep a Changelog convention (https://keepachangelog.com/) with Semantic Versioning (https://semver.org/): MAJOR.MINOR.PATCH, plus a pre-release suffix such as -rc.1. Write for a human reader, newest first, grouped under Added, Changed, Deprecated, Removed, Fixed, Security, omitting the headings with no entries. Reference the requirement or defect ID each entry closes. And never paste raw commit output: commits record typing, a change log records decisions.

When a change needs a change request — this edition’s threshold is three hours, tighter than the long edition’s four, because three hours is a seventh of your week: it adds, removes, or materially rewords a Must; it changes an interface contract or the data model; it moves a milestone or changes the plan by more than 3 hours; it changes the stack; or it changes the definition of done. Everything else is an ordinary commit with a clear message. Do not bureaucratize your own project.

Change log and change request — template

# Changelog
All notable changes to this project are documented here.
Format: Keep a Changelog. Versioning: Semantic Versioning.

## Specification changes since baseline v1.0        <- the INDEX
| CR | Date | Change | Requirements | Hours | Decision |

## [Unreleased]
## [X.Y.Z] - YYYY-MM-DD
### Added / Changed / Deprecated / Removed / Fixed / Security
- <the user-visible thing, not the code you touched> (FR-… or DEF-…)
### Known issues
- <defect shipping in this release: ID, severity, workaround, effort>
docs/change-requests/CR-nnn-<short-slug>.md              <- the RECORD, one page
| Raised by | Date raised | Baseline affected | Trigger |
## What changes    quote the before and the after
## Why             "it turned out harder" is legitimate IF you say how much harder
## Impact analysis cost (build + test + doc) · blast radius (IDs, modules, tests, docs) ·
                   hours remaining after · schedule effect · which risk moves
## Decision        ACCEPT / DEFER -> where, sized / REJECT · decided by · date ·
                   rationale, required in writing even when you approve your own request
## Applied         [ ] requirements  [ ] traceability  [ ] plan re-baselined
                   [ ] row in the CHANGELOG.md change table  [ ] new tag if a Must moved

Change log — worked (PantryPilot)

The change table at the top of CHANGELOG.md. spec-baseline-v1.0 was declared at the Week-4 design review on 2026-09-23, so the Week-3 scope decision — barcode entry, editing, the configurable window — is not in this table. It happened before the freeze, which is where cutting is cheapest and needs no ceremony. Everything after the freeze does.

## Specification changes since baseline v1.0

| CR | Date | Change | Requirements | Hours | Decision |
|---|---|---|---:|---|---|
| CR-001 | 2026-09-25 | Make expires_on nullable; count undated items on the expiring-soon view | FR-EXP-02, FR-INV-01 | +0.75 | approved — ADR 0004 |
| CR-004 | 2026-10-08 | Withdraw "regenerate the join code" — my own addition, never built | FR-ACC-02 | −4.0 | approved |
| CR-006 | 2026-10-09 | Add a native mobile client | new | +86.0 | rejected — 86 h against 9 remaining |

The 1.0.0 release entry, tagged in Week 7:

## [1.0.0] - 2026-10-17
### Added
- Undated items: an item may be saved with no expiry date. Undated items appear in
  the pantry list and are counted on the expiring-soon view, and never trigger a
  warning (FR-EXP-02, FR-INV-01, ADR 0004, CR-001).

### Changed
- The expiring-soon window is fixed at seven days and is no longer configurable
  (FR-EXP-02, Week-3 scope decision). Nothing to migrate; the screen never shipped.

### Removed
- Barcode entry, which appeared in 0.4.0 as an experiment, is gone, along with its
  fixture file. Cut in the Week-3 scope decision (FR-SCAN-01, FR-SCAN-02, withdrawn).

### Fixed
- Items expiring today no longer disappear from the expiring-soon view (DEF-014).
- Marking an item consumed no longer loses keyboard focus; it moves to the next
  item in the list (DEF-016, NFR-ACC-01).
- A quantity of 0 is now rejected with a message naming the field (DEF-019).

### Security
- Join-code entry is now rate limited to 10 attempts per hour per client; the
  limiter previously ran after the route and never fired (DEF-009, NFR-SEC-02).
- Join codes are stored only as salted hashes and are excluded from all log
  output; a test greps the captured log fixture for a known code (NFR-SEC-02).

### Known issues
- DEF-022 (S3): the pantry list is not paginated and renders slowly past roughly
  800 items. The list stays correct. Workaround: use the expiring-soon view, which
  is filtered. Estimated 2.5 h to fix; see docs/handoff.md §4.
- Editing an item is not implemented (FR-INV-02, cut in Week 3). Delete and re-add.

Why this one works:

  • Read it as a stranger and you can tell what the software now does, what you must do differently, what was broken and is not, and what is still broken — and every line traces to an identifier you can look up in docs/requirements.md, docs/defect-log.md, docs/change-requests/, or docs/adr/.
  • Removed explains a feature that once existed and is now gone, with the reason and the withdrawn identifiers. Silently deleting a feature a user saw is how you lose their trust in one release.
  • Fixed entries state the symptom the user saw, not the code that changed. “Fixed off-by-one in getWindow” means nothing; “items expiring today no longer disappear” means everything.
  • The Security section names a real security defect plainly, in public, in the release that fixed it. That is the entry most students quietly bury, and burying it is worth less than the two lines it takes to own it.
  • Known issues appear in the release entry with severity, workaround, and effort. A release with no known-issues section claims zero known defects, which is almost never true.

Change log — bad, and what it costs

## v1.0
- various bug fixes and improvements
- updated dependencies
- refactored the backend
- added new features
- misc
LineWhat is wrongWhat it costs
”various bug fixes”Names nothing.A user cannot tell whether their bug is fixed. Every defect you fixed and did not name is work you did and got no credit for.
”updated dependencies”Hides whether a security advisory was involved.An operator deciding whether the upgrade is urgent has nothing to decide with. If it was a security fix, you buried the most important line in the release.
”refactored the backend”Internal churn, invisible to a user — and it hides the migration.Somebody deploys without running migrations and the application fails on first request. That is an outage caused by a change log.
”added new features” · “misc”The Added section with no features in it, then an author who stopped caring.Nothing to demonstrate, nothing to trace, nothing to grade — on the most visible file in the repository after the README.
(absent)No known-issues section.Implies zero known defects, which your own defect log contradicts in the same repository. Two of your documents disagree, and a grader who finds one contradiction stops trusting both.
(absent)No change table, and docs/change-requests/ is empty.The Week-4 baseline was declared and then quietly ignored. Every cut after that point is undocumented, which is the difference between a scope decision and a story you tell in Week 8.
(absent)No date, and v1.0 is not a semantic version.You cannot tell what shipped when, and v1.0 versus 1.0.0 is the small inconsistency that makes a reviewer check everything else.

C.13 — The Retrospective

Handed to you in: Week 8 — retrospective-template.md in the Chapter 8 companion code. Lands at: docs/retrospective.md · Hat: all of them, looking backward · Time: 2 h in the final week.

The last document you write and the one most likely to be read by a stranger, because it is the one you will point an interviewer at. It is also the easiest to fake and the easiest to catch faking.

One rule governs it: every claim must be traceable to something already in the repository — an hours-log row, a requirement ID, a defect number, a change request, a commit. A retrospective without evidence is a feeling, and eight weeks of documents exist precisely to replace feelings with numbers.

This edition adds §4, the two-thirds question, and it is graded: did you scope to 160 hours, or to what you wished you had? And where did the adaptation leak?

Retrospective — template

# Capstone Retrospective — <project name>

1. What I set out to build   one paragraph from the Week-1 charter, unedited to look
                             prescient; which brief you adopted and what you changed.
                             Then one sentence: what I actually shipped.
2. The numbers   | | Planned | Actual | Delta |   total hours 160 · construction hours
                 (Weeks 5-6) 40 · Must/Should/Could · Musts shipped and passing ·
                 withdrawn after the Week-4 baseline (by CR) · defects logged/open ·
                 largest single-task estimate error. Pulled from the repo; the files win.
3. Estimate versus actual    your three worst by ID, then the rule WITH A NUMBER IN IT
4. The two-thirds question   did I scope to 160 h or to what I wished? Where did the
                             adaptation leak, and when did I catch it?
5. The decision I would make differently   ONE, examined honestly
6. The same 160 hours, spent better  | take from | h | give to | why |  every row cites the repo
7. What did not get done  | ID | what | priority | state | remaining effort | where the plan lives |
                          Every open Must belongs in this table.
8. What the process taught me    9. What I would tell the version of me in Week 1

Retrospective — worked (PantryPilot)

§2 The numbers

PlannedActualDelta
Total hours160168.5+8.5
Hours in construction (Weeks 5–6)4044.0+4.0
Requirements: Must / Should / Could5 / 3 / 15 / 0 / 0−3 Should, −1 Could
Musts shipped and passing550
Withdrawn after the Week-4 baseline (by CR)1 (FR-ACC-02, CR-004)
Defects logged / open at submission21 / 2DEF-022 (S3), DEF-025 (S4)
Largest single-task estimate errorT-2.4, 1.5 h est / 5.0 h actual3.3×

Every figure is computed from docs/hours-log.csv, docs/requirements.md, docs/change-requests/, and docs/defect-log.md. Where a number here disagrees with those files, the files win.

§3 Estimate versus actual — my three worst

TaskEstActualRatioWhy
T-2.4 — unit normalization (carton, jar, kg, “a bag”)1.55.03.3×I estimated the code. The actual work was deciding what a unit is — a domain question I had priced as a coding task
T-1.3 — join-code hashing and the rate limiter2.04.52.3×First time using this hashing function myself. Two of those hours were reading, not typing, and reading is real work I never estimate
T-7.1 — first deploy from the tag2.54.251.7×The deploy worked. The secrets did not — three push-fail-read-logs cycles before I understood the host’s secret store

My rule, with a number in it: any task that touches an interface I have never personally called — a hashing library, a host’s secret store, a migration tool — gets my estimate multiplied by three. Any task I have done before in another course gets multiplied by 1.2. Applied retroactively to the Week-3 plan, that rule turns 67.6 raw hours into 79.4 — against the 76.5 I actually spent on those tasks. It is off by 2.9 hours over five weeks. The rule works. I did not have it in Week 3, which is exactly what the 1.24× calibration factor was standing in for.

§4 The two-thirds question. I scoped to it, but only after Week 3 made me. My Week-1 charter carried three Must slices and 27 feature hours, which was honest. My Week-3 plan carried 67.6 raw hours, which was not — because I had quietly kept both Shoulds from the brief as though they were free. The calibrated verdict said 83.8 against 53.2 plannable, and cutting barcode entry took me thirty-five minutes of arguing with myself before the estimate spread settled it, which is thirty-five minutes I would not have spent if I had believed the two-thirds rule in Week 1 instead of Week 3.

Where the adaptation leaked: FR-INV-03’s acceptance criterion said “given a household with items entered by two different members.” That is brief #1’s household. In mine, exactly one person enters and three read — which is the brief’s own stated hard part, and I copied past it anyway. I caught it in the Week-2 adaptation sweep, but only because the stale-term list had “household member” on it. Nothing in my own reading found it, because an inherited sentence reads as normal.

§7 What did not get done

IDWhat it isPriorityState todayRemaining effortWhere the plan lives
DEF-022Pantry list unpaginatedS3 / P3Shipping. Correct, slow past ~800 items; workaround documented2.5 hdocs/handoff.md §4
FR-INV-02Edit an itemShould → Won’t, Week-3 scope decisionNot built. Delete and re-add4.0 hdocs/requirements.md §7
FR-SCAN-01/02Barcode entry and its failure pathShould → WithdrawnCut in Week 3; experimental code removed in 1.0.0~14 h if reviveddocs/plan.md; CHANGELOG.md
DEF-025Empty-state copy ignores the active filterS4 / P4Shipping. Left as the handoff guide’s starter issue on purpose0.3 hdocs/handoff.md §2

Why this one works:

  • The numbers are populated from the repository, it says so, and it names which source wins in a disagreement. That one sentence tells a grader the author checked.
  • It reports 168.5 hours against 160. Overrunning is normal. Reporting 160.0 exactly would be the least believable number in the document.
  • The estimate rule has a multiplier in it and is tested against the actual data — 79.4 predicted against 76.5 spent. That is a finding, not a resolution, and it is a sentence this student can use in their first job.
  • §4 answers both halves honestly, including the thirty-five minutes of arguing, and names the specific criterion where the adaptation leaked. Vague self-criticism is free; naming the identifier is not.
  • Every open item is in §7 with an effort estimate and a location. Leaving one out is the most expensive omission available in the final week.

Retrospective — bad, and what it costs

# Retrospective

This project was a great learning experience. I learned a lot about full-stack
development and project management. Things took longer than expected, but I got
most of the features done.

What went well: I built a working application and learned new technologies.
What didn't go well: time management, and I underestimated some tasks.
What I'd do differently: start earlier and plan better.

Overall I'm proud of what I accomplished and I think it turned out well.
#What is wrongWhat it costs
1Not one number in the entire document.Eight weeks of hours logs, defect logs, change requests, and estimates sit in the same repository, unused. The section meant to prove you can measure your own work proves the opposite.
2”Most of the features” — which?If a Must is missing this conceals it, and a grader finds it in the requirements two minutes later. Concealment discovered is far more expensive than a gap disclosed.
3”I underestimated some tasks” · “start earlier and plan better.”No task, no ratio, no pattern — and an intention is not a finding. The most valuable output of a capstone retrospective is a calibrated rule about your own estimating. Compare: “any task touching an interface I have never called, multiply by three.”
4The two-thirds question is not answered at all.A graded section of the accelerated retrospective. Skipping it says either that the scope arithmetic never happened or that it did not go well enough to write down.
5No adaptation account.You adopted a brief. A reader wants to know what you changed and what slipped through, and the absence reads as “I never checked” — the failure this whole edition is built to prevent.
6No decision examined, no “what did not get done” table, and “I think it turned out well.”Every capstone contains one decision the author would reverse; naming none reads as no reflection or no honesty, and a reviewer cannot tell which. Then unsupported self-praise closes a document whose entire purpose is evidence-based self-assessment. “Let another praise you, and not your own mouth” (Proverbs 27:2, ESV) — and the evidence is already in your repository, if you let it speak.

C.14 — The Consistency Sweep

Individually strong documents that contradict each other score worse than a coherent set of merely good ones, because a reviewer who finds one contradiction starts hunting for more and reads everything after it looking for defects instead of merit.

Run this in Week 8, before you tag. Forty minutes, and it is the cheapest set of points in the course.

#CheckWhere it usually breaks
1Every requirement ID in docs/test-plan.md and docs/traceability.csv exists in docs/requirements.mdAn identifier that lost its area prefix on the way into the matrix
2Every Must is passing in docs/test-results.md or open in docs/defect-log.md with a severityA Must quietly disappears in a Week-6 cut and nothing records it
3Every cut in the Week-3 scope decision appears in the requirements’ Won’t-have listThe plan changes; the specification does not
4Every change after the Week-4 baseline has a file in docs/change-requests/ and a row in the CHANGELOG.md change tableThe file exists and the index does not, or the reverse
5docs/architecture.md describes the system as built; every ADR’s status is current and superseded ones point at their successorWeek-3 diagrams never updated; stale Proposed records on decisions you made in Week 5
6Feature claims in README.md match what actually runs, and CHANGELOG.md known issues match the open rows in docs/defect-log.mdA cut feature still advertised; the two issue lists drift in the final week
7docs/runbook.md §5 covers the failures in the defect log and the risk registerThe information exists; nobody moved it
8Deferred work appears identically in docs/handoff.md and docs/retrospective.mdDifferent effort estimates in the two files
9docs/hours-log.csv has entries in all eight weeks, including the bad ones, and its ai_assisted rows match docs/ai-usage.mdSilences and backfilled disclosure, both of which the report script names out loud
10LICENSE is a real license whose SPDX identifier matches the README’s claim, and every time-varying claim has a source and a checked dateA README says MIT and the file is empty; verification tables left blank
11No file in the repository is named chapter, exercises, project-NN, or project-arcCourse files copied into a student repository

Coach’s Note — Do the sweep in one sitting, with everything open, and do it before you write the retrospective. Half of what you find is something the retrospective should have been reporting anyway, and the other half takes four minutes each to fix while you already have both files in front of you.


C.15 — Using an Assistant on These Documents

AI is genuinely useful on this kit and dangerous in a specific, predictable way. Sort every use into one of three piles before you start.

Real leverage, low risk. Breadth checks — “here is my risk register; what categories have I not covered?” It is excellent at naming what you forgot; harvest the list and write the rows yourself. Adversarial review — “here is one requirement and its acceptance criteria; find every way a developer could satisfy this sentence and still build the wrong thing.” Format conformance — “does this defect report contain all seven required fields?” Mechanical and verifiable in ten seconds. And this edition’s best prompt, the stale sweep: “here is my charter and my adapted requirements; list every requirement that still assumes something my charter does not contain. Quote the exact phrase. Rewrite nothing.” The model has no attachment to the brief’s wording, which is precisely the attachment you have.

Unreliable — use it, verify every line. Durations, because it does not know how fast you are or that your last four hours went to a broken lock file; your calibration factor is worth more than its estimate, because it is made of your own history. Anything time-varying — prices, free-tier limits, license terms, versions — which it will state confidently and out of date, and which is exactly what the verification tables in C.4 exist for. Identifiers, because it will cite FR-INV-21 when your inventory requirements end at FR-INV-04; the Week-4 review sample in Chapter 4 contains two rejected findings that are precisely this failure.

Where it must not go. Decision records, memos, the retrospective, and the AI-usage log itself — not because generated prose is bad, but because these documents are your judgment, and judgment is the thing being graded. An ADR whose context you did not write is an ADR you cannot defend when someone asks why.

The spine rule for this course and for the job you are about to take: the human stays in the loop where the judgment lives. AI accelerates; you decide, you verify, and you are accountable. Every substantive use gets a row in docs/ai-usage.md, written the day it happens, and a matching yes in the hours log. Ninety seconds, and it is evidence of judgment rather than evidence of nothing.


C.16 — Why Write Any of This Down?

You have just read thirteen sets of forms. It is fair to ask whether they are worth it in a course this short, and there is a version of that question worth taking seriously: isn’t this bureaucracy? Doesn’t the code speak for itself?

The code does not speak for itself, and everyone who has claimed otherwise has been corrected by the second maintainer. But the deeper answer is not about efficiency. Look at what these documents actually do. The charter counts the cost before the tower is started — which is not my image, it is the one in Luke 14. The requirements specification says exactly what you will do, so your yes means yes. The hours log records what really happened rather than what you wish had. The defect log goes looking for your own faults on purpose. The risk register admits you do not know what tomorrow holds. The handoff guide provides for a successor you will never meet. The retrospective tells the truth about your own work without flattering yourself.

Those are not clerical habits. They are the practical shape of honesty applied to work, and Scripture is not shy about tying the two together: “A false balance is an abomination to the LORD, but a just weight is his delight” (Proverbs 11:1, ESV). A weight is a boring object. It is also, in that verse, a moral object — because a weight is how one person’s claim gets tested by somebody who was not there. Your hours log is a weight. Your defect log is a weight. Your test results are a weight.

That is what makes documentation an ethical act rather than an administrative one. Almost every artifact in this kit exists for somebody who is not you: the grader who cannot ask a question, the maintainer who inherits the repository, the housemate who trusted the expiry warning, the teammate at the job you have not started yet. Writing them well is a small, concrete form of loving your neighbor — not in the abstract, but at an inconvenient hour when a stranger is trying to restore your database from a dump they have never seen.

And it cuts inward. The reason the bad versions in this appendix are comfortable is that vagueness protects you. “The feature works correctly” cannot be falsified. “Fast and responsive” cannot fail. “Most of the features got done” cannot be checked. Every vague sentence in a technical document is a small place to hide, and the discipline of this appendix — a number, a date, a source, an identifier, a name — is the discipline of not hiding.

Write it down. Not because the rubric says so, though it does. Because the work is not finished until someone else can carry it.


Coach’s Final Word

Thirteen artifacts. More than forty versions of them on this page. Here is what I actually want you to take away.

The gap between the good and the bad versions is almost never knowledge. Nobody reading this thinks “the feature works correctly” is a strong acceptance criterion. The gap is fifteen minutes and the willingness to be specific when vagueness is available and comfortable. Every good version here came from somebody who, at the moment they were about to write fast, stopped and asked how fast, measured how, under what conditions — and then wrote the harder sentence.

Eight weeks does not change that. It changes how quickly the bill arrives. In a sixteen-week course a vague requirement written in Week 3 detonates in Week 12 and you have four weeks to recover. Here it detonates in Week 6 and you have two. That is the whole difference, and it is an argument for writing the harder sentence sooner, not for skipping it.

So use this appendix as a mirror, not a library. When you have a draft you think is finished, open the bad version of that artifact and read it looking for yourself. You will find yourself in there more often than is comfortable. That discomfort is the entire product.

And keep them current. A document set that was true at the Week-4 baseline and has not been touched since is not a document set; it is a fossil. Twenty minutes every Monday — burn-down, top risks, and whatever last week made obsolete — is the whole practice.

Fill in the blanks. Then go build the thing they describe.

See you on Monday.