Appendix B

The Document Kit: Templates and Worked Examples

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

Appendix B — The Document Kit: Templates and Worked Examples

“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 longest appendix in the book and the one you will open most often. It exists because of a promise I made you in Chapter 1: this course will not tell you to “write good requirements” and then leave you alone with a blank file. It will show you.

Every deliverable this course asks for appears here three times.

  1. A blank template. Copy it, fill it, commit it. Where a chapter already hands you a template file, the version here is the same document with the guidance expanded — not a competing standard. Use the chapter’s file as the thing you copy; use this appendix as the thing you read when you are stuck on a section.
  2. A fully worked example, drawn from PantryPilot — the running example introduced in Chapter 2, a household food-tracking web application built by one student in one semester. Your project will look nothing like a pantry app. The shape transfers exactly.
  3. A deliberately bad version, annotated — not a strawman, but the actual draft I read every single semester — 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 of this appendix. Anybody can recognize good writing when it is 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. That is a learned skill, and the fastest way to learn it is to read a hundred bad documents with the defects circled. Here are sixteen of them.

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


How to Use This Appendix

The sixteen artifacts, and when each one lands

Everything in this table is a graded artifact in the Week-16 submission, and almost everything here is also graded the week you ship it, as part of the 25% milestone bucket. Nothing here is optional — the milestones are the final deliverable, produced one week at a time. The full accounting lives in Appendix C.

§ArtifactHanded to you inTemplate file in that chapter’s companion codeLands in your repo at
B.1Project charterWeek 1charter-template.mddocs/charter.md
B.2Requirements specification (functional)Week 3srs-template.mddocs/requirements.md §1–5
B.3Non-functional requirementsWeek 4nfr-template.mddocs/requirements.md §5–9
B.4Acceptance criteriaWeek 3(inside srs-template.md)inline with every requirement
B.5Architecture decision recordWeek 5adr-template.mddocs/adr/NNNN-*.md
B.6Work breakdown structureWeek 7wbs-template.mddocs/plan.md §3–4
B.7Schedule and burn-downWeek 7wbs-template.mddocs/plan.md §5–7
B.8Risk registerWeek 7risk-register-template.mddocs/risk-register.md
B.9Test planWeek 11test-plan-template.mddocs/test-plan.md
B.10Defect report and defect logWeek 11defect-report-template.mddocs/defect-log.md
B.11Hours logWeek 1 (reported from Week 10)hours-log.csv, hours_report.pydocs/hours-log.csv
B.12READMEWeek 13readme-template.mdREADME.md
B.13RunbookWeek 13runbook-template.mddocs/runbook.md
B.14Onboarding and handoff guidesWeeks 13 and 14handoff-template.mddocs/onboarding.md, docs/handoff.md
B.15Change logWeek 12changelog-template.mdCHANGELOG.md
B.16RetrospectiveWeek 16retrospective-template.mddocs/retrospective.md

Three documents in the kit have no chapter template because they are not forms — the definition of done (Chapter 4), the technical specification (Chapter 6), and the ai-usage log (Chapter 1 and Chapter 10) — but every one of them obeys the same conventions as everything below.

About the running example

PantryPilot is a household inventory application: it records what food a shared house has, shows what is about to expire, and suggests something to cook from what is closest to spoiling. Built by one student, M. Ruiz, over one sixteen-week term. Real users (three roommates), one third-party dependency (a product-lookup service reached by barcode), one optional AI feature (a recipe suggestion), and it has to run on a machine that is not the author’s laptop.

Where a second shape helps, I will contrast with TraceLens — a command-line tool that parses server logs and reports anomalies. No user interface, no accounts, heavy on data processing. The same sixteen documents fit it; several sections come out almost empty, and that emptiness is information, written down as one honest line rather than deleted.

Dates in the worked examples come from one representative term running mid-January to late April. 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.


B.0 — House Conventions Every Document in This Kit Obeys

Get these five things right once and every document in the kit becomes easier.

Identifiers

KindFormExampleRule
Functional requirementFR-<AREA>-<nn>FR-INV-01Assigned once. Never reused, never renumbered. Retire by marking Withdrawn.
Non-functional requirementNFR-<CATEGORY>-<nn>NFR-PERF-01Same rule. Category from the eight in Chapter 4.
Constraint / assumption / dependencyCON-nn / ASM-nn / DEP-nnASM-03Assumptions get an owner and a verify-by week.
Decision recordADR-NNNNADR-0004Four digits, sequential from 0001. Immutable once Accepted.
Work package / taskWP-n / T-n.mT-4.2Task carries the requirement ID it serves, or - for enabling work.
RiskR-nnR-07Stable forever. Retired risks stay in the register, marked retired.
DefectDEF-nnnDEF-027Never reused, even for a duplicate. Duplicates are closed, not deleted.
Test caseTC-nnnTC-021Maps to at least one requirement ID.
Change requestCR-nnnCR-002Raised in Chapter 8 once the baseline is frozen.

Two identifier styles are legal, and the one rule that matters is that you use exactly one of them. The area-scoped FR-<AREA>-<nn> in the table above is the recommended form and the one this appendix and the Chapter 3 templates use throughout — on a solo project the area prefix is the cheapest navigation you will ever buy, because a test name or a commit message tells you what part of the system it touches without opening anything. The flat form — FR-07, FR-014 — is also accepted, and you will see it in several of the book’s worked artifacts (the specification excerpt in Chapter 6, the traceability matrix in Chapter 11, the work-breakdown sample in Chapter 7). Either is defensible. Copying one of those artifacts into your own repository will not fail the linter.

What will fail is mixing them. The Week-3 linter — check_requirements.py, in the Chapter 3 companion code — accepts either style but reports mixed identifier styles … pick one and use it for every requirement as an error the moment both appear in one document, and a linter error costs you the Milestone 3 rubric line that pays for a clean run. The reason is not tidiness. Mixing styles is how a traceability matrix ends up with orphans that are not really orphans — FR-014 and FR-EXP-14 are the same requirement to you and two different rows to every tool and every script you will write — and you spend forty minutes in Week 11 proving that to yourself. Pick your style in Week 3, write it into docs/requirements.md, and never revisit the decision.

The document control block

Every document in the kit opens with the same four facts, on one or two lines:

**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. Four rows in that table by Week 16 is a document somebody maintained. Zero rows is a document somebody wrote once and never looked at again, and a grader can tell the difference in four seconds.

Status values, and what they promise

StatusWhat it meansWho may change the content
DraftBeing written. Nothing depends on it yet.You, freely.
In reviewFrozen for the length of a review.Nobody, until the review closes.
BaselinedFrozen at the Week-8 design review.Only through a change request.
Superseded by ADR NNNNApplies to decision records only. The record stays; the decision does not.Nobody. Ever.

The four tests every document in this kit must pass

  1. The stranger test. Could someone who has never met you and cannot ask you a question act on this? Every worked example below was written to pass this test, and every bad example fails it in the first paragraph.
  2. The number test. Every quantity has a number. Fast, soon, large, most, reasonable, 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, or the key is expired? A document that only describes success describes a demo, not a system.
  4. The date test. Every time-varying claim — a price, a version, a rate limit, a license term, an availability window — carries a source and the date you checked it. Without that, it is not a fact. It is a rumor you have committed to version control.

Coach’s Note — These four tests are also the fastest self-review you own. Twelve minutes with this list, 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.


B.1 — The Project Charter

Handed to you in: Week 1 — charter-template.md in the Chapter 1 companion code. Lands at: docs/charter.md · Hat: project manager · Time: 3–4 hours in Week 1, thirty minutes to revise in Week 2.

The charter is the only document you write before you have an idea, and that is deliberate. It is not a charter for the project. It is a charter for you and the next sixteen weeks: how many hours you actually have, which weeks are already broken, what you refuse to build, and what you will cut first when the arithmetic stops working. In Week 2, after the scoping decision, the project itself gets dropped into §2 and the file is re-dated.

A charter is the cheapest insurance in the course. It costs four hours in Week 1 and it is the only document that can stop a bad decision in Week 10, because it is the only one written while you were still calm.

Charter — template

# Project Charter — <your name>

**Owner:** <name> · **Course:** Capstone · **Started:** <YYYY-MM-DD> · **Last revised:** <YYYY-MM-DD>

## 1. Purpose
Two or three sentences. Why does this capstone exist for you, and what will be
true at the end that is not true now? Verifiable, not aspirational.

## 2. Project  (filled in Week 2, after the scoping decision)
- One-sentence description:
- Primary user (a real person or role, not "users"):
- The one thing it must do to be worth finishing:

## 3. Capacity and constraints

| Constraint | My reality |
|---|---|
| Hours available per week | |
| Total hours budgeted | 240 |
| Weeks already broken, and where those hours move | |
| Machine (OS, RAM, disk) | |
| Administrator rights on that machine? | |
| Money I will spend on this project | |
| Technologies I already know well | |
| Technologies I am willing to learn (max two) | |
| Hard external deadlines besides this course | |

## 4. Definition of finished
Three to six bullets. The acceptance test for the whole semester, written so
somebody else could check it without asking you a question.

## 5. Non-goals — what I will NOT build or do
At least five, each specific enough to point at in Week 10.

## 6. Risks to me finishing

| # | Risk | L | I | Early warning sign | What I will do |
|---|---|---|---|---|---|
| R1 | | | | | |

## 7. Working agreement
- Sessions: <days and times blocked on a real calendar>
- Logging: every session ends with a row in docs/hours-log.csv, before I close the laptop.
- Board: work-in-progress limit of <n>.
- Commits: requirement identifier first in the subject line.
- When I fall behind, I cut in this order: <ranked, decided now, while it is cheap>
- AI use: governed by docs/ai-usage.md; every Amber-zone use logged the day it happens.

## 8. Signature
I have counted the cost of this work as honestly as I can today, and I accept
the schedule above.
<name>, <YYYY-MM-DD>

Charter — worked (PantryPilot)

Excerpt from docs/charter.md. Sections 1, 3, 4, and 5 shown; the full file also carries the risk table and the working agreement.

Owner: M. Ruiz · Started: 2026-01-13 · Last revised: 2026-01-21 (project added after the Week-2 scoping decision)

1. Purpose. To ship one small system that a stranger can clone, run from the README in under ten minutes, and extend — and to produce the requirements, design, test, and handoff documents a professional team would expect to find beside it. At the end I will have a repository I can hand to an interviewer and defend in detail, rather than a demo I have to apologize for.

3. Capacity and constraints.

ConstraintMy reality
Hours available per week15, mostly Tue/Thu evenings and Saturday mornings
Total hours budgeted240
Weeks already brokenWeek 6 (Operating Systems midterm, −6 h; those hours move forward into Week 5). Week 12 (two other projects due, −3 h; the work planned for them moves forward into Week 10, where the schedule has slack)
MachinePersonal laptop, 16 GB RAM, 400 GB free
Administrator rights?Yes on my own laptop. No on the lab machines
Money I will spendUp to $10 total. Everything else must be free-tier, and I will confirm each free tier and its terms in writing before I depend on it
Technologies I know wellJavaScript, Node, SQL, Git
Technologies I am willing to learn (max two)One: a schema-migration tool. That is the entire novelty budget
Hard external deadlinesOperating Systems midterm Week 6; family obligation Week 12

4. Definition of finished. By the last week:

  • A stranger clones the repository, follows README.md, and has the application running locally in under ten minutes with no help from me. Verified by an actual stranger, on their own machine, and recorded in docs/clean-machine-test.md.
  • Every requirement marked Must in docs/requirements.md either passes a named acceptance test in docs/test-results.md or appears in docs/defect-log.md with a severity and a workaround. No Must is silently missing.
  • The application is deployed somewhere that is not my laptop, and docs/runbook.md has been executed end to end by a human on that deployment.
  • docs/handoff.md names every account, key, and cost, and every one of them can be transferred without me.
  • The hours log has an entry for all sixteen weeks, including the weeks that went badly.

5. Non-goals.

  1. I will not build a mobile client. Web only, one browser engine as the tested target, a second as best-effort.
  2. I will not support more than one household per account. Multi-tenancy is a second data model and I do not have the hours.
  3. I will not write my own authentication. I will use a library and write an ADR saying which and why.
  4. I will not track prices, spend, nutrition, or allergens. Health claims carry a duty of care this project cannot meet.
  5. I will not build an admin interface. Administration is done with SQL and documented in the runbook.
  6. I will not rewrite anything that works in order to make it elegant after Week 12.

Why this one works:

  • The purpose statement names an observable end state (“a stranger can clone and run it”) rather than a feeling (“something impressive”).
  • The capacity table admits two broken weeks in Week 1 and says where the hours went. That is the single most predictive line in a capstone charter.
  • The novelty budget is capped at one new technology, in writing, before there is any temptation.
  • The non-goals are specific enough to point at. “I will not build a mobile client” ends an argument in Week 10 in four seconds. “I won’t over-engineer it” does not.
  • Non-goal 6 is a rule about the future author, who is a different and much more tired person than the one writing the charter.

Charter — bad, and what it costs

# Project Charter

I am going to build a really useful app that helps people manage their food so
they waste less. It will be modern and scalable and have a great user experience.
I'll use whatever technologies are best for the job and learn some new ones along
the way, which is part of the point of a capstone.

Timeline: 16 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. “Consistently” is not a number of hours. There is nothing to plan against in Week 7, so the schedule will be built on a guess.The Week-7 plan cannot be checked against reality, so it will be optimistic by roughly the amount you wanted it to be. Typically 30–60 hours of overrun, discovered in Week 13.
2No broken weeks. Every student has two. Not writing them down does not remove them; it removes your ability to move the hours somewhere.Two weeks of silent debt that surface in the burn-down as an unexplained cliff.
3”Modern,” “scalable,” “great user experience.” Three adjectives with no metric. None of them can be tested, so none of them can be finished.Fails the number test in Week 4, and every one of these becomes a non-functional requirement you have to write anyway — at higher pressure, four weeks later.
4”Whatever technologies are best” and “learn some new ones.” An uncapped novelty budget.This is the single most reliable predictor of a capstone that does not ship. Every new technology costs 10–20 hours you did not plan, and the student who says this usually picks three.
5No non-goals. Nothing is refused, so everything is in scope by default.There is no document that can stop a Week-10 feature idea. Scope grows until the hours run out, and then it stops for the wrong reason.
6”Get a good grade” as the definition of finished. Not verifiable, and not by you.The definition-of-finished line in the Week-16 rubric has nothing to score against.
7Risks with no trigger, owner, or response. “Running out of time” is a feeling, not a risk.Cannot be detected — only discovered, in Week 15, as a catastrophe. See B.8.
8No signature and no dates.The document was never a commitment. Everyone can tell.

The deepest problem is not any single line. It 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.


B.2 — The Software Requirements Specification (Functional)

Handed to you in: Week 3 — srs-template.md in the Chapter 3 companion code, alongside a worked excerpt and a linter you can run over your own draft. Lands at: docs/requirements.md §1–5 · Hat: business analyst · Time: 9–11 hours in Week 3.

The functional requirements are the contract. Every later document points back at these identifiers: the technical specification designs to them, the work breakdown estimates them, the test plan verifies them, the change log closes them, the retrospective counts them. Get the identifiers stable in Week 3 and the next thirteen weeks have a spine. Get them wrong and every downstream document is quietly wrong too.

Target for a solo capstone: eighteen to thirty-five functional requirements. Fewer than eighteen usually means you have written features, not requirements. More than thirty-five means you have written tasks.

SRS — template

# Software Requirements Specification — <Your Project Name>

**Author:** <name>  **Version:** 1.0  **Date:** <YYYY-MM-DD>
**Status:** Draft | In review | Baselined

## 1. Purpose and Scope
One paragraph: what this system is for, who it serves, what problem it removes.
One paragraph: what is explicitly outside the boundary of this release.

## 2. Stakeholders and Personas

| Persona | Who they are | What they need from the system | Evidence they exist |
|---|---|---|---|

Include the maintainer who inherits this repository. They are a stakeholder.

## 3. Definitions
Every term your requirements use in a project-specific sense.

## 4. Assumptions and Dependencies
- Assumption: <taken as true without proof> — If false: <consequence>
- Dependency: <external service, dataset, device, person> — If unavailable: <fallback>

## 5. Functional Requirements

### 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.
**Acceptance criteria:**
- Given <starting state>, when <the actor does this>, then <this observable thing is true>.
- Given <edge or failure case>, when <trigger>, then <defined behavior>.
**Source:** <interview, observation, regulation, your own decision — name it>

## 6. Non-Functional Requirements
(Week 4 — see B.3.)

## 7. Out of Scope (the Won't-Have List)

| Not building | Why not | Revisit when |
|---|---|---|

## 8. Open Questions

| # | Question | Who can answer it | Needed by |
|---|---|---|---|

## 9. Document Change Log

| Date | Version | Change | Reason |
|---|---|---|---|

SRS — worked (PantryPilot)

Two requirements out of twenty-eight, plus the persona table and the Won’t-have list. The full excerpt ships with Chapter 3.

2. Stakeholders and Personas

PersonaWho they areWhat they needEvidence they exist
Dana, 22, the organizerBuys most of the groceries; keeps a whiteboard list that goes staleTo see, in under ten seconds, what will spoil this weekInterview 2026-02-03; photographed the whiteboard
Marcus, 21, the passive housemateWill open the app only when something pings himTo be told what to eat tonight without entering anythingInterview 2026-02-04
The next maintainerInherits this repository after the course endsTo understand what each feature was for, from the document aloneCourse requirement; Week-13 clean-machine test

FR-EXP-02 — Expiring-soon view

Priority: Must Requirement: A signed-in household member shall be able to view every pantry item whose expiry date falls within the next seven days, ordered soonest first. Rationale: This is the whole reason Dana would open the app. Every other feature exists to make this view accurate. Acceptance criteria:

  • Given 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.
  • Given a pantry with no items expiring in the next seven days, when a member opens the view, then the system displays an explicit empty state rather than a blank screen.
  • Given an item whose expiry date is today, when a member opens the view, then that item is listed first. (Boundary case added after DEF-014.)

Source: Interview with Dana, 2026-02-03.

FR-SCAN-02 — Behavior when the lookup service is unavailable

Priority: Must Requirement: When the product-lookup service does not respond within five seconds, the system shall present the manual add-item form with any data the member has already entered preserved. Rationale: The one third-party dependency in this project will be down at some point, probably during the demo. Acceptance criteria:

  • Given the lookup service is unreachable, when a member submits a barcode, then within six seconds the manual form appears, the barcode is still in the field, and a message states that lookup is unavailable.
  • Given the lookup service is unreachable, when a member completes the manual form, then the item saves normally.

Source: Risk identified during scoping, Week 2. Tracked as R-01.

7. Out of Scope (the Won’t-Have List)

Not buildingWhy notRevisit when
Shopping list generationA second feature area with its own data model; an estimated 25 hours the budget does not haveAfter a v1.0 release exists
Multiple households per accountNo evidence any interviewed user wants itA second household asks
Price and spend trackingRequires reliable price data this project cannot sourceNever, in this course
Native mobile applicationsDoubles the build and the release processOut of scope permanently
Nutrition or allergen informationHealth claims carry a duty of care this project cannot meetOut of scope permanently

Why this one works:

  • Every requirement names the actor, the action, and the condition. Strip any one of those and it stops being checkable.
  • FR-SCAN-02 is a requirement about failure. Most student specifications contain none. This one exists because the risk register found it in Week 2, which is what a risk register is for.
  • The third criterion 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.
  • The persona table’s fourth column is evidence, with dates. “Dana wants X” is a claim. “Interview 2026-02-03, photographed the whiteboard” is a fact.
  • The Won’t-have list has five rows with reasons and revisit conditions. A short Won’t-have list means you have not thought hard enough about what you are refusing.

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 will track them and notify them when
   things are expiring soon so they don't waste food.
3. The app will use React and a REST API to display the pantry.
4. Handle errors gracefully.
5. The recipe suggestions should be good.
6. Support for 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. It also hides two real non-functional requirements you now have to write in Week 4 anyway.
2”add items and track them and notify them”compoundThree obligations in one sentence with one identifier. When you ship two of the three, the traceability matrix says the requirement is done. It is not, and the demo will show it.
2”expiring soon”ambiguousSoon has no number. You will implement three days, your user assumed a week, and the disagreement surfaces in the Week-16 demo in front of the person grading you.
2no actormissing actorWho does it — any member? The household owner? A background job? The data model cannot be designed from this sentence.
3”will use React and a REST API”solution-biasedThis is a design decision wearing a requirement’s clothes. It belongs in an ADR (B.5), with options considered and consequences named. Written here, it removes your right to change your mind and pre-commits you before the evaluation in Week 5.
4”Handle errors gracefully”unverifiableWhich errors? Handled how? Gracefully is not observable behavior. This line is where every unhandled exception in the demo comes from.
5”should be good”unverifiable + probabilisticFor an AI-backed feature this is the most expensive line in the document. See B.4 for the fix: a fixed evaluation set and a pass fraction, committed before you look at the output.
6”would be nice to have”no priorityMoSCoW exists so that Week 12 has a defensible cut list. “Nice to have” is not a MoSCoW value; it is a way of avoiding the decision.
No identifiers anywherefatalNothing downstream can point at anything. The traceability matrix in Week 4 has nothing to trace, the WBS in Week 7 has nothing to reference, the test plan in Week 11 has nothing to cover, and the change log in Week 12 has nothing to close. One missing convention breaks five documents.
No acceptance criteriafatalNobody but you can tell whether any of this is finished — including the person who assigns the 50%.
No Won’t-have listfatalEverything not mentioned is ambiguous rather than excluded. Scope grows by default.

Six lines. Roughly fourteen distinct defects, and at least four documents downstream that cannot be written correctly on top of it. This is why Week 3 is nine hours and not two.


B.3 — Non-Functional Requirements, Constraints, and Obligations

Handed to you in: Week 4 — nfr-template.md in the Chapter 4 companion code, with a traceability matrix and a checker. Lands at: docs/requirements.md §5–9 · Hat: business analyst · Time: 7–9 hours in Week 4.

Functional requirements say what the system does. Non-functional requirements say how well it must do it, and they are where capstone projects are actually lost — not because students disagree about them, but because they never wrote them down, so nothing forced a decision until the week there was no time to make one.

One rule governs this entire document: no adjective survives without a number. Every quality gets four fields — metric, threshold, condition, method. If you cannot say how you would measure it in one afternoon, it is not a requirement yet.

NFR — template

## 5. Non-Functional Requirements

Use at least six of the eight categories. For each one you skip, write one
sentence saying why it does not apply.

### 5.1 Performance
| ID | Requirement (metric · threshold · condition) | Priority | How it is measured |
|---|---|---|---|
| NFR-PERF-01 | | | |

### 5.2 Reliability & Availability     ### 5.3 Security
### 5.4 Privacy & Data Handling        ### 5.5 Accessibility
### 5.6 Usability                      ### 5.7 Maintainability
### 5.8 Portability

## 6. Constraints  <things you did NOT choose and cannot change>
| ID | Constraint | Where it comes from | What it rules out |
|---|---|---|---|

## 7. Assumptions  <things you treat as true but have NOT verified>
| ID | Assumption | Owner | Verify by | If it is false |
|---|---|---|---|---|

## 8. Dependencies  <things outside your control that you need>
| ID | Dependency | Version / plan pinned | Failure mode | Fallback |
|---|---|---|---|---|

## 9. Obligations  <license, third-party terms, data rights>
| Obligation | Primary source (URL) | Date checked | What it requires of me |
|---|---|---|---|

NFR — worked (PantryPilot)

5.1 Performance

IDRequirementPriorityHow it is measured
NFR-PERF-01The expiring-soon view renders in under 1.5 s at the 95th percentile with 200 seeded items, on a connection throttled to the browser’s “Fast 3G” profileMust20 loads in the browser dev tools with that throttling profile; p95 recorded in docs/measurements.md with the date and the commit
NFR-PERF-02A barcode lookup returns or gives up within 5 s; the user is never left without a responseMustIntegration test with the lookup client stubbed to hang; asserts a response before 6 s

5.3 Security

IDRequirementPriorityHow it is measured
NFR-SEC-01No credential, token, or key appears in the repository at any commit in its historyMustSecret scan across full history, run in CI on every push; zero findings, and the run is linked from docs/test-results.md
NFR-SEC-02A household member can read and write only their own household’s itemsMustTwo-account test: account B requests account A’s item by ID and receives 404, not 403 and not the item

5.5 Accessibility

IDRequirementPriorityHow it is measured
NFR-ACC-01The three core flows — add item, view expiring, mark consumed — are completable with the keyboard alone, with a visible focus indicator at every stepMustManual pass with the mouse physically unplugged, once per iteration, recorded with date and tester
NFR-ACC-02Text and interactive controls on the two main screens meet the contrast ratio recommended by the current Web Content Accessibility Guidelines for normal textShouldAutomated contrast check plus a manual spot check; guidance at https://www.w3.org/WAI/

7. Assumptions

IDAssumptionOwnerVerify byIf it is false
ASM-01The product-lookup service’s terms permit caching responses locally for the life of this projectM. RuizWeek 5Drop the cache, take the latency hit, and re-check NFR-PERF-02; if that fails too, FR-SCAN-01 drops to Could and manual entry becomes the only path
ASM-02Three roommates will actually enter items for two weeks during the Week-12 trialM. RuizWeek 10Fall back to a seeded data set; the usability NFR is then measured on one observed session instead of three, and the retrospective says so

9. Obligations

ObligationPrimary sourceDate checkedWhat it requires of me
Project licensehttps://spdx.org/licenses/2026-02-05A real LICENSE file at the repository root whose SPDX identifier matches what README.md claims
Dependency licenseseach dependency’s own LICENSE file2026-02-05Attribution retained; a list in docs/licenses.md; nothing copyleft pulled into a distributed binary without checking
Product-lookup terms of servicethe vendor’s own terms page2026-02-16Attribution where results are shown, and the caching and rate limits recorded in ASM-01 and R-01. Re-check before the Week-14 release; terms change

Why this one works:

  • Every row has a metric, a threshold, a condition, and a method. NFR-PERF-01 names the record count and the network profile, because “fast” on your laptop on your wifi with four rows of data is not a measurement.
  • NFR-SEC-02 specifies 404, not 403 — a real security decision made in Week 4 for free, instead of in Week 12 under pressure.
  • The obligations table has a date checked on every row and a note to re-check before release. Terms and licenses are time-varying. Undated, they are rumor.
  • ASM-01 names the fallback and the requirement whose priority changes if the assumption fails. That is what makes it an assumption and not a hope.

NFR — bad, and what it costs

## Non-Functional Requirements

- Performance: the app should be fast and responsive.
- Security: the app will be secure and follow best practices.
- Accessibility: the app will be WCAG compliant.
- Usability: the UI will be intuitive and user-friendly.
- Scalability: the architecture will scale to many users.
- Reliability: the app should have high uptime.
#What is wrongWhat it costs
1”Fast and responsive” — no metric, no threshold, no condition, no method.Nothing to test in Week 11 and nothing to design for in Week 6. You will discover the actual performance in Week 14, on the deployed instance, which is the most expensive place to discover it.
2”Secure and follow best practices” — best practices according to whom, checked how?Zero of the security work gets budgeted in Week 7, so the Week-12 security pass is done in ninety minutes at midnight, and it finds a key in the git history.
3”WCAG compliant” — no conformance level, no scope, no method.This is the single most common fabricated claim in capstone documents. Stating conformance you have not measured is worse than stating nothing, because it is a false claim in a document you signed. State the level, the pages, and the tool.
4”Intuitive and user-friendly”Fails the stranger test and the number test in the same six words. The fix is one sentence: a first-time user completes the primary task, unaided, in under three minutes, observed twice.
5”Scale to many users”You have three users. This line buys nothing and costs a great deal: it is the justification students give themselves in Week 10 for a caching layer, a queue, and a second database they did not need. A non-functional requirement you cannot justify from a persona is an invitation to gold-plate.
6”High uptime”For a project with no on-call rotation and a free-tier host that may sleep, an uptime target is either meaningless or a promise you will break. Say what actually matters — that a failure produces a message naming what failed and what to do next — and make that the requirement.
No constraints, assumptions, dependencies, or obligations sections at all.This is the expensive omission. Nobody wrote down that the lookup service’s terms were unverified, so nobody checked, so in Week 12 the caching layer that the whole performance story depends on turns out to be against the terms — and there is no fallback in the plan, because nothing recorded the assumption.

B.4 — Acceptance Criteria

Handed to you in: Week 3, inside srs-template.md in the Chapter 3 companion code. Lands at: inline with every requirement in docs/requirements.md · Time: roughly 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 16 someone who is not you decides whether you built it.

The house form is Given a specific starting state / When a specific actor does a specific thing / Then a specific, 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 <a named actor performs one specific action>,
       then <one observable, checkable result — with a number if there is one>.

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

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

Three tests before you accept a criterion. The stranger test: could someone who has never seen your code run this and get an unambiguous pass or fail? The number test: does every quantity have a number? The failure test: have you said what happens when things go wrong?

Acceptance criteria — worked (PantryPilot)

For FR-INV-01 — Add a pantry item (a plain CRUD requirement, where students are usually too brief):

  • AC1 Given a signed-in member on the pantry screen, when they submit a name, a quantity with a unit, and an expiry date, then the item appears in the household pantry list within one page refresh and is visible to every member of that household.
  • AC2 Given a submission missing the item name, when the member submits, then the system rejects the submission and states which field is missing. (This one criterion is a design decision: rejection, not a silent default, with a field-level message. Made in Week 3, for free.)
  • AC3 Given a submission whose expiry date is earlier than today, when the member submits, then the item is saved and displayed in the expired group rather than rejected.
  • AC4 Given a quantity of 0, when the member submits, then the item is rejected with a message naming the field. (Boundary.)

For FR-REC-01 — Suggest something to cook tonight (the AI-backed feature, where students are usually catastrophically vague):

  • AC1 Given a pantry with at least five non-expired items, when a member opens the suggestion panel, then between one and five recipe suggestions are returned within 3 seconds, each using at least two on-hand items.
  • AC2 Given the frozen evaluation set of 20 pantry states committed at tests/fixtures/recipe-eval/, when the suggestion routine is run over all 20, then at least 16 produce a suggestion whose named ingredients are all either on hand or in the fixed pantry-staples list. The evaluation set is fixed in advance, committed before results are seen, and not edited afterward.
  • AC3 Given the model service is unreachable or returns an error, when a member opens the suggestion panel, then the offline rule-based path runs instead, its results are labeled as such in the interface, and no error is shown to the user.
  • AC4 Given a pantry with fewer than three non-expired items, when a member opens the suggestion panel, then an explicit empty state explains that there is not enough on hand, rather than returning an invented recipe.

Why this one works:

  • AC2 on FR-REC-01 is the pattern for every probabilistic feature: the evaluation set is the acceptance criterion, fixed in advance, and the pass bar is a fraction, not a promise. “16 of 20” is an honest requirement. “All suggestions shall be good” is a hope with a shall in front of it.
  • AC3 defines the degraded path, which means the demo cannot be destroyed by somebody else’s outage.
  • AC4 forbids the specific failure mode of this class of feature — confidently inventing something when there is nothing to work from.
  • Every criterion becomes a test name in Week 11, 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 AI suggestions are relevant and helpful.
- 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 11 you will have to invent the criteria anyway in order to write the test, and you will invent them to match the code you already wrote. That is not verification; it is a photograph of the implementation.
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 other household members.
3”Performance is acceptable” — acceptable to whom, measured how, under what load?Belongs in a non-functional requirement with a number (B.3). Here it is a placeholder for a conversation nobody had.
4”Relevant and helpful” — the fatal one for any AI-backed feature.There is no evaluation set, so there is no fixed target, so the feature can never be declared done. Worse: because you will evaluate after seeing the output, every bad case will look like a case that “doesn’t count.” Fix it with a committed fixture set and a pass fraction, decided before you look.
5”Tested and no bugs” — a claim about the future, made by the person who benefits from it.This sentence is a promise the defect log will publicly contradict in Week 11. Zero known defects on a real project means you did not look.
No failure criterion anywhere.Every criterion here 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 will take it down.

B.5 — The Architecture Decision Record

Handed to you in: Week 5 — adr-template.md in the Chapter 5 companion code, with an evaluation matrix and a scoring script. Lands at: docs/adr/NNNN-kebab-case-title.md, indexed from docs/adr/README.md · Hat: architect · Time: 45–90 minutes per record.

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,” which is the ancestor of nearly every ADR template in industry; 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 ADR and mark the old one Superseded by ADR NNNN. You never delete one; deleting destroys exactly the history the folder exists to preserve. And an ADR with no negative consequences is not an ADR. Every real choice costs something. If you cannot name the cost, you did not evaluate. You shopped.

You need at least five by Week 16. Most of you will write eight to twelve.

ADR — template

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

- **Status:** Proposed | Accepted | Superseded by ADR NNNN | Deprecated
- **Date:** YYYY-MM-DD
- **Decider:** <your name — you are the architect this semester>
- **Requirements affected:** <FR-…, NFR-… from docs/requirements.md>
- **Related ADRs:** <numbers this one depends on or constrains>

## Context
The forces, not the answer. What about YOUR requirements makes this a real
decision? Which identifiers push on it? What is new to you? What is the hours
budget? A stranger should be able to predict the decision before reaching it.

## Options considered
| Option | Weighted score | The detail that decided it |
|---|---:|---|
At least two real options, scored in docs/tech-evaluation.md.

## Decision
One paragraph, active voice, present tense. Name the thing precisely, including
version or edition where it matters. If you did not take the top-scored option,
say so and say why.

## Consequences
**Positive** — what becomes easier, tied to a requirement identifier.
**Negative** — what becomes harder, what you now have to learn and the hours
you budgeted for it, and the mitigation with its cost.

## Revisit trigger
The measurable event that would make you write a superseding ADR. Name the number.

## Verification
| Claim in this ADR | Source | Checked on |
|---|---|---|
Every time-varying claim gets a source and a date.

ADR — worked (PantryPilot)

docs/adr/0004-store-expiry-as-a-date-with-a-household-time-zone.md

Status: Accepted · Date: 2026-03-05 · Decider: M. Ruiz Requirements affected: FR-EXP-02, FR-INV-01, NFR-REL-01 · Related: ADR-0001 (stack), ADR-0003 (schema conventions)

Context. FR-EXP-02 asks for “every item whose expiry date falls within the next seven days.” That sounds like arithmetic and is not. Two roommates in different time zones — which this household actually has one semester a year, when one member is abroad — will disagree about what “today” is for up to sixteen hours a day. The Week-8 design review logged this as DR-012: expiry dates were stored as local dates with no time-zone policy, so “expiring today” would render differently for two members looking at the same pantry.

I have about 11 hours of plannable time left in this work package and I have never implemented a time-zone policy before. Whatever I choose has to be testable without a second physical machine, and it has to survive a demo where I change my laptop’s clock in front of an audience.

Options considered

OptionWeighted scoreThe detail that decided it
Store a full timestamp in UTC, render in each member’s browser zone0.62Correct for events. But expiry is not an instant — milk does not go off at 14:32 — so a timestamp encodes precision the domain does not have, and every query needs a conversion
Store a plain calendar date, interpret it in one household time zone stored on the household record0.81Matches the domain exactly, one conversion at one boundary, and the whole policy is a single column plus one function
Store a plain date, interpret it in each viewer’s local zone0.44Simplest to write and the reason DR-012 exists: two members see two different answers from the same row

Decision. Expiry is stored as a plain calendar date with no time component. Each household record carries one IANA time-zone identifier, set when the household is created and editable in settings. “Today” for every expiry calculation is computed once, from the household’s zone, at the single boundary function currentHouseholdDate(household). Nothing else in the codebase is permitted to call the system clock directly, and a test asserts that.

Consequences

Positive

  • FR-EXP-02 becomes deterministic: two members in two countries see the same list from the same row. That is the requirement, and now it is one function.
  • Testing the boundary case in FR-EXP-02 AC3 needs no clock manipulation — inject a household zone and a date, assert the output. Cheap tests are tests that get written.
  • The demo can no longer be broken by a laptop clock.

Negative

  • A household spanning two zones has one canonical zone, so one member is up to a day “off” from their own local sense of today. This is a real cost and it is documented in README.md under what the system does not do.
  • One new concept and one migration I have not written before. Budgeted: 5.5 hours across T-3.4 (2.2) and T-3.5 (3.3). T-3.5’s three-point spread was 1.5 / 3 / 6 — a ratio of 4.0, right at the line where the rule says split it or spike it, which is part of why this record exists. Actual: 5.25 h on T-3.5 alone, logged, and rolled into the calibration factor.
  • Every date-handling site now has to route through one function. I added a lint rule and a test to catch direct clock calls, which cost another 40 minutes and will pay for itself the first time I forget.

Revisit trigger. If a requirement ever needs a time of day on an expiry — for example a same-day pickup or delivery window — this decision is wrong and needs a superseding ADR. Also revisit if the household model ever supports members with individually meaningful local deadlines.

Verification

ClaimSourceChecked on
The chosen database’s date type stores no time component and no zonethe database’s own type documentation2026-03-05
The runtime’s standard library exposes IANA zone conversion without an extra dependencythe runtime’s official docs2026-03-05

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.
  • Three real options, one of which is the naive one, with the concrete detail that killed each. Not a slogan — a fact.
  • The negative consequences are specific, priced in hours, and one of them is permanent and user-visible, disclosed in the README rather than hidden.
  • The estimate versus actual is recorded right in the record, which feeds the calibration factor in B.6 and the retrospective in B.16.
  • The revisit trigger names a requirement shape, not a mood.
  • The verification table dates two time-varying claims. In eighteen months, that table is the difference between a decision record and a rumor.

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
1Title names the technology, not the decision.The folder becomes a list of products instead of a list of choices. Six months later 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 budget, no novelty assessment. A stranger cannot predict the decision — because there was not one. This was a preference.
3No options considered.You cannot demonstrate a decision without an alternative. The evaluation matrix in Week 5 exists precisely so this table is not empty, and leaving it out throws away the work.
4”Most popular,” “most companies,” “very scalable.”Three unsourced, undated, unquantified claims — exactly what the verification table exists to prevent. On a project with three 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: an operational dependency, a host that is no longer free, a migration tool to learn, a heavier local setup for the next developer. Naming none of them means naming none of the risks either.
6No requirement identifiers.Nothing connects this decision to anything it was supposed to serve. When a requirement changes in Week 12, you cannot find the decisions it invalidates.
7No revisit trigger, no date, no decider, no verification.The record cannot age. In Week 16 you will not be able to say what you knew when you decided, which is the exact question a design-review panel and an interviewer both ask.

What it actually costs in the rubric: this is one of at least five required decision records, and it earns almost nothing on the decision-records line (Appendix C) — while taking as long to write as a real one would have. Fifteen more minutes and honest thinking would have produced an artifact you could show an interviewer.


B.6 — The Work Breakdown Structure

Handed to you in: Week 7 — wbs-template.md and a sample in the Chapter 7 companion code. Lands at: docs/plan.md §3–4 · Hat: project manager · Time: 5–6 hours in Week 7.

A work breakdown structure decomposes the work into deliverables — not phases, not technologies, not a vibe. It obeys the 100 percent rule: the children of any node account for all of that node’s work, no more and no less. “No more” kills gold-plating. “No less” kills the silent killer — the work you did not write down gets no hours, so it gets done at midnight in Week 15 out of the documentation budget.

Two levels are enough for a solo capstone: work packages (4–20 hours) containing tasks (1–6 hours). Under an hour is noise. Over six hours means you do not understand it yet — split it, or write a spike.

WBS — template

## 3. Work breakdown

### WP-<n> — <work package name>  ·  requirements <FR-…>  ·  owner: me

| Task | Name | Reqs | O | M | P | E | Done when | Depends on |
|---|---|---|---:|---:|---:|---:|---|---|
| T-<n>.1 | <task> | FR-… | | | | | <verifiable condition> | — |

E = (O + 4M + P) / 6      spread P/O over 4 means: spike it or split it

## 4. Roll-up

| Work package | Tasks | Raw E (h) | Calibrated (h) |
|---|---:|---:|---:|
| **Total** | | | |

Calibration factor from docs/hours-log.csv: <n.nn>×
(actual hours ÷ estimated hours over the tasks you have already finished)

Every task carries four things or it is not a task: a stable identifier, a requirement trace (or - for enabling work, with a reason), a three-point estimate, and a done-when that somebody who is not you could check.

WBS — worked (PantryPilot)

docs/plan.md §3, one work package of nine, plus the roll-up.

WP-3 — Expiry engine and the expiring-soon view · requirements FR-EXP-01, FR-EXP-02, NFR-PERF-01 · owner: me

TaskNameReqsOMPEDone whenDepends on
T-3.1Expiry window query with a seeded fixture of 200 itemsFR-EXP-011.52.552.8Query returns exactly the items in a given window against the 200-item fixture; test asserts the 2/6/20-day caseT-2.1
T-3.2Expiring-soon view, sorted soonest firstFR-EXP-022353.2Page renders the fixture in the right order; AC1 test greenT-3.1
T-3.3Explicit empty state for the viewFR-EXP-020.5121.1With an empty pantry, the view shows the empty-state copy, not a blank panel; AC2 test greenT-3.2
T-3.4Household time-zone column and migrationFR-EXP-021242.2Migration runs clean on an empty database and rolls back; existing rows get the default zoneT-2.1
T-3.5currentHouseholdDate() boundary function + lint ruleFR-EXP-021.5363.3No module outside the boundary calls the system clock; a test proves it; AC3 (expiry == today) greenT-3.4
T-3.6Performance pass against NFR-PERF-01NFR-PERF-011252.3p95 under 1.5 s over 20 throttled loads at 200 items, recorded in docs/measurements.mdT-3.2
T-3.7Keyboard operability of the viewNFR-ACC-0111.531.7Full flow completed with the mouse unplugged; visible focus at every stepT-3.2

Roll-up (all nine work packages)

Work packageTasksRaw E (h)Calibrated (h)
WP-0 Enabling: CI, fixtures, secrets, seed data59.410.7
WP-1 Accounts and households611.813.5
WP-2 Inventory CRUD714.116.1
WP-3 Expiry engine and view716.618.9
WP-4 Barcode lookup + manual fallback613.215.0
WP-5 Recipe suggestion (Could)512.914.7
WP-6 Verification: test plan, suite, defects511.012.5
WP-7 Documentation set + clean-machine test510.512.0
WP-8 Deployment, release, handoff510.211.6
Total51109.7125.0

Calibration factor from docs/hours-log.csv: 1.14× — actual hours divided by estimated hours over the 19 tasks already finished in Weeks 1–7.

Why this one works:

  • Every task has a done-when a stranger could check, and none of them is “implement X.” “Migration runs clean on an empty database and rolls back” is checkable in ninety seconds by somebody with no context.
  • The invisible work is visible: WP-0 (CI, fixtures, secrets, seed data), T-3.3 (empty state), T-3.6 (performance), T-3.7 (accessibility), WP-7 (documentation). Most student breakdowns contain none of these, which is why most student plans are short by 25 to 40 hours.
  • Two tasks trace to non-functional requirements. NFRs have no screen, so they vanish from plans unless they are given a row.
  • The calibration factor is computed from the student’s own logged history, not chosen because it felt about right.
  • T-3.5’s spread is 6 ÷ 1.5 = 4.0, right at the split-or-spike line — and it is exactly the task ADR-0004 was written about. The spread saw it first.

WBS — bad, and what it costs

## Work breakdown

1. Backend        — 40 hours
2. Frontend       — 40 hours
3. Testing        — 10 hours
4. Deployment     — 5 hours
5. Documentation  — 5 hours
#What is wrongWhat it costs
1”Backend” is not a deliverable; it is a neighborhood.You cannot estimate a neighborhood, so you will guess it, and the guess will be a round number chosen because it sounded reasonable. Look at the list: every number is round. That is the tell.
2Nothing traces to a requirement.When you cut scope in Week 12, you will not know which requirements you just broke. The traceability matrix from Week 4 becomes a lie, and the change log in Week 12 cannot name what changed.
3No done-when on anything.”Backend” is done when you feel like it is done. On a solo project there is no second person to disagree, so the definition drifts toward whatever you have finished.
4Testing is a phase at the end, with 10 hours.Every project that shipped with no tests started from a breakdown that looked like this. End-loaded work is the work that gets compressed, because Weeks 10–12 always run long.
5Documentation gets 5 hours.Documentation carries a large share of the 50% (Appendix C). Budgeting 5 hours for it is a decision to lose those points, made in Week 7 without noticing you made it. Realistic is 10–14.
6All the invisible work is invisible.No CI, no seed data, no error handling, no accessibility pass, no secrets and configuration, no time for reading and fixing generated code. Conservatively 20–30 hours that exist and are not in the plan.
7No estimation method, 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. A single number tells you nothing, so nothing gets spiked, so the unknowns stay unknown until they detonate.
8Sums to a suspiciously exact 100 hours.This plan was written to reach a target, not to describe the work. Reverse-engineered plans always land on round totals.

The deepest cost is not any single line. It is that this breakdown cannot be tracked. You cannot burn down against “Backend, 40 hours” — you will report 50% for three weeks running, which is the classic ninety-percent-done-for-the-second-half-of-the-project pattern. See B.7.


B.7 — The Schedule and the Burn-Down

Handed to you in: Week 7 — wbs-template.md §2, §5–7 in the Chapter 7 companion code; the chapter’s interactive planner lets you watch the line move as you cut. Lands at: docs/plan.md §2, §5–7 · Hat: project manager · Time: 3–4 hours in Week 7, then twenty minutes every Monday.

A schedule is where the work breakdown meets the calendar and one of them loses. Almost always the work breakdown. The purpose of this document is to find out which week the plan goes over budget while it is still an addition problem, rather than in Week 13 when it has become a subtraction problem with no solution.

Three parts: the honest capacity table (hours you actually have, after chapters, quizzes, reps, and milestone write-ups), the schedule (which work packages are in flight which week, and what gates them), and the burn-down (ideal remaining against projected remaining, week by week). Then the part students skip and graders look for first: the scope decision that makes the arithmetic work.

Schedule and burn-down — template

## 2. Capacity — Weeks <N>–16

| Week | Chapter, quiz, reps, milestone write-up | Available for this plan |
|---|---:|---:|
| **Total** | | |

Declared project buffer: <n>% of available = <n.n> h
Plannable effort (available − buffer) = <n.n> h

## 5. Schedule

| Week | Work packages in flight | Planned hours | Gate / dependency |
|---|---|---:|---|

Rules: risky work first · integration before Week 12 · nothing new starts after Week 14.

## 6. Burn-down baseline

| Week | Capacity | Ideal remaining | Projected remaining |
|---|---:|---:|---:|

First week the plan exceeds remaining capacity: <week n>
Hours over plannable: <n.n>

## 7. The scope decision

| Cut / deferred / re-estimated | Item | Reqs | Hours recovered | MoSCoW before → after | Why |
|---|---|---|---:|---|---|

Signed: <name>, <date>. Re-baselined after any change of more than <n> hours.

Schedule and burn-down — worked (PantryPilot)

§2 Capacity, Weeks 8–16

WeekCourse load (chapter, quiz, reps, write-up)Available for the project
8 — design review + midterm checkpoint114
9 — walking skeleton312
10 — iteration one312
11 — testing312
12 — iteration two (−3, the broken week the charter named in Week 1)39
13 — documentation312
14 — deployment and handoff312
15 — presentation prep96
16 — delivery day105
Total4884

Declared project buffer: 20% of 84 = 16.8 h. Plannable effort = 67.2 h.

§6 Burn-down baseline — calibrated total from B.6 is 125.0 h against 67.2 h plannable.

Both columns are the hours remaining at the start of that week. Ideal burns the 67.2 plannable hours at the week’s capacity; projected burns the 125.0 calibrated hours at the same rate.

WeekCapacityIdeal remainingProjected remaining (before the cut)
8467.2125.0
91263.2121.0
101251.2109.0
111239.297.0
12927.285.0
131218.276.0
14126.264.0
1560.052.0
1650.046.0

The ideal line reaches zero partway through Week 14 rather than at Week 16. That is not an error — it is the 16.8-hour buffer, sitting at the end of the plan where it belongs.

First week the plan exceeds remaining capacity: Week 8 — that is, immediately. Hours over plannable: 57.8. Even spending the entire buffer, the plan is 41.0 hours too big.

§7 The scope decision

ActionItemReqsHours recoveredMoSCoWWhy
CutWP-5 recipe suggestion, entire packageFR-REC-01, FR-REC-0214.7Could → Won’tThe only Could-priority package. Zero Musts depend on it. It is also the highest-novelty work in the plan, so it carries the widest spread. Moved to the Won’t-have list with a revisit note
CutIn-app household settings screenFR-ACC-058.5Should → Won’tAn operator can set the time zone and the member list directly; the procedure goes in docs/runbook.md §4 instead. A whole screen replaced by six lines of documentation
CutBarcode camera capture in the browserFR-SCAN-036.5Should → Won’tManual barcode entry (FR-SCAN-01) delivers the same requirement at a fraction of the cost. Camera permissions are a problem I have never solved, so I cannot estimate them
CutItem edit history / audit trailFR-INV-065.3Could → Won’tNobody interviewed asked for it. I wanted it
DeferHousehold invitations by emailFR-ACC-045.0Must — not demoted. Delivery moved to v1.1Three roommates can be seeded directly for the trial. I am not lowering its priority to make this table look better; it stays a Must that I did not deliver, and it is disclosed in README.md, in the change log, and in the retrospective
Re-estimate downWP-2 inventory CRUD, after the T-2.1 spikeFR-INV-*4.0unchangedThe spike showed the schema was simpler than feared; three tasks re-estimated downward with the spike as the evidence
Re-estimate downWP-6 verification3.0unchangedThe technical specification fixed the integration seams, so two integration tasks collapse into one
Re-estimate upWP-7 documentation−3.0unchangedHonest correction upward. Week 13 is bigger than I first wrote, and it is the largest documentation block in the rubric

Recovered: 14.7 + 8.5 + 6.5 + 5.3 + 5.0 + 4.0 + 3.0 − 3.0 = 44.0 h. New plan total: 125.0 − 44.0 = 81.0 h against 84.0 h available.

Plannable was 67.2 h, so this plan draws 13.8 h of the 16.8 h buffer — declared here, in writing, in Week 7, rather than spent silently in Week 14. Buffer remaining: 3.0 h. Signed: M. Ruiz, 2026-02-27. Re-baselined after any change of more than 5 hours.

Coach’s Note — Notice that 81.0 against 84.0 is not comfortable. It is three hours of margin across nine weeks. That is what an honest plan looks like at this point in a capstone — and it is exactly why the Monday burn-down review is twenty non-negotiable minutes for the rest of the course. A plan that ends the arithmetic with a large surplus has almost always hidden work rather than cut it.

Coach’s Note — Look at the FR-ACC-04 row again. The tempting move is to demote it from Must to Should, because then the Week-16 line “every Must shipped” is true. That is not planning; it is redefining the target after you have thrown. Deferring delivery is legitimate and gets written down. Lowering a priority to make a number look better is the beginning of a habit that ends somewhere much worse than a capstone.

Why this one works:

  • It shows the plan failing in Week 7, on paper, where failing costs nothing.
  • The buffer is declared as a percentage and then spent in writing, with the draw recorded. A buffer you spend silently was never a buffer; it was optimism with a name.
  • The scope decision uses four different verbs — cut, defer, re-estimate down, re-estimate up. That upward re-estimate on documentation is the line that tells a grader this is an honest document rather than an hours-recovery exercise.
  • Every cut names the requirement identifiers it takes with it, so the requirements specification, the traceability matrix, and the change log can all be updated from this one table.
  • Week 12’s capacity is already reduced by the broken week the charter identified in Week 1. The documents are talking to each other.

Schedule and burn-down — bad, and what it costs

## Schedule

Weeks 9-10:   Build backend
Weeks 11-12:  Build frontend
Week 13:      Testing
Week 14:      Deployment
Week 15:      Documentation and presentation
Week 16:      Submit

## Burn-down
I'll track progress on the project board.
#What is wrongWhat it costs
1No capacity numbers. Weeks are named but no hours are attached.There is nothing for the work to exceed, so the plan can never be shown to be too big. It will still be too big. You just will not find out until Week 13.
2Phases, not deliverables — and back-to-back phases at that.”Build backend” for two weeks means nothing is demonstrable end to end until Week 12. That is the opposite of the walking skeleton discipline in Chapter 9, and it puts every integration risk in the last third of the course.
3Testing is one week, after all construction.The first defect found in Week 13 is a defect in code written in Week 9, which you no longer remember. Cost of a fix rises steeply with the distance from when it was written; this schedule maximizes that distance on purpose.
4Documentation shares a week with the presentation.Two of the three graded components, in one week, at the end, competing. One of them will be done badly and it will be the one worth more.
5No buffer.Every plan without a declared buffer has an undeclared one, taken from whatever comes last. Here that is documentation and the presentation.
6No gates or dependencies.Nothing says “CI must be green before feature work merges” or “integration must be complete before Week 12 ends.” Without gates, a schedule is a wish list in date order.
7”I’ll track progress on the project board” instead of a burn-down.A board shows what is moving. A burn-down shows whether you will finish. They answer different questions, and only one of them is answerable in Week 10 while there is still time to act.
8No scope decision at all.The single most-looked-for artifact of Week 7. Its absence says the arithmetic was never done — and a grader who suspects the arithmetic was never done reads everything after it with less trust.

B.8 — The Risk Register

Handed to you in: Week 7 — risk-register-template.md in the Chapter 7 companion code, with worked rows and scales. Lands at: docs/risk-register.md · Hat: project manager · Time: 2–3 hours in Week 7, then ten minutes every Monday.

A register you write once is a document. A register you review weekly is a control. The difference is the trigger column: an observable event — a number, a date, a log line — that tells you the risk is happening rather than merely possible.

Use these scales so your numbers mean the same thing in Week 14 as they did in Week 7. 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 the project cannot ship. Exposure = L × I. Order by exposure; work the top five. Response is one of avoid, mitigate, transfer, accept.

Eight to twelve rows is right for a solo capstone. Fewer than six means you have not looked.

Risk register — template

| Field | What goes in it |
|---|---|
| ID | R-01, stable forever, never reused |
| Risk | cause → uncertain event → consequence, in one sentence |
| Category | technical · dependency · scope · schedule/personal · data/legal |
| L / I / E | likelihood, impact, exposure (L × I) |
| Trigger | the observable thing that says it is happening — a number, a date, a log line |
| Owner | you, by name; a risk with no owner is a wish |
| Response | avoid / mitigate / transfer / accept + the concrete action, with a due week |
| Contingency | what you do if it happens anyway |
| Status | open · retired · became an issue on <date> |

Risk register — worked (PantryPilot)

Four rows of nine, ordered by exposure, as they stood after the Week-11 review.

R-01 — Because the barcode lookup service is a free third-party API whose rate limit I have not confirmed, calls may start failing during Week-12 integration testing, and FR-SCAN-01 could not be demonstrated in the final demo. · dependency · L 3 · I 4 · E 12 · Trigger: two consecutive days where my daily call count exceeds 60% of the documented cap, or any 429 response in the logs. · Owner: M. Ruiz. · Response: mitigate, by Week 10 — cache every lookup by barcode locally (T-4.2) and commit a fixture file of 50 known products so the demo never touches the network. · Contingency: manual entry (FR-SCAN-01 manual path) is already specified, already estimated, and already in the plan. · Status: open. Reviewed 2026-03-23.

R-02 — Because I have never deployed to this host, the Week-14 deployment may take far longer than the 3.7 hours estimated, eating the documentation time in the same week. · technical/novelty · L 4 · I 3 · E 12 · Trigger: the Week-9 hello-world deploy (T-0.2) takes more than 3 h. · Owner: M. Ruiz. · Response: avoid — deploy the walking skeleton in Week 9, not Week 14, so the unknown is retired five weeks early. · Contingency: fall back to the host used in a previous course; ADR-0002 names it as the alternative. · Status: retired 2026-03-11. The Week-9 deploy took 3.0 h. The unknown is now known.

R-05 — Because the household time-zone policy (ADR-0004) touches every date-handling site, a missed site may produce an off-by-one expiry that is invisible in testing and visible in the demo. · technical · L 3 · I 3 · E 9 · Trigger: any defect logged whose root cause is a direct system-clock call outside the boundary function. · Owner: M. Ruiz. · Response: mitigate — lint rule plus a test asserting no module outside the boundary calls the clock (T-3.5), and one acceptance criterion (FR-EXP-02 AC3) that specifically covers expiry == today. · Contingency: if it happens after Week 14, it ships as a documented defect with a workaround rather than a hurried fix during the release freeze. · Status: became an issue on 2026-03-24 → DEF-014. The trigger fired exactly as written; the contingency was not needed because the fix was a one-line window boundary. Regression test TC-021b added. Risk remains open at reduced likelihood (L 2, E 6) until the Week-14 pass.

R-07 — Because I am the only person who has ever run this deployment, if I am unavailable in the final week nobody can produce the release. · schedule/personal · L 2 · I 4 · E 8 · Trigger: any week where I do not complete the Monday review. · Owner: M. Ruiz. · Response: transfer — write docs/runbook.md in Week 13 to the standard that a stranger can execute it, and have one classmate actually run the deploy section end to end in Week 14 without my help. · Contingency: the recorded demo required by Chapter 15 covers the presentation half; the release half has no contingency, which is why the response is scheduled rather than optional. · Status: open.

Why this one works:

  • Every row is cause → uncertain event → consequence. “The API might fail” is not a risk. “Because the limit is unconfirmed, calls may fail in Week 12, and FR-SCAN-01 could not be demonstrated” is one you can act on.
  • Every trigger is observable by somebody other than you: a 429 in the logs, a deploy that ran past three hours, a missed Monday review.
  • R-02 is retired, with the date and the evidence. A register where nothing ever changes status was not being reviewed.
  • R-05 became an issue and says so, with the defect number, what actually happened, and the residual likelihood. That single row is the most credible thing in the document, because it proves the register was live.
  • Responses name a verb, an action, and a week. “Mitigate by Week 10 — cache lookups (T-4.2)” ties the register to the work breakdown, so the mitigation has hours.
  • R-07 covers the person, not the software. Bus factor is a real risk on a solo project and almost nobody writes it down.

Risk register — bad, and what it costs

| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Running out of time | High | High | Work harder |
| Technical difficulties | Medium | High | Research solutions |
| Scope creep | Medium | Medium | Stay focused |
| Losing data | Low | High | Make backups |
#What is wrongWhat it costs
1”Running out of time” has no cause and no specific event.Nothing could make it true or false, so nobody can tell whether it is happening. It cannot be detected — only discovered, in Week 15, as a catastrophe.
2No triggers anywhere.This is the defect that makes the whole table decorative. Without a trigger, a register is a list of anxieties. With one, it is a monitoring system.
3”High/Medium” instead of scored exposure.You cannot order the rows, so you cannot work the top five, so you work whichever one you happened to think of last.
4”Work harder” is not a response.It names no action, no week, and no cost. It is also, specifically, the response that does not work: if the plan is 40 hours over, there are no 40 extra hours to find.
5”Research solutions” and “stay focused” are not actions either.They cannot be scheduled, so they get no hours in the WBS, so they never happen.
6”Make backups” with no command, cadence, or restore test.A backup you have never restored is not a backup. The runbook (B.13) is where this becomes real, and this row never gets it there.
7No owner, no status, no dates.Nothing here can be reviewed, so nothing here will be. By Week 12 this table is exactly as it is now, and everyone can tell.
8Every risk is generic.Not one row mentions this project. No third-party 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.

B.9 — The Test Plan

Handed to you in: Week 11 — test-plan-template.md in the Chapter 11 companion code, with a traceability matrix and a coverage reporter. Lands at: docs/test-plan.md · Hat: tester · Time: 3–4 hours to write, at the front of Week 11.

The test plan is written before the test suite, and its 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 ten concurrent users, because there are three users and no evidence of more” is a plan a grader trusts — and trust is the currency of the verification line in the rubric.

The second most valuable section is exit criteria, because those are the numbers you will be held to in Week 16. Write them when you are calm.

Test plan — template

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

## 1. Scope — what is under test
Name the build (commit SHA or tag) and the requirement IDs this plan covers.

## 2. Out of scope — what is NOT under test, and why
Each line gets a reason, not an apology.

## 3. Test levels
| Level | What it covers here | How it runs | Roughly how many |
|---|---|---|---|
| Unit | | `<command>` | |
| Integration | | `<command>` | |
| Acceptance | | `<command or manual script>` | |

## 4. Environment
| Item | Value |
|---|---|
| Runtime / language version | |
| Operating system(s) | |
| Database / storage, and how it is seeded | |
| Third-party services: real, sandbox, or stubbed | |
| Test data set: path in repo + how it is generated | |
| Where it runs: local + CI workflow name | |

## 5. Entry criteria — we may begin testing when
## 6. Exit criteria — verification is DONE when   (numbers, not adjectives)
## 7. Risks to the testing itself
## 8. Schedule
## 9. Approval
I have read this plan and I am accountable for the exit criteria above.
Signed: <you> · Date: <YYYY-MM-DD>

Test plan — worked (PantryPilot)

§1 Scope. This plan covers FR-ACC-01 through FR-SCAN-04 and NFR-PERF-01, NFR-SEC-01, NFR-SEC-02, NFR-ACC-01, NFR-MNT-01 — 23 functional and 5 non-functional requirements — at commit a1b2c3d, tagged v0.8.0. The five requirements withdrawn in the Week-7 scope decision (FR-REC-01, FR-REC-02, FR-SCAN-03, FR-ACC-05, FR-INV-06) are not covered and are not in the build. FR-ACC-04 is still a Must and is still not implemented; it is tracked as an open gap, not as a passing test.

§2 Out of scope, and why.

Not testedWhy not
Concurrent load beyond 5 simultaneous sessionsThree real users; no requirement asserts more. NFR-PERF-01 is a single-user latency target and is tested as one
Browsers other than the two named in NFR-PORT-01The requirement names two; testing a third would be testing something nobody promised
The product-lookup service’s own correctnessNot my software. My requirement (FR-SCAN-02) is about its failure, and that is tested with a stub
Screen-reader announcement qualityNFR-ACC-01 is a keyboard-operability requirement, deliberately scoped that way in Week 4 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 the restore; I have executed it once from a clean backup. Corruption recovery is out of scope and is named as a known gap in docs/handoff.md

§3 Test levels

LevelWhat it coversHow it runsCount
Unitexpiry window arithmetic, currentHouseholdDate(), quantity/unit normalization, validation rulesnpm test -- unit41
Integrationdatabase layer against a real ephemeral database; lookup client against a stub that can hang, 500, and return junknpm test -- integration18
Acceptanceone script per acceptance criterion on every Must; four run manually (keyboard, contrast, clean-machine, throttled performance) and are recorded with date and testernpm test -- acceptance + docs/manual-tests.md29 automated, 4 manual

§4 Environment. Runtime pinned in .tool-versions; database version pinned in docker-compose.yml; seed data generated by script/seed from tests/fixtures/pantry-200.json (200 items, deterministic, committed); the lookup service is stubbed in all automated tests and exercised live exactly once per release, manually, recorded in docs/test-results.md; runs locally and in CI on every push (workflow ci.yml).

§6 Exit criteria — verification is done when:

  • 100% of Must requirements have at least one passing acceptance test, listed by ID in docs/test-results.md.
  • 0 open defects at severity S1 or S2.
  • Every open S3/S4 defect appears in docs/defect-log.md with a documented workaround.
  • Every defect fixed this iteration has a named regression test that fails on the old code — verified by checking the test out against the pre-fix commit.
  • The full suite runs from one documented command and passes in CI on the merge commit.
  • NFR-PERF-01 measured and recorded within the last 7 days, at the current commit.
  • The keyboard pass (NFR-ACC-01) completed on the three core flows, with the date and the tester’s name.
  • The secret scan over full history returns zero findings.

Signed: M. Ruiz, 2026-03-23.

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 test plan against a moving target.
  • The environment section makes the suite reproducible by someone else: pinned versions, deterministic committed fixtures, and an explicit statement that the third-party service is stubbed. That last one is the difference between a suite that passes in CI and a suite that passes when the vendor is having a good day.
  • 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 of them 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 11.
#What is wrongWhat it costs
1”Thoroughly” and “everything.”Fails the number test in the first sentence. Nothing here can be shown to be complete, so the verification line in the rubric has nothing to award against.
2No build under test. No commit, no tag.Results cannot be reproduced or even located. In Week 16 you cannot prove that the passing run corresponds to the code you submitted.
3No requirement identifiers.No traceability. You cannot answer the single question a grader asks first: which requirements are verified, and which are not?
4No 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. Experienced reviewers go looking for the gap, find it, and then doubt everything else.
5”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 — every Must has a passing acceptance test — and let line coverage be a hint about where you have not looked.
6No environment.The suite passes on your laptop. That is all anybody will ever be able to say about it.
7No entry or exit criteria.Testing is “done” when Week 11 ends, which means it is done when you run out of week, not when the software is verified.
8No test data.Tests will be written against whatever happened to be in your development database, which means they encode its accidents and cannot run anywhere else.
9”Fix any bugs that we find.”No severity, no priority, no triage, no log. See B.10 — this sentence is where a missing defect log comes from.

B.10 — The Defect Report and the Defect Log

Handed to you in: Week 11 — defect-report-template.md in the Chapter 11 companion code. Lands at: docs/defect-log.md, with the full report in the issue tracker · 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, not a defect report.

Two ladders, and the distinction between them is one of the genuinely professional habits this course teaches.

Severity — a fact about the productPriority — a decision about your remaining hours
S1Data loss, security exposure, or it will not runP1Fix now, next thing you touch
S2A Must requirement is not met; no acceptable workaroundP2Fix this iteration
S3A requirement partly met, or a real workaround existsP3Fix if hours remain; otherwise ship as a known defect
S4Cosmetic, wording, or a Could-priority gapP4Will not fix — recorded, with the reason

In industry those are set by two different people. Here they are set by the same person wearing two different hats — so write down which hat you were wearing, because an S2/P3 is a decision you will have to defend in Week 16.

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/browser and 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 should happen, quoted>
Actual:   <what happened, observably>

Evidence: <failing test name, log excerpt, screenshot path, request/response>

Severity: <S#> — <one line of reasoning about the product>
Priority: <P#> — <one line of reasoning about the calendar>

Status:   <Open | Fixed — commit <sha> | Deferred | Won't fix — reason>
Regression test: <TC-nnn — the test that fails on the old code>
Owner:    <you>

Defect log table (paste into docs/defect-log.md):

| ID | Title | Req | Sev | Pri | Found by | Status | Fix commit | Regression test |
|---|---|---|---|---|---|---|---|---|

Defect report — worked (PantryPilot)

ID:          DEF-027
Title:       Add-item form blocks for 30 s when the lookup service is unreachable
Reported:    2026-03-25 by M. Ruiz
Environment: commit 7c9e11a, macOS 15, Node 22 (pinned in .tool-versions),
             seed data set `pantry-200`, lookup client pointed at the hang-stub
Requirement: FR-SCAN-02 (AC1)      Test case: TC-034

Steps to reproduce:
  1. Start the app with LOOKUP_BASE_URL pointed at tests/stubs/hang (accepts the
     connection, never responds).
  2. Sign in as any household member and open Add item.
  3. Enter barcode 0123456789012 and submit.
  4. Start a stopwatch.

Expected: FR-SCAN-02 AC1 — "within six seconds the manual form appears, the
          barcode is still in the field, and a message states that lookup is
          unavailable."
Actual:   The form is unresponsive for 30.4 s (the runtime's default socket
          timeout), then the manual form appears with the barcode field EMPTY
          and no message.

Evidence: TC-034 fails on the six-second assertion. Log:
          `lookup: request start t=0.00 ... socket timeout t=30.41 fallback=manual`
          No explicit timeout is set on the lookup client
          (src/clients/lookup.js line 24). Screenshot: docs/img/def-027.png

Severity: S2 — a Must requirement is not met and there is no workaround; the
          user is simply stuck. This is also the exact failure mode R-01 was
          written about.
Priority: P1 — it is on the demo path, the fix is a timeout constant plus
          preserving one field, and every day it stays open is a day the
          acceptance suite is red. (Hat: project manager. Severity above was
          assigned wearing the tester hat.)

Status:   Fixed — commit 4d2f81b (5 s timeout via AbortSignal; barcode carried
          into the manual form; message added).
Regression test: TC-034b — asserts a response before 6 s AND that the barcode
          field is non-empty. Verified to FAIL against 7c9e11a.
Owner:    M. Ruiz

The defect log, as it stood at the Week-14 release

IDTitleReqSevPriFound byStatusFix commitRegression test
DEF-014Expiring-soon view drops items expiring todayFR-EXP-02S2P1acceptance testFixede4f5a6bTC-021b
DEF-027Add-item form blocks 30 s when lookup is unreachableFR-SCAN-02S2P1test plan §3 stubFixed4d2f81bTC-034b
DEF-031Keyboard focus lost after marking an item consumedNFR-ACC-01S2P2manual keyboard passFixed9a7c204TC-041
DEF-034Invite to an existing member returns 500FR-ACC-04S3P2integration testFixedb31d0e8TC-039
DEF-036Expiring-soon view unpaginated; slow past ~800 itemsNFR-PERF-01S3P3performance passOpen — shipping
DEF-041Past expiry date silently discarded on addFR-INV-01S2P1acceptance testFixedc0d5f19TC-018b
DEF-044Empty-state copy says “no items” on a non-empty filterFR-EXP-02S4P4self-reviewWon’t fix — wording only, recorded

DEF-036, the one that ships. Severity S3, priority P3. Workaround: filter by storage location. The pantry that triggers it is roughly four times the largest real household pantry observed in the Week-12 trial. It is listed in README.md under known limitations, in CHANGELOG.md under known issues, and in docs/handoff.md §5 with an estimate of the remaining work (2.5 h: paginate the query, add a page control, extend TC-052).

Why this one works:

  • The steps to reproduce start from a known state and include starting a stopwatch. Somebody else can run them.
  • Expected is quoted from the acceptance criterion, not paraphrased from memory. That is the whole payoff of writing criteria in Week 3.
  • The evidence names a failing test, a log line, a source location, and a screenshot. Four kinds of evidence, none of which is “trust me.”
  • Severity and priority carry separate reasoning, and the report names which hat assigned which.
  • The regression test was verified to fail against the pre-fix commit. A regression test 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 effort estimate for the next owner. 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. I think it's a
timezone 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.”Isn’t showing everything” on which build, which data, which machine? Not reproducible, and a defect that is not reproducible is not fixed — it is unobserved.
3No steps to reproduce.Future-you, in three weeks, cannot confirm the fix. Neither can a grader.
4No expected versus actual.”Isn’t showing everything it should” hides the entire question of what it should show. That is what the acceptance criterion was for, and it is not cited.
5”I think it’s a timezone thing?”A guess where evidence belongs. No log line, no failing test, no source location. Guesses in defect reports get fixed by changing things until the symptom stops, which is how you get a second defect.
6No severity, no priority.Nothing can be triaged. With twenty of these you will fix whichever one annoyed you most recently rather than whichever one is S1.
7No requirement, no test case.Not traceable in either direction. In Week 16 you cannot show that a Must requirement’s failure was found, fixed, and verified.
8”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.
9The tone.This is a note to yourself. It reads like one, and it reads that way to the person assigning the 50%.

B.11 — The Hours Log

Handed to you in: Week 1 — hours-log.csv and hours_report.py in the Chapter 1 companion code; Chapter 10 adds a reporting script that runs the honesty checks a grader runs. Lands at: docs/hours-log.csv · Every hat · Time: ninety seconds per session. Before you close the laptop.

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

It is also the only document in this kit that cannot be reconstructed. Every other artifact can be written late, badly, at 2 a.m. This one cannot, and the scripts in Chapters 1 and 10 will tell you so.

Hours log — template

date,start,end,hours,phase,hat,task,estimate_hours,blocked_hours,notes
2026-01-13,19:00,21:15,2.25,inception,project-manager,create repo + scaffold docs/ tree,1.50,0.00,underestimated .gitignore research
ColumnWhat goes in it
dateYYYY-MM-DD, the day the work happened
start, endReal clock times. They are what stop you from 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
blocked_hoursHours inside the session where you were stuck on something outside your control
notesOne clause. Especially: why it took longer than you thought

Run python3 hours_report.py docs/hours-log.csv for hours by week, by phase, by hat, the running total against 240, and the estimate-to-actual ratio.

Hours log — worked (PantryPilot)

Ten consecutive rows from Week 7, when the plan was being written and the arithmetic went bad.

date,start,end,hours,phase,hat,task,estimate_hours,blocked_hours,notes
2026-02-23,19:00,20:30,1.50,planning,project-manager,read ch7 + on-page quiz,1.50,0.00,
2026-02-24,18:45,21:30,2.75,planning,project-manager,T-plan.1 WBS to task level WP-0..WP-4,2.00,0.00,remembered CI + seed data + a11y - added WP-0 entirely
2026-02-25,20:00,21:15,1.25,planning,project-manager,T-plan.1 WBS WP-5..WP-8,1.00,0.00,
2026-02-26,17:30,20:45,3.25,planning,project-manager,three-point estimates on 51 tasks,2.50,0.50,blocked 30m on T-3.5 - spread came out 4.0 so I flagged it for an ADR instead of guessing
2026-02-26,21:00,21:45,0.75,planning,project-manager,calibration factor from weeks 1-6 log,0.50,0.00,1.14x - worse than I expected and I checked it twice
2026-02-27,09:00,11:30,2.50,planning,project-manager,capacity table + burn-down baseline,2.00,0.00,plan is 57.8h over. bad morning
2026-02-27,13:00,15:15,2.25,planning,project-manager,scope decision - cut WP-5 and FR-SCAN-03,1.50,0.00,took longer because I argued with myself about the recipe feature for 40m
2026-02-28,10:00,12:00,2.00,planning,project-manager,risk register 9 rows with triggers,2.00,0.00,
2026-02-28,14:00,15:30,1.50,planning,writer,milestone 7 write-up + board updated,1.00,0.00,
2026-03-01,19:00,20:00,1.00,planning,project-manager,re-baseline plan.md v1.1 + sign,0.75,0.00,

Ten sessions, 18.75 hours against 14.75 estimated — a local ratio of 1.27, which is why the semester-to-date calibration factor moved to 1.14.

What the report says about this log:

BY PHASE
  planning                          18.75 h
BY HAT
  project-manager                   17.25 h
  writer                             1.50 h

BUDGET
  logged                           104.75 h  (43.6% of 240)
  remaining                        135.25 h
  blocked (in the logged total)       0.50 h
  estimated                         91.50 h
  estimate-to-actual ratio            1.14  (1.00 = calibrated)

Why this one works:

  • The hours are not round. 2.75, 3.25, 0.75. Real work is not round, and a log full 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 at all. Fill this column after the fact and the number it produces is a lie you will plan on.
  • The notes explain the overruns specifically: “remembered CI + seed data + a11y,” “argued with myself for 40m.” Those two clauses are the raw material of the Week-16 retrospective, and they cannot be recovered later.
  • A 0.75-hour session appears. Students routinely fail to log short sessions, and short sessions are 15–20% of a semester.
  • The blocked column is used honestly and sparingly.

Hours log — bad, and what it costs

date,hours,description
2026-02-23,3,worked on project
2026-02-25,4,worked on project
2026-02-28,3,worked on project
2026-03-08,12,catching up
2026-03-15,10,catching up
2026-03-22,8,project work
#What is wrongWhat it costs
1Every value is a whole number.The Week-10 report flags this outright: “6/6 entries are whole numbers. Real work is not round.” It is the clearest signal in the file that these numbers were remembered rather than recorded.
212 hours in one day, then 10, then 8.The script flags any day over 10 hours as a backfill tell. A twelve-hour day is possible; three of them clustered after silences is not a work pattern, it is a reconstruction.
3”Worked on project” three times.Flagged for repetition. It also destroys the log’s only real value — you cannot tell what took the time, so you cannot learn anything about your own estimating.
4No estimate_hours.No calibration factor is computable. The Week-7 plan therefore has no calibration, which means it is built on raw estimates from a person who has never estimated this work.
5No phase and no hat.No phase distribution. You cannot answer the question the retrospective asks: where did 240 hours actually go?
6A fifteen-day silence between 2026-02-28 and 2026-03-15, then a ten-hour “catching up.”The script prints the gap by name. 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.
7Six rows for six weeks.A real log has roughly three to five rows a week. Six rows covering a month and a half is a log written in one sitting, and it reads that way.

The cost is not just the hours-log line in the Week-16 rubric (Appendix C). It is that a grader who catches a reconstructed hours log now 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.


B.12 — The README

Handed to you in: Week 13 — readme-template.md in the Chapter 13 companion code, alongside a documentation linter and the clean-machine protocol. Lands at: README.md at the repository root · Hat: technical writer · Time: 3–4 hours, and it is the highest-leverage block of time in Week 13.

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. Every other purpose is secondary. If a grader cannot run your software, they do not grade your architecture generously and dock you a few points for a thin README. They grade what they can observe, and what they can observe is nothing.

The 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. Chapter 13 makes you run it and record the result.

README — template

# {{PROJECT NAME}}

> {{ONE SENTENCE: what it does and who it is for. Not what you wish it did.}}

**Status:** {{Working / Partially working — say plainly what does not work yet}}
**Built by:** {{Your name}} · {{Course, term}}

## 1. What this is and who it is for
{{2–4 sentences: the problem, the user, the thing it replaces.}}
**What it does not do:** {{2–3 honest non-goals. This is the part that earns trust.}}

## 2. Demo
{{Screenshot, GIF, or a 60–90 second recording; for a CLI, a real terminal session.}}

## 3. Prerequisites
| Requirement | Version built and tested on | Check it with | What breaks without it |
|---|---|---|---|

## 4. Install
Expected: {{the last line of successful output}}. Takes about {{N}} minutes.

## 5. Configure
| Variable | Required? | What it is | Default |
|---|---|---|---|
Never commit .env. Placeholder values only, in this file and in .env.example.

## 6. Run
Expected: {{exact success line}} — then open {{URL}} and you should see {{what}}.
**Verify it actually works:** {{one concrete action a stranger can take, and the
result they should get. This is the difference between "it started" and "it works."}}

## 7. Test
Expected: {{N}} tests, {{N}} passing, {{N}} skipped, about {{N}} seconds.

## 8. Project layout
## 9. Troubleshooting
| Symptom (exact error text) | Likely cause | Fix |
|---|---|---|
## 10. License        ## 11. Contact and further reading

README — worked (PantryPilot)

Sections 1–7 of README.md, condensed. Note what is in section 6.

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

Status: Working. Household invitations by email are not implemented (FR-ACC-04, deferred to v1.1); members are added by an operator using the runbook. The expiring-soon view is unpaginated and slows past roughly 800 items (DEF-036, workaround below). Built by: M. Ruiz · Senior Capstone, Spring 2026

1. What this is and who it is for. PantryPilot is a small web application for a shared household — three to six people who buy groceries together and throw food away because nobody remembers what is in the back of the fridge. It replaces a whiteboard. One person adds items as they are unpacked; everybody can see what expires this week, sorted soonest first; anyone can mark something consumed.

What it does not do: it does not track prices or spending, it does not do nutrition or allergen information, it does not have a mobile app, and it does not support more than one household per account. Each of those was refused deliberately and the reasons are in docs/requirements.md §7.

3. Prerequisites

RequirementBuilt and tested onCheck it withWhat breaks without it
Node runtimethe version pinned in .tool-versionsnode --versionInstall step fails at dependency resolution
PostgreSQLthe version pinned in docker-compose.ymlpsql --versionMigrations fail with role does not exist
A product-lookup API keyoptionalsee §5Barcode lookup is unavailable; manual entry works normally and the app tells you so

4. Install

git clone https://github.com/<owner>/pantrypilot.git
cd pantrypilot
npm ci
docker compose up -d db
npm run migrate

Expected last line: migrations: 7 applied, 0 pending. About 4 minutes on a first run, most of it the database image.

5. Configure

cp .env.example .env
VariableRequired?What it isDefault
DATABASE_URLyesConnection string; the compose file’s default works as-isnone
SESSION_SECRETyesAny random 32+ character string. openssl rand -hex 32none
LOOKUP_API_KEYnoProduct-lookup service key. Without it, barcode lookup is disabled and the manual form is usedunset
HOUSEHOLD_TZ_DEFAULTnoIANA zone for new households (see ADR-0004)America/Chicago

Never commit .env. The values in .env.example are placeholders and are not live credentials.

6. Run

npm run seed        # optional: 200 demo items
npm start

Expected: pantrypilot listening on http://localhost:3000 — open that URL and you should see the sign-in page.

Verify it actually works. Sign in as demo@example.com / demo-password-change-me (created by the seed script). Add an item named milk with quantity 1, unit carton, and an expiry date two days from today. Then open Expiring soon. milk must appear at the top of that list. If it does not, the time-zone configuration is wrong — see Troubleshooting.

7. Test

npm test

Expected: 88 tests, 88 passing, 0 skipped, about 22 seconds. Four acceptance checks are manual (keyboard operability, contrast, throttled performance, clean-machine) and are recorded in docs/manual-tests.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 6 contains a specific verification action with a specific expected result — add milk expiring in two days, see it at the top of a named list. “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.
  • The optional API key degrades gracefully and the README says so, so a grader with no key can still run and grade the software.
  • Version pinning points at files in the repository rather than restating numbers that will drift.
  • The seed credentials are obviously fake and obviously seeded.

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
- AI recipe suggestions
- 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

## Technologies
Node.js · Express · PostgreSQL · React · Docker · OpenAI
#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.Every step is a category, not an instruction. A stranger cannot follow a single one of these five lines without guessing. This is the number-one cause of a failed clean-machine test.
3No expected output anywhere.The reader cannot tell whether step 3 worked. When step 5 fails, they have no idea which of the earlier steps actually did.
4”Add your API keys to .env” — which keys? What are they called? Where do you get them? Is the app usable without them?The grader with no key concludes the software cannot be run. The software probably could have been run.
5No 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 message that names nothing in this document.
6”AI recipe suggestions” is listed — and it was cut in the Week-7 scope decision.Now the README makes a claim the software does not honor. A grader who tries it and finds nothing stops trusting the whole document, and the retrospective’s honesty is undermined before it is read.
7No status, no known limitations, no defects.Implies everything works. Two minutes of use disproves it. Admitted gaps read as professionalism; discovered gaps read as either carelessness or concealment.
8”Modern,” “full-stack,” “responsive.”Adjectives aimed at an imaginary recruiter rather than a real reader who is trying to run your software right now.
9A technology list with no link to the decisions.Six technologies, zero reasons. docs/adr/ exists to answer “why these?” and this file never points at it.
10No verification step.Even if the app starts, nothing tells the reader what success looks like. They will click around, see an empty screen — because they did not seed — and conclude it is broken.

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


B.13 — The Runbook

Handed to you in: Week 13 — runbook-template.md in the Chapter 13 companion code. Lands at: docs/runbook.md · Hat: release engineer · Time: 2.5–3 hours, 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. The runbook is a set of procedures, not an explanation.

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 — template

# Runbook — {{PROJECT NAME}}

## 1. Where it runs
| Environment | Where | Who has access | How to get in |
|---|---|---|---|
Where the data actually lives: {{host, database, bucket}}.
Where the logs actually live: {{path or console}}.

## 2. Start, stop, restart      (with the expected success line for each)

## 3. Health check
The one thing you run to answer "is it alive?"
Healthy looks like: {{exact output}}
Unhealthy looks like: {{exact output}} → go to section 6.

## 4. Routine operations
| Task | Command | How often | Expected result |
|---|---|---|---|
Back up · restore · apply a schema change · rotate a secret · seed demo data

## 5. Normal readings
So the operator can tell abnormal from normal: startup time, typical response
time, log lines to safely ignore, known limits and what happens at them.

## 6. Failure catalogue    (one block per failure, written from your defect log)
### F-01 — {{short symptom, in the operator's words}}
- Symptom · First check · Likely cause · Fix · Verify the fix · If that does not work

## 7. Escalation and the things only a human can decide

## 8. Last verified
Executed end to end on {{date}} by {{name}} on {{machine/OS}}.

Runbook — worked (PantryPilot)

§3 Health check

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

Healthy: {"status":"ok","db":"ok","migrations":"7/7","uptime_s":<n>} — HTTP 200, returns in under 500 ms. Unhealthy: HTTP 503 with {"status":"degraded","db":"unreachable"}, or no response at all. → §6.

§5 Normal readings. Cold start about 6 s (free-tier instances sleep when idle; the first request after a sleep takes 8–15 s and this is expected, not a fault). Expiring-soon view typically 200–400 ms warm at 200 items. lookup: cache hit appears on most add-item requests and is normal. Known limits: the expiring-soon view is unpaginated (DEF-036) and gets slow past roughly 800 items — the view stays correct, it just takes several seconds.

§6 Failure catalogue

F-01 — “Everything is expiring today” or “nothing shows as expiring”

  • Symptom: the expiring-soon view is either empty or full, and the dates in the item list look one day off.
  • First check: psql "$DATABASE_URL" -c "select id, name, time_zone from households;" — is time_zone a valid IANA identifier, or is it null/empty?
  • Likely cause: a household created before migration 0009 has no zone, so the boundary function falls back to the server’s zone (ADR-0004).
  • Fix: psql "$DATABASE_URL" -c "update households set time_zone='America/Chicago' where time_zone is null or time_zone='';"
  • Verify: reload the expiring-soon view; an item expiring today appears first. Or run npm test -- acceptance -t "expiry boundary".
  • If that does not work: the server clock may be wrong. date -u on the host. Escalate per §7.

F-02 — Add-item form hangs, or barcode lookup returns nothing

  • Symptom: submitting a barcode does nothing for several seconds, or the manual form appears with a “lookup unavailable” message.
  • First check: curl -sS -o /dev/null -w '%{http_code} %{time_total}\n' "$LOOKUP_BASE_URL/ping" and grep -c '429' <log path> for the last day.
  • Likely cause: the lookup service is down, or the key is exhausted or revoked (R-01).
  • Fix: none required — this is the designed degraded path (FR-SCAN-02). Manual entry works. If 429s are present, stop using the live service for demos and set LOOKUP_FIXTURES=1 to serve the committed 50-product fixture.
  • Verify: add an item manually; it saves and appears in the pantry list.
  • If that does not work: the failure is not in the lookup client. Check /healthz and go to F-03.

F-03 — Database unreachable (/healthz returns 503)

  • Symptom: every page returns an error; /healthz reports db: unreachable.
  • First check: psql "$DATABASE_URL" -c "select 1;" from the app host.
  • Likely cause: the managed database was paused for inactivity, credentials rotated, or the host’s IP allow-list changed.
  • Fix: resume the instance from the provider console; if credentials changed, update the secret in the host’s secret store and restart. Do not edit .env on the running instance — it is rebuilt on deploy and your change will vanish.
  • Verify: /healthz returns 200 with db: ok, then add and delete one test item.
  • If that does not work: stop. Do not restore from backup to fix a connectivity problem. §7.

§7 Escalation. Data loss suspected → stop, do not restart, do not restore. Take a copy of the current data directory or a fresh dump first; a restore over live data destroys the evidence and possibly the only good copy. A secret may be exposed → rotate first, investigate second; steps in §4. Third-party outage → check the vendor’s status page; the system degrades to manual entry and stays usable. Contact: M. Ruiz — and after handoff, the maintainer named in docs/handoff.md.

§8 Last verified. Executed end to end on 2026-04-14 by A. Reyes (a classmate, not the author) on Windows 11 + WSL2, against the deployed instance. Backup and restore verified with a real restore into a scratch database. Secret rotation UNVERIFIED — documented but not executed, because rotating the live key would have broken the demo instance; the procedure was reviewed 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.
  • §5 tells the operator what normal is, including a slow cold start that would otherwise be diagnosed as a fault. Most runbooks describe failure and never describe normal, which makes every anomaly look like an incident.
  • Every failure block came from the defect log or the risk register — F-01 from ADR-0004 and DEF-014, F-02 from R-01. Failure catalogues written from imagination describe failures that never happen and miss the ones that do.
  • F-02’s fix is “none required — this is the designed degraded path.” Telling an operator that something is working as intended is a real and often-missing entry.
  • 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 the CI pipeline 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. Everything after this is guesswork.
2”Check the logs” with no path, no command, no idea what to look for.Logs on a managed host are often not on disk. An operator who does not know where they live cannot start. And “what a normal log line looks like” is missing, so they 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 can destroy the evidence and the recovery path. That is why the good version says stop in bold.
5No failure catalogue.The three failures that actually happen to this system — the time-zone default, the lookup outage, the paused database — appear nowhere, even though two of them are already in the defect log and the risk register. The information existed. Nobody moved it.
6No secrets, rotation, or access section.If a key leaks the day after handoff, nobody knows where it lives or how to replace it.
7Nothing was ever executed, and there is no “last verified” line.This document is a set of assumptions about your own system. Assumptions are what a runbook exists to replace.
8Written for the author.Every line assumes you already know the deployment. The one reader who needs this file is the one who does not.

B.14 — The Onboarding Guide and the Handoff Guide

Handed to you in: the onboarding guide in Week 13 (Chapter 13); the handoff guide in Week 14 — handoff-template.md in the Chapter 14 companion code. Lands at: docs/onboarding.md and docs/handoff.md · Hats: technical writer, then release engineer · Time: 2 hours and 3 hours respectively.

These are two documents for two different successors, and students merge them into one mediocre file every semester. Separate them now.

Onboarding guideHandoff guide
ReaderThe next developerThe next owner
Question it answers”How do I make my first change safely?""What am I now responsible for?”
ContentsVocabulary, feature-to-code map, a real first issue, how work moves, where decisions liveAccounts, keys, hosting, cost, license, known defects, bus-factor register, what happens if nobody takes it
Failure modeA tour of the architecture instead of a path to a merged changeA list of technologies instead of a list of liabilities

One is about the codebase. The other is about the custody.

Onboarding and handoff — template

# Onboarding — <project>
## 1. Ten-minute orientation   (what it does · the three files that matter · the vocabulary)
## 2. Environment in one command   (point at README.md; do NOT duplicate it)
## 3. Feature-to-code map
| Feature | Entry point | Core logic | Tests |
|---|---|---|---|
## 4. Your first good first issue   (a real, small, unclaimed task — do NOT do it yourself)
## 5. How work moves   (branch naming, commit style, what CI runs, what must be green)
## 6. Where decisions live, and how to add one

# Handoff Guide — <project>
## 0. Thirty-second orientation   (what · who uses it · status · deployed at · start here)
## 1. Day one — get it running (target: under 30 minutes)
| Step | Command | Expected result | If it fails |
|---|---|---|---|
## 2. Week one — a ramp that ends in a merged change
## 3. Accounts, keys, and who pays for what
| Thing | Where it lives | Owned by | Cost | Rotation / transfer step |
|---|---|---|---|---|
## 4. Landmines — at least five
## 5. Known defects and deferred work
## 6. AI dependencies    (model pinned to · where the prompts live · what drifts · fallback · cost owner)
## 7. What only I know — the bus-factor register
| Knowledge | Consequence if lost | Now written down at |
|---|---|---|
## 8. If nobody picks this up

Onboarding and handoff — worked (PantryPilot)

From docs/onboarding.md §1 and §3

Vocabulary — read this before the code. An item is a physical thing in a pantry: this carton of milk, expiring on this date. A product is a barcode-identified type of thing: milk, 2%, this brand. One product, many items. A household owns items and has exactly one time zone (ADR-0004). “Expiring” always means within the household’s window, never the server’s. Getting item and product confused is the single most common mistake in this codebase and it produced two defects.

FeatureEntry pointCore logicTests
Add an itemsrc/routes/items.jssrc/services/items.jstests/items.test.js
Expiring soonsrc/routes/expiry.jssrc/services/expiry.js, src/lib/household-date.jstests/expiry.test.js, tests/household-date.test.js
Barcode lookupsrc/routes/scan.jssrc/clients/lookup.js (timeouts here)tests/lookup.test.js, tests/stubs/hang
Accounts and householdssrc/routes/auth.jssrc/services/households.jstests/auth.test.js
Migrationsmigrations/tests/migrate.test.js

Your first good first issue: #47 — “Expiring-soon empty state does not say which filter is active.” Real, small, useful, unclaimed, and it is DEF-044 in the defect log — a wording defect we chose not to fix. Open src/views/expiry.ejs and src/routes/expiry.js; the empty-state string is built in one place. Run npm test -- -t "empty state". Done means: the message names the active filter, one test asserts it, and CHANGELOG.md gets a line under Fixed with the defect ID. It should take about forty minutes. I have deliberately not fixed it.

From docs/handoff.md §3 and §7

ThingWhere it livesCurrently owned byCostRotation / transfer step
Source repositorygithub.com/<owner>/pantrypilotM. Ruiz (personal account)free tierTransfer ownership in repository settings, then re-add the two CI secrets by hand — they do not transfer
Deploy targetthe application host named in docs/deployment.mdM. Ruizfree tier as of 2026-04-14; verify with the vendor, tiers changeInvite the new owner, promote to owner, then remove me. Do not delete the service — the URL is in the README
Databasethe managed database named in docs/deployment.mdM. Ruizfree tier as of 2026-04-14; verify with the vendorTake a dump first (docs/runbook.md §4), transfer, restore, verify with /healthz
LOOKUP_API_KEYhost secret store + local .envM. Ruiz’s account with the vendorfree tierNew owner creates their own key, sets it in the secret store, deploys, confirms lookup works, then I revoke mine. In that order
SESSION_SECREThost secret store onlynoneRotating it signs out every user. Do it during a maintenance window 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. Before this project is truly handed off, every one of those names must change.

§7 — What only I know (bus-factor register)

KnowledgeConsequence if lostNow written down at
The expiry window is computed from the household zone, never the server clockOff-by-one expiry defects that pass every test written on a single-zone machineADR-0004; docs/onboarding.md §1; the lint rule in .eslintrc
The lookup client must set an explicit timeout; the runtime default is 30 sA hung form on any lookup outage — this was DEF-027src/clients/lookup.js comment; docs/runbook.md F-02; docs/test-plan.md §3
The 50-product fixture exists so demos never touch the networkA live demo that fails on somebody else’s outagetests/fixtures/products-50.json header comment; runbook F-02
Migration 0009 backfills household time zones and is not idempotentRunning it twice on a partly-migrated database sets zones back to the defaultComment at the top of the migration; docs/runbook.md §4
The seed account password is a placeholder and must be changed before any real useAn open account on a deployed instanceREADME.md §6; docs/runbook.md §7

Why this one works:

  • The vocabulary section defines item versus product and says which confusion caused defects. Nothing costs a newcomer more time than a word they think they already understand.
  • The feature-to-code map is five rows and saves more time than every other section combined.
  • The first issue is real, unclaimed, and deliberately left undone, with the file, the test command, and the definition of done. Most students write “you could add tests.” That is not an issue; it is a shrug.
  • The accounts table names a rotation order — new key live before the old one is revoked. Getting that backwards is how a handoff takes the system down.
  • Free-tier claims carry a checked date and an instruction to verify with the vendor, because tiers and prices change and this document will outlive the term.
  • The bus-factor register is the section that makes a handoff real. Each row names the knowledge, the consequence of losing it, and — critically — where it is now written down, so the register is an index rather than a confession.

Onboarding and 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 API 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.”An assertion about your own work, made by the person least able to judge it. It is also the exact claim the clean-machine test exists to check, and it is not the claim a handoff guide is supposed to make.
2”Standard patterns.”Names nothing. There is no orientation, no vocabulary, no feature-to-code map — the successor starts at the repository root with no idea which of forty files matters.
3No 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 card is charged or a service is deleted, and no one can tell which.
4”My API 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. There is no rotation procedure and no transfer order.
5”Let me know if you have questions.”The premise of the entire document is that the successor cannot ask you a question. This sentence is a promise that will not be kept in eighteen months, and it is offered instead of the writing that would have made it unnecessary.
6No known defects and no deferred work.DEF-036 ships in the release, and the successor will find it themselves — after they have trusted the document. A gap you disclose is professionalism. A gap they discover is a reason to distrust everything else.
7”Future work: add tests, improve the UI, maybe a mobile app.”Not a plan. No identifiers, no effort estimate, no priority. Compare: “DEF-036, 2.5 h: paginate the expiry query, add a page control, extend TC-052.”
8No bus-factor register.The five things only you know stay only known to you. That is the definition of a failed handoff, and it is precisely what Chapter 14 exists to prevent.
9No onboarding guide at all.Two documents’ worth of the rubric, answered with one paragraph of reassurance.

B.15 — The Change Log

Handed to you in: Week 12 — changelog-template.md in the Chapter 12 companion code, with good and bad versions of the release-candidate entry. Lands at: CHANGELOG.md at the repository root · Hat: release engineer · Time: twenty minutes per release, if you keep the Unreleased section current.

The shape follows 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 for a release candidate.

Four rules make a change log useful rather than decorative. Write for a human reader, newest version first. Group under the six standard headings — Added, Changed, Deprecated, Removed, Fixed, Security — and omit the ones with no entries. Reference the requirement or defect ID each entry closes. And never paste raw commit output: commits are a record of typing, a change log is a record of decisions.

Change log — template

# Changelog

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

## [Unreleased]

## [X.Y.Z] - YYYY-MM-DD
### Added
- <user-visible capability> (FR-…)
### Changed
- <behavior that changed, and what a user must do differently> (FR-…)
### Deprecated
- <still works, will be removed, and when>
### Removed
- <gone, and what replaced it>
### Fixed
- <the symptom the user saw, not the code you touched> (DEF-…)
### Security
- <hardening or upgrade, with the advisory ID if there is one>
### Known issues
- <defect shipping in this release, with its ID, severity, and workaround>

Change log — worked (PantryPilot)

The 1.0.0 release entry, tagged in Week 14. Chapter 12 shows the earlier 0.9.0-rc.1 entry for the same project.

## [1.0.0] - 2026-04-15
### Added
- Household time zone: each household has one time zone, set at creation and
  editable in settings. All expiry calculations use it (FR-EXP-02, ADR-0004).
- Product fixture mode: setting LOOKUP_FIXTURES=1 serves 50 committed products
  instead of calling the lookup service, so demos never depend on the network
  (FR-SCAN-02, R-01).

### Changed
- Barcode lookup now gives up after 5 seconds and opens the manual form with the
  barcode preserved, instead of blocking the page (FR-SCAN-02, DEF-027).
- Expiry is stored as a plain calendar date. Migration 0009 backfills the
  household time zone. **Run migrations before starting the app.** The migration
  is not idempotent; do not run it twice on a partly-migrated database (ADR-0004).

### Removed
- AI recipe suggestions, which appeared in 0.7.0 as an experiment, are removed.
  The feature was cut in the Week-7 scope decision and the dead code and its
  prompt file are gone. Nothing depended on it (FR-REC-01, withdrawn).

### Fixed
- Items expiring today no longer disappear from the expiring-soon view
  (DEF-014).
- Adding an item with a past expiry date no longer silently discards the date;
  it saves and appears in the expired group (DEF-041).
- Keyboard focus is no longer lost after marking an item consumed; it moves to
  the next item in the list (DEF-031, NFR-ACC-01).
- Inviting an address that is already a household member returned a 500; it now
  returns 409 with a readable message (DEF-034).

### Security
- Session cookies are now HttpOnly and Secure; the deployed instance is
  HTTPS-only (NFR-SEC-01).
- A household member requesting another household's item now receives 404 rather
  than a 403 that confirmed the item exists (NFR-SEC-02).
- Upgraded the image-resizing dependency to a patched release; the prior version
  carried a published advisory. Verified against the GitHub Advisory Database.

### Known issues
- DEF-036 (S3): the expiring-soon view is not paginated and renders slowly past
  roughly 800 items. The view remains correct. Workaround: filter by storage
  location. Estimated 2.5 h to fix; see docs/handoff.md §5.
- Household invitations by email are not implemented (FR-ACC-04, deferred to
  v1.1). Members are added by an operator; see docs/runbook.md §4.

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.
  • The Changed section carries an operational warning in bold — run migrations, and do not run this one twice. That warning also exists in the runbook and in the migration file. Important warnings live in every place the reader might be standing.
  • Removed explains a feature that once existed and is now gone, with the reason and the withdrawn requirement ID. 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 getExpiryWindow” means nothing to a user; “items expiring today no longer disappear” means everything.
  • Known issues appear in the release entry itself, with severity, workaround, and effort. A release with no known-issues section is claiming zero known defects, which is almost never true.
  • Every line traces to an identifier a reader can look up in docs/requirements.md, docs/defect-log.md, or docs/adr/.

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, so they have to try. 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 this upgrade is urgent has nothing to decide with. If it was a security fix, you have 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”The Added section, with no features in it.Nothing to demonstrate, nothing to trace, nothing to grade.
”misc”The author stopped caring, and the reader can tell.Reads as carelessness on the most visible file in the repository after the README.
(absent)No known-issues section.Implies zero known defects. Your own defect log contradicts it, in the same repository, which is a consistency defect — two of your documents disagree, and a grader who finds one stops trusting both.
(absent)No requirement or defect IDs anywhere.Nothing traces. In Week 16 nobody can connect your shipped software to the requirements you baselined in Week 8, which is exactly the connection the final rubric is looking for.
(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 kind of small inconsistency that makes a reviewer check everything else.

B.16 — The Retrospective

Handed to you in: Week 16 — retrospective-template.md in the Chapter 16 companion code. Lands at: docs/retrospective.md · Hat: all seven, looking backward · Time: 2–3 hours in the final week.

The retrospective is 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 commit, a change-log entry. A retrospective without evidence is a feeling. And feelings are the one thing sixteen weeks of documents have equipped you to replace with numbers.

Retrospective — template

# Capstone Retrospective — <project name>

## 1. What I set out to build
One paragraph from the Week-1 charter, without editing it to look prescient.
Then one sentence: what I actually shipped, in the same terms.

## 2. The numbers
| | Planned | Actual | Delta |
|---|---:|---:|---:|
| Total hours | 240 | | |
| Hours in construction (Weeks 9–12) | | | |
| Requirements: Must / Should / Could | / / | / / | |
| Musts shipped and passing | | | |
| Defects logged / open at submission | | | |
| Largest single-task estimate error | | | |

## 3. Estimate versus actual
Your three worst estimates by ID, with estimate, actual, and the reason. Then:
> What kind of task do I systematically underestimate, and by what multiplier?
State it as a rule with a number in it.

## 4. The decision I would make differently   (ONE, examined honestly)

## 5. The same 240 hours, spent better
| Take hours from | Hours | Give them to | Why |
|---|---:|---|---|
Every row cites something in the repository.

## 6. What did not get done, and what it would take
| ID | What it is | Priority | State today | Remaining effort | Where the plan lives |
Every open Must belongs in this table.

## 7. What the process taught me   (3–5 sentences, about process, not technology)
## 8. What I would tell the version of me in Week 1

Retrospective — worked (PantryPilot)

§2 The numbers

PlannedActualDelta
Total hours240251.5+11.5
Hours in construction (Weeks 9–12)45.058.25+13.25
Requirements: Must / Should / Could19 / 6 / 319 / 4 / 0−2 Should, −3 Could
Musts shipped and passing1918−1 (FR-ACC-04 deferred)
Defects logged / open at submission44 / 22 open: DEF-036 (S3), DEF-044 (S4)
Largest single-task estimate errorT-4.2, 3.0 h estimated / 9.5 h actual3.2×

Every figure above is computed from docs/hours-log.csv, docs/requirements.md, 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-4.2 — cache lookups by barcode3.09.53.2×I had never written a cache with an invalidation policy. Most of the time went to deciding when an entry goes stale, which is a design question I had estimated as a coding task
T-3.5 — household-date boundary function + lint rule3.35.251.6×Estimated the function. Did not estimate finding and rewriting eleven call sites, or the lint rule that keeps them from coming back
T-8.1 — first deploy to the host3.76.01.6×The deploy worked. The secrets did not: three cycles of push-fail-read-logs 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 third-party API, a host’s secret store, a caching layer, a migration tool — gets my estimate multiplied by three. Any task I have done before in another course gets multiplied by 1.2. Applying that rule retroactively to the Week-7 plan turns 125.0 calibrated hours into 141 — which is 3 hours off the 138 I actually spent on the plan’s tasks. The rule works. I did not have it in Week 7.

§4 The decision I would make differently. In Week 5 I chose to build the barcode lookup against the live service from day one, because I wanted to see real product names. What I knew at the time: the service was free, the endpoint was simple, and I had a fallback specified in FR-SCAN-02. What I know now: every subsequent task in that work package was slowed by a network call I did not control — flaky tests, a demo I could not run on the campus network, and DEF-027, which existed for eleven days because the failure only showed up when the service was slow rather than down. What I would do instead: build against the committed fixture file first, with the live client behind a flag, and touch the live service exactly twice — once in a Week-9 spike, once in the Week-14 release verification. That is exactly the reasoning behind R-02’s response — retire an unknown early, in Week 9, where it is cheap — applied to test data instead of to deployment. I had already written that response down. I just did not see that it was the same problem.

§6 What did not get done

IDWhat it isPriorityState todayRemaining effortWhere the plan lives
FR-ACC-04Household invitations by emailMust — delivery deferred in the Week-7 scope decision; the priority was never loweredNot implemented. Members added by an operator, documented in the runbook6.0 h: mail provider, token flow, expiry, two testsdocs/handoff.md §5; CHANGELOG.md known issues
DEF-036Expiring-soon view unpaginatedS3 / P3Shipping. Correct, slow past ~800 items. Workaround documented2.5 hdocs/handoff.md §5
DEF-044Empty-state copy ignores the active filterS4 / P4Shipping. Left as the onboarding guide’s first issue on purpose0.7 hdocs/onboarding.md §4
FR-REC-01/02AI recipe suggestionsCould → WithdrawnCut in Week 7. Experimental code removed in 1.0.0~15 h if reviveddocs/plan.md §7; docs/requirements.md §7

Why this one works:

  • The numbers table is populated from the repository, and it says so, and it names which source wins in a disagreement. That single sentence tells a grader the author checked.
  • It reports 251.5 hours against a 240-hour budget. Overrunning is normal. Reporting 240.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 — 141 predicted against 138 spent. That is not a resolution; it is a finding, and it is a sentence this student can use in their first job.
  • Section 4 examines one decision, states what was known at the time without hindsight cheating, and connects it to an existing ADR the student did not apply broadly enough. That connection is the mark of real reflection.
  • Every open Must is in §6, with effort and a location. Leaving one out is the single most expensive omission available in the final week — a grader who finds it themselves stops trusting the rest of the document.

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
managed to get 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.Sixteen weeks of hours logs, defect logs, and estimates exist in the same repository, and none of it is used. The section that was supposed to prove you can measure your own work proves the opposite.
2”Most of the features” — which?If a Must is missing, this sentence conceals it, and a grader will find it in the requirements document two minutes later. Concealment discovered is far more expensive than a gap disclosed.
3”I underestimated some tasks” — no task, no ratio, no pattern.The single most valuable output of a capstone retrospective is a calibrated rule about your own estimating. This produces none, so nothing transfers to your next project or your first job.
4”Start earlier and plan better” is an intention, not a finding.It is also not actionable and not checkable. Compare: “any task touching an interface I have never called, multiply by three.”
5No decision examined.Every capstone contains at least one decision the author would reverse. Naming none reads as either no reflection or no honesty, and a reviewer cannot tell which.
6No “what did not get done” table.Whatever is missing is now something the grader discovers alone. See #2.
7”I think it turned out well.”Self-praise, unsupported, in a document whose entire purpose is evidence-based self-assessment. “Let another praise you, and not your own mouth” (Proverbs 27:2, ESV) — the evidence praises you if you let it, and the evidence is already in your repository.
8Nothing traces to anything.Every other document in this kit points somewhere. This one points nowhere, which makes it the only document in the repository that could have been written without doing the project.

B.17 — The Consistency Sweep: Making Sixteen Documents Agree

Individually strong documents that contradict each other score worse than a coherent set of merely good ones. The reason is simple: a reviewer who finds one contradiction starts hunting for more, and every subsequent document is read looking for defects instead of merit.

Run this sweep in Week 16, before you tag. It is the cheapest set of points in the course.

#CheckWhere it usually breaks
1Every requirement ID in the test plan exists in the requirements specificationRenumbering in Week 4, or an identifier that lost its area prefix on the way into the test plan
2Every Must is passing in the test results or open in the defect log with a severityA Must quietly disappears in a Week-12 cut and nothing records it
3Every cut in the Week-7 scope decision is reflected in the requirements’ Won’t-have listThe plan changes; the specification does not
4The architecture document describes the system as built, not as designed in Week 6Every divergence needs an updated diagram or an ADR
5Every ADR’s status is current; superseded ones point at their successorStale Proposed records on decisions you made in Week 9
6Feature claims in README.md match what actually runsA cut feature still advertised — see B.12 defect 6
7CHANGELOG.md known issues match the open rows in the defect logThe two lists drift in the final week
8The runbook’s failure catalogue covers the failures in the defect log and the risk registerThe information exists; nobody moved it
9Deferred work appears identically in the handoff guide and the retrospectiveDifferent effort estimates in the two files
10The hours log has an entry for all sixteen weeks, including the bad onesSilences the report script will name out loud
11Every AI-assisted artifact has its row in docs/ai-usage.md, dated the day it happenedBackfilled disclosure, which is its own integrity problem
12LICENSE is a real license whose SPDX identifier matches the README’s claimA README says MIT; the file is empty or absent
13Every time-varying claim — price, tier, version, terms — has a source and a checked dateVerification tables left blank
14No 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 all the documents open, and do it before you write the retrospective. Half the inconsistencies you find are things the retrospective should be reporting anyway — and the other half take four minutes each to fix while you are already looking at both files.


B.18 — Using an Assistant on These Documents

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

Where an assistant helps — real leverage, low risk.

  • Breadth checks. “Here is my risk register. What categories of risk have I not covered?” It is excellent at naming the thing you forgot. Harvest the list; 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.” This is the highest-value prompt in the whole course.
  • Format conformance. “Does this defect report contain all seven required fields?” Mechanical, checkable, and the answer is verifiable in ten seconds.
  • Bad-version generation for practice. “Rewrite this good requirement as a vague one.” Then fix it back. This is a rep, and a good one.

Where it is unreliable — use it, but verify every line.

  • Durations. It does not know how fast you are, how strange your environment is, or that your last four hours went to a broken lock file. It will hand you a smooth, uniformly optimistic schedule where every task takes four hours. Your calibration factor is worth more than its estimate, because your calibration factor is made of your own history.
  • Anything time-varying. Prices, free-tier limits, license terms, version numbers, standards clauses. It will state these confidently and be out of date, and a wrong claim about licensing costs you real money and real time. This is what the verification tables in B.3 and B.5 exist for: primary source, and the date you checked.
  • Identifiers. It will cite FR-INV-21 when your inventory requirements end at FR-INV-18. This exact failure appears in the Week-8 design review sample, twice, as a rejected finding. Check every identifier it hands you against your own document.

Where it must not go — the Red zone.

Decision records, memos, reflections, 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. A retrospective you did not write teaches you nothing, which is the entire reason it exists.

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 Amber-zone use gets one row in docs/ai-usage.md, written the day it happens. Ninety seconds, and it is evidence of judgment rather than evidence of nothing.


B.19 — Why Write Any of This Down?

You have just read sixteen sets of forms. It is fair to ask whether they are worth it, 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 everybody who has said otherwise has been proved wrong by the second maintainer. But the deeper answer is not about efficiency. Look at what every document in this kit actually does. The charter counts the cost before the tower is started. The requirements specification says exactly what you will do, so that your yes means yes. The hours log records what really happened, not what you wish had. The defect log looks for your own faults on purpose. The risk register admits that you do not know what tomorrow will bring. The handoff guide provides for a successor you will never meet. The retrospective tells the truth about your own work without flattering yourself.

These 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 someone who was not there. Your hours log is a weight. Your defect log is a weight. Your test results are a weight. Every one of them is an instrument by which somebody who was not in the room can check what you said.

That is what makes documentation an ethical act rather than an administrative one. Almost every document in this kit exists for somebody who is not you: the grader who cannot ask a question, the maintainer who inherits the repository, the roommate 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 2 a.m. when a stranger is trying to restore your database from a backup they have never seen.

And it cuts inward too. 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. “Search me, O God, and know my heart! Try me and know my thoughts!” (Psalm 139:23, ESV) is written by a man asking to be examined. The defect log is the same posture, in a much smaller key: I went looking for my own faults, I wrote them down where you can see them, and here is what I did about each one.

“But all things should be done decently and in order” (1 Corinthians 14:40, ESV) is Paul writing about worship, not software, and I am not going to pretend otherwise. But the instinct behind it — that order serves people, that clarity is a form of care, that what is done in common should be intelligible to everyone in the room — is exactly why sixteen documents beat a brilliant explanation you happened to give out loud in Week 10 to somebody who has since graduated.

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

Sixteen artifacts. Forty-eight versions of them on this page. Here is what I actually want you to take from it.

The gap between the good and bad versions above 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 was produced by 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.

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 in Week 8 and has not been touched since is not a document set; it is a fossil. Twenty minutes every Monday — burn-down, top five risks, and whatever document last week made obsolete — is the whole practice.

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

See you on Monday.