Design, Decide, and Stand It Up
Why lay a foundation before you decorate?
Chapter 3 — Design, Decide, and Stand It Up
“A Walking Skeleton is a tiny implementation of the system that performs a small end-to-end function. It need not use the final architecture, but it should link together the main architectural components.” — Alistair Cockburn, who named the practice
“Let each one take care how he builds upon it. For no one can lay a foundation other than that which is laid, which is Jesus Christ.” — 1 Corinthians 3:10–11 (ESV)
Why This Matters
This is the week that buys the rest of the term.
You have a charter and a scoped project from Week 1. You have requirements with identifiers, acceptance criteria a stranger could verify, and a Must list you cut to fit 160 hours in Week 2. All of it is what. This week is how, and it is the last week of design — because in Week 4 a review freezes your specification, and from Week 5 you are building.
Here is what makes this week different from the same week in a sixteen-week capstone: you do all of it at once. The long edition spends a week choosing a stack, a week writing the specification, a week planning the build, and a week standing up a walking skeleton. You have twenty hours and one week for the whole thing. That sounds impossible until you notice why it is not: you did not shop for an idea, and you are not shopping for a stack either. The Fast-Start brief you adopted in Week 1 already suggests one, sized to this budget by somebody who did the arithmetic. Your job this week is not to author an architecture from a blank page. It is to check an inherited one against the project you actually scoped, adopt what still fits, deviate where it does not, and write down why — in one page per decision.
That is the accelerated skill again, in its third and hardest form. In Week 2 the signature failure was keeping a requirement that quietly described somebody else’s project. This week it is keeping a stack that does. The brief’s suggested technology was sized for the brief’s scope, and you changed the scope. If your users turned out to be a club instead of a household, if you dropped the integration, if you cut two Musts — some part of that suggestion is now wrong for you, and it will not announce itself. It will wait until Week 6.
You wear three hats in five days: architect (what fits together), project manager (what it costs and what could go wrong), and release engineer (does it run on a machine that is not yours). The AI thread runs hard through all three. As a tool, scaffolding a repository and drafting a continuous-integration workflow is the single highest-leverage use of an assistant in this entire course — it can hand you in ninety seconds a thing that would cost you three hours — and this is also the first week unreviewed generated code can enter your repository and stay there until Week 8. As a workload, if your project calls a model, this is the week its key exists on your laptop for the first time, which makes it the week you can leak it. The spine rule does not move: the assistant accelerates, you read every line before it is committed, and your name is on the commit.
And underneath all of it, the week’s question, which is not really a question about concrete: why lay a foundation before you decorate? Nobody has ever been praised for a foundation. It does not demo. In an eight-week course the temptation to skip it is twice as strong, because the deadline is twice as close. We take that seriously in §3.10.
3.1 — Why the Skeleton Walks in Week 3, Not Week 5
Two students start the same eight-week capstone.
Student A designs on paper for two weeks, then starts building features in Week 5: a good-looking front end over hard-coded arrays, then a data layer tested on its own, then authentication tested on its own. In Week 7 they begin wiring it together and discover the client assumes a shape the service does not return, the driver does not work on the deployment target, and CI has never run. Week 7 was supposed to be documentation. It becomes integration. Week 8 is a presentation about a prototype.
Student B spends six hours in Week 3 building one ugly screen that shows one real row from one real table, served through the real route and the real service, installed from scratch by a robot on every push. Then they add features to a system that already works.
Student B ships. This is not a story about talent, and in an eight-week course it is not even close.
A walking skeleton is the thinnest slice that touches every architectural component and returns a real result. “Thin” is about breadth of behavior, not depth of stack: you keep every layer and shrink what it does to almost nothing.
| Not a walking skeleton | Why not | An actual walking skeleton |
|---|---|---|
| A styled pantry page rendering a hard-coded array | Never leaves the browser. Zero hops. | The page fetches GET /api/items, which reaches a real service, which queries a real items table holding one seeded row, and renders it |
| A login screen that posts nowhere | A vertical slice of nothing. Auth is the hardest hop and proves the least early. | Anonymous read of one item. Auth arrives in Week 5, on top of a path that already works. |
Unit tests for ItemService with the store mocked | Proves the class, not the system. Mocks agree with you. | One end-to-end assertion on a value that could only have come from the database |
”The API works, look at this curl” | Half the path. The rendered result is a hop. | A human loads the page and sees the words oat milk |
The running examples carry through the whole book. PantryPilot — the household pantry tracker, brief #1 in the Fast-Start Catalog, Node and PostgreSQL per ADR 0001 — has a web client, a route layer, a domain service, a relational store, and one third-party lookup. TraceLens — the command-line log analyzer — has no browser, no auth, no HTTP. Its skeleton is tracelens scan samples/one-line.log: parse one argument, open a real file with the real reader, run one real rule through the real analyzer, format with the real reporter, print one anomaly line, exit 0. Six hops, no server, identical principle. Every component in your specification appears in the path, and the path is real from end to end.
And it has to walk, not stand. Standing is “it compiles.” Walking is “somebody who is not me runs one command and watches a request travel all the way through and come back.”
Coach’s Note — The skeleton is where your specification stops being an opinion. If a hop you drew on Tuesday turns out to be impossible — the driver has no async support, the host will not open that port, the API needs a paid tier — you find that out on Friday of Week 3 with five weeks of runway, instead of in Week 6 with none. That is not a setback. That is the skeleton doing its job, and it is exactly why it comes before the features and not after them.
3.2 — Adopt or Deviate: the Brief’s Stack Is a Recommendation, Not a Verdict
A full-length capstone builds a weighted evaluation matrix across five technology decisions, with committed weights, evidence cells, and a sensitivity pass. That is good engineering and you do not have the hours for it. What you have instead is better than a blank page and worse than a guarantee: a suggestion from somebody who sized it to 160 hours.
So run the fast version, and run it honestly. It takes about two hours.
Step one — the drivers pass, on paper, in twenty minutes. Open docs/requirements.md. Most of your requirements do not constrain the technology; “the user can rename an item” runs on anything. Highlight only the ones that would change your technology choice if they changed. You are looking for four to six, from four places: the shape of the data and how you query it; a hard number in an NFR; an external interface; and a constraint you cannot move — hosting you can actually reach, a license you must honor, and the hours you actually have. If you highlight twenty, you are highlighting wrong. If you highlight zero, that is a real and useful finding: your project is stack-indifferent, so take the brief’s suggestion or the thing you already know, and spend the saved hours on the product.
Step two — diff the brief against what you actually scoped. This is the step nobody does and the one this edition exists for. Line up the brief’s suggested stack against your adapted requirement set and ask, per piece: what in the brief justified this, and is that thing still in my project?
| Suggested piece | What justified it in the brief | Still true after your Week-2 edits? | Verdict |
|---|---|---|---|
| Relational store | A three-way join on the main screen (FR-INV-03) | Yes — you kept it | Adopt |
| A managed host with two web workers | Concurrent household members | Yes | Adopt |
| Third-party barcode lookup | Fast item entry | No — you demoted it to a Should | Deviate: fixture file, live API later |
| A background worker process | The nightly expiry digest | No — you cut the digest in Week 2 | Deviate: delete the container entirely |
Two of those rows are the whole exercise. A container you keep because the brief drew it is a container you must build, run, debug, document, deploy, and demo — with hours you do not have. Delete it now while deleting is free.
Step three — count your novelty load, and spend exactly one token. Count the pieces of your stack you have never shipped with. Not read about. Shipped — built, deployed, debugged at 11 p.m. Dan McKinley’s framing is that a team gets a small fixed budget of innovation tokens and every exciting new technology spends one. A sixteen-week solo capstone gets one. You have half the calendar; you still get one, and you must spend it on the piece closest to what makes your project interesting. Two new things at 160 hours is not ambition, it is arithmetic that does not close: learning curves compound rather than add, because when something breaks across two unfamiliar pieces you cannot tell which one is lying to you.
| Novelty load | At 160 hours this means | Do this |
|---|---|---|
| 0–1 | Healthy. Your hours go to the product. | Proceed. |
| 2 | Only survivable if they are nowhere near each other. | Demote one to something you have shipped with. |
| 3+ | This is how an accelerated capstone dies in Week 6. | Rebuild the stack around what you know. Not a suggestion. |
Step four — check the seams, not the boxes. Each decision looks fine in isolation; every seam is where the arrows are. One row per boundary where two chosen pieces must talk — app ↔ store, app ↔ third party, CI ↔ tests, CI ↔ host — and one column that matters more than the others: have I crossed this seam before? An unfamiliar library is a study problem, bounded, with docs. An unfamiliar seam is an integration problem, and integration problems have no docs, because they are specific to your combination. Any seam you have never crossed gets attempted this week, in the skeleton, or it gets designed out. There is no Week 6 in which you have time for it.
Coach’s Note — “But I want to learn X.” Good. You should. Learn one thing well enough to defend it in the Week-4 review, ship it, and write honestly about what it cost. That is a stronger story in an interview than three half-learned tools and an unfinished repository — and in eight weeks, three half-learned tools is not a story, it is a postmortem.
3.3 — The Architecture Decision Record, in One Page
Six months from now somebody — very possibly you — will open this repository and ask why on earth is it built like this? Code answers what; tests answer whether. Nothing in a repository answers why unless you write it down. An architecture decision record is a short, immutable, numbered document capturing one decision, the forces around it, and its consequences. The format comes from Michael Nygard’s “Documenting Architecture Decisions” (2011), and it became an industry norm because it is small enough that people actually do it.
You write three this week, in about ninety minutes total, from code/adr-one-page.md. Three, not seven — and here is the rule for which three: one ADR for the load-bearing store, one for every deviation from the brief, and one for hosting and CI. Four rules govern them.
- One decision per record, numbered
0001,0002, … indocs/adr/. - Immutable. You never edit an accepted ADR to reflect a new decision. You write a new one and mark the old superseded. The trail is the point; the record of a decision you later reversed is often the most valuable file in the repository.
- Written at decision time, while the alternatives are still in your head. An ADR reconstructed in Week 7 is fiction with a date on it, and a reader can smell it.
- Consequences include the negative ones. Always. This is the tell.
This edition adds a fifth field the long edition does not need: Adopt / Deviate. Every ADR says plainly whether it accepts the brief’s suggestion or departs from it, because that single word is what a Week-4 reviewer reads first. And the deviations are where the grading weight sits, because a deviation is a decision you made and an adoption is a decision you checked.
Here is what a bad one looks like, and it is not a straw man:
# ADR 1: Database
**Status:** Accepted
We will use the database the brief suggested because it is flexible and
scales well and is very popular in industry.
No date, no author, no requirement identifiers, so nothing connects it to the project. No alternatives — “considered nothing” is what this says. “Scales well” cannot discriminate between options at student scale, so it is decoration. Zero negative consequences and no revisit trigger, so nothing would ever cause anyone to reopen it. And the fatal one for this edition: it adopted without checking. It does not say what in this project still justifies the suggestion.
The worked adopt and the worked deviate — both complete, both one page — are in code/adr-one-page.md. Read the deviation one twice. Notice that it is not a complaint about the brief. It names what changed about the project, names the hours, names what the project loses, and accepts that cost out loud. A decision record that cannot be argued with has not recorded a decision.
3.4 — A Technical Specification Sized to Eight Weeks
A technical specification is the bridge between a requirement and a commit. On one side is FR-EXP-02: the system shall show items expiring within the configured window. On the other side is a function you type at 11 p.m. in Week 6. Between them sit at least a dozen decisions: what “expiring” means in days, from when, in which timezone, who computes it, what a user with zero items sees, what happens when the store is unreachable. You make those decisions this week, on purpose, in daylight — or in Week 6, one at a time, exhausted, in the middle of writing something else. There is no third option. The specification is not extra work; it is the same work done when it is cheap.
The standard the document is held to is the stranger test: hand it to a competent developer who has never spoken to you, point at one requirement, and ask them to build it. If they must ask you a question to start, the specification has a hole where an answer should be.
At this budget it is four to seven pages, and the measure is decisions per page, not page count. Start from Part 1 of code/design-pack-template.md and copy it to docs/architecture.md. Six parts carry the weight:
| Part | The question it answers | Where the hours go |
|---|---|---|
| Scope and boundary | What is inside, outside, and which side holds secrets? | 0.5 h |
| Containers + component responsibilities | What runs, who does what, who owns which state? | 1.0 h |
| Interface contracts | What can be called, with what, returning what? | 1.0 h |
| Data model | What is stored, keyed how, with what invariants? | 1.0 h |
| One sequence flow, with its failure branch | How does the risky path actually run — and break? | 0.5 h |
| Edge cases, dependencies, open questions | What have you not decided, and what does it block? | — |
And here is what the compression actually cut, so you can defend it in the review. Diagrams: two levels, not four. Context (your system as one box, with the people and systems around it) and containers (open the box: what runs, in what technology, with every arrow carrying a direction, a payload, and a protocol). Draw a component-level diagram only where a reviewer would otherwise have to guess. Skip code-level diagrams entirely; class diagrams of your own source have a half-life of about four days. Simon Brown’s C4 model (https://c4model.com) is the source of that ladder, and its real gift is telling you when to stop drawing. Use a text-based tool — Mermaid (https://mermaid.js.org) or PlantUML (https://plantuml.com) — because the source is a file that lives in Git and shows up in a diff. A photograph of a whiteboard does not diff. Commit both source and rendered image under docs/diagrams/.
Sequence flows: one, not three. Diagram the risky path — the flow that crosses a boundary you do not control — and then, in a table beneath it, the same flow with the boundary broken. The failure branch is where the decisions actually live. Watch it work:
| Step | What can go wrong | System behavior | User sees |
|---|---|---|---|
| 1 | Barcode is 6 digits | Reject before any I/O | 400, inline message |
| 2 | Data store unreachable | Fail fast; do not call the third party | 503, “try again shortly” |
| 3 | Lookup times out (>2 s) | One retry with backoff, then give up | ”Enter the name yourself” |
| 3 | Lookup returns 429 | No retry; honor Retry-After; log | Same manual-entry prompt |
| 4 | Item insert fails after cache fill | Item not created; stray cache row is harmless | 500, nothing saved |
Specifying the failure branch just forced three decisions the happy path never surfaced: the timeout value, the retry policy, and — the important one — that a failed lookup must not stop the user from adding an item. That is a product decision hiding inside a technical one, and you made it on a Wednesday in Week 3 instead of finding it in Week 6 with the schedule already tight.
Component responsibilities: keep the table, keep the rule. One sentence per component, starting with a verb. utils is not a component; it is a confession. And the single-owner rule holds without exception: for every piece of state and every behavior, exactly one component is responsible. If two components compute days-to-expiry, they will disagree — not today, but on the day one is fixed for a daylight-saving bug and the other is not.
The data model: do not compress this one. You can refactor a function over lunch. You cannot refactor a table full of data you care about over lunch, and from Friday of this week your store holds data. Every entity gets a primary key, every column a type and a nullability rule whose meaning you can say in words, every table its invariants. Timestamps in UTC, converted in exactly one place. Money in minor units as integers, never floats. Enumerations constrained, never free text — a unit column that accepts any string will contain g, G, grams, and g by Week 6. And decide the migration mechanism now: a tool, or numbered SQL files applied in order and tracked in a schema_migrations table. Both are fine at this scale. A migration a stranger cannot run is not done.
Coach’s Note — If drawing the sequence flow does not change one of your interface contracts, you drew it after you decided instead of to decide. Draw it first. Let it break something. Twenty minutes of breaking your own design on Wednesday is the cheapest twenty minutes in this course.
3.5 — The Work Plan and the Risk Register on One Screen
Now the project-manager hat, for two hours, and it starts with a number.
You have spent about 60 of your 160 hours getting to this sentence. That leaves 100 across Weeks 4 through 8. But not all of it is yours — some of it belongs to the course. Subtract honestly:
| Week | What it is | Course overhead | Available for your project |
|---|---|---|---|
| 4 | Design review + midterm checkpoint | 9 | 11 |
| 5 | Build the core | 3 | 17 |
| 6 | Test, integrate, cut | 3 | 17 |
| 7 | Document, deploy, hand off | 3 | 17 |
| 8 | Deliver: submission + presentation | 11 | 9 |
| Total | 29 | 71 |
Seventy-one hours. Then take the safety out of every task and declare one visible buffer instead of hiding a cushion in thirty places. Padding hidden across thirty tasks does not protect you — Parkinson’s Law eats it (“work expands so as to fill the time available for its completion,” C. Northcote Parkinson, 1955), and Goldratt’s student syndrome means it gets consumed at the front, before the trouble even arrives. Hold back 25%:
Available for the project, Weeks 4-8 .......... 71.0 h
Declared project buffer at 25% ................ 17.8 h
PLANNABLE ..................................... 53.2 h
Fifty-three hours. That is what your Must list has to fit inside — features and tests and documentation and deployment. Write the number at the top of docs/plan.md. This is the two-thirds rule with a decimal point on it: 160 hours buys a project about two-thirds the size of a sixteen-week capstone, which is three to four Must features, not five or more. If your work breakdown comes out bigger than 53 hours — and on the first pass it will — the honest move is to make the plan smaller today, not to type faster in Week 6.
Decompose to work packages (4–20 hours) and tasks (1–6 hours), each with a requirement identifier, a done-when somebody else could check, and a dependency. Estimate in three numbers, never one: optimistic, most likely, pessimistic, then E = (O + 4M + P) / 6. The spread teaches more than the expectation — if P / O is over 4 you do not understand the task, so split it or time-box a spike. And add the work nobody writes down, because it gets done anyway, out of the documentation budget, at midnight in Week 7: seed data, error handling (15–25% of every feature task), the accessibility pass, secrets and configuration, reading and fixing generated code, and the README, runbook, and handoff guide that carry a large slice of your final grade.
Then the risk register: six rows, not twenty. A risk is an uncertain future event with a consequence — the moment it occurs it stops being a risk and becomes an issue in docs/defect-log.md. Each row is a sentence in the form cause → uncertain event → consequence, scored L and I from 1 to 5 with impact scored in hours you would lose, not in dread, sorted by exposure. And then the three columns students skip, which are the only ones that make a register a control instead of a worry list:
- Trigger — the observable thing that says it is happening. A number, a date, a log line. Not “if it becomes a problem.”
- Owner — you, by name. A risk with no owner is a wish.
- Response — avoid, mitigate, transfer, or accept, and it is already a task in
docs/plan.mdwith hours attached. A response that is not in the plan is not a response.
Six risks you will actually reread beat twenty you will never open again, and you reread them in four days: the Week-4 halfway audit opens this file in front of you. Parts 2 and 3 of code/design-pack-template.md are the templates, with a worked row and the bad row beside it.
Coach’s Note — An assistant is excellent at breadth here and unreliable at duration. Ask it to list every task a feature needs including setup, error handling, tests, and documentation, and it will name twenty, four of which you had forgotten. Then delete every number it wrote. It does not know your speed, your machine, or that your last migration took four hours because of a version mismatch. It may propose tasks; only you may propose durations.
3.6 — Repository Structure, Branching, and the Command Directory
Your repository is the graded artifact. In Week 8 a person who has never seen it forms an opinion in about ninety seconds. Build for that person now, because restructuring later means rewriting every path in every document you have written.
The canonical layout for this course was set up in Week 1. Here is the state it should be in by Friday, with this week’s additions marked:
your-capstone/
├── README.md # + the "## Run it" section, this week
├── LICENSE # chosen deliberately; it matters in Week 7
├── CHANGELOG.md # present, mostly empty until Week 6
├── .gitignore # committed FIRST, before anything worth ignoring
├── .env.example # NEW — every variable the app reads, placeholders only
├── <runtime version file> # NEW — .nvmrc / .python-version / .tool-versions / go.mod
├── <lock file> # NEW — committed; this is the reproducibility
├── script/ # NEW — setup, lint, test, build, start, smoke
├── src/ # NEW — folders named after your specification's components
├── tests/ # NEW
├── docs/
│ ├── charter.md # Week 1
│ ├── requirements.md # Week 2
│ ├── definition-of-done.md # Week 2
│ ├── architecture.md # THIS WEEK
│ ├── plan.md # THIS WEEK
│ ├── risk-register.md # THIS WEEK
│ ├── skeleton-trace.md # THIS WEEK
│ ├── hours-log.csv # every week
│ ├── ai-usage.md # every week
│ ├── adr/ # THIS WEEK — three one-page records
│ └── change-requests/ # a directory; one file per request, from Week 4
└── .github/workflows/ # NEW — continuous integration
Names differ by stack — pyproject.toml and poetry.lock, go.mod and go.sum, Cargo.toml and Cargo.lock. What must not differ is the three-part shape: documents in docs/, source in a tree whose top-level folders match the components in your specification, and one directory of one-word commands anybody can run without reading your mind.
That last one has a name. GitHub has published the pattern for years as “Scripts to Rule Them All”: a small script/ directory with normalized verbs, so every project a person joins is operated the same way whatever it is written in. Your script/setup might be npm ci && npm run db:migrate && npm run db:seed, or poetry install && alembic upgrade head. The stranger does not care. They type ./script/setup and it works. If the honest instructions are eleven steps, then script/setup is a file with eleven lines in it and the stranger still types one command.
Branching, for a team of one, in four lines. main is always green and always runnable; if main is red, that is the only thing you work on. One short-lived branch per requirement — fr-pan-06-expiring-list. Open a pull request even though you are the only reviewer, because that is where CI reports and where you read your own diff before it becomes history. Merge often; a branch older than two days is a merge conflict with a countdown timer.
And commit messages, which students dismiss and graders read closely. fix, update code, asdf, Final version 2 FINAL — every one of those costs you an hour in Week 7 when you need to know which commit broke the expiry filter. Adopt Conventional Commits (https://www.conventionalcommits.org): a type, an optional scope, an imperative subject, and the requirement identifier where it belongs.
feat(api): return items expiring within the configured window (FR-EXP-02)
Seven days came from the Week-2 interviews; longer horizons made the list
unusable. Half-open interval [today, today+7) so an item expiring today still
appears. Timezone fixed to the server zone; multi-timezone is a Won't for v1.
The diff is the what. Your future self and the Week-8 grader need the why, and the why is the only thing that is gone forever if you do not write it down.
3.7 — Continuous Integration From the First Real Commit
Continuous integration is a practice before it is a product: integrate frequently, and have an automated build verify each integration so problems surface immediately rather than at the end. For your capstone it does something specific and enormous: it is a clean machine, running your setup instructions, on every push, for the rest of the term. Week 7 grades you on whether a stranger can clone and run your project. Wire CI up this week and you get several hundred rehearsals of that exam before you sit it — in an eight-week course, that is the highest-return ninety minutes on the calendar.
Six stages. Learn what each one proves, because a stage that proves nothing is theater.
| Stage | The command | What a failure actually tells you |
|---|---|---|
| Checkout | actions/checkout | Almost never fails. If it does, your history is broken. |
| Install | ./script/setup | Your dependency list is incomplete, unpinned, or needs something only your laptop has. This is the stage that catches “works on my machine.” |
| Lint | ./script/lint | Style drift, dead code, and — with a decent config — real bugs. |
| Test | ./script/test | Behavior regressed. |
| Build | ./script/build | It runs in your dev server but does not survive compilation or packaging. |
| Smoke | ./script/smoke | The skeleton stopped walking. Something in the path is broken end to end. |
Start from code/ci-starter.yml. It is stack-agnostic on purpose: two clearly marked blocks to fill in, and every other step is one of your script/ files, so CI never knows a command you do not. Now the bad version — and it is bad in the way real student workflows are bad, which is that it is green:
name: ci
on:
workflow_dispatch # only when I click the button
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install # not `npm ci` — resolves whatever is newest today
- run: npm test || true # never fails the build
Three lies in nine lines. workflow_dispatch alone means it never runs unless you remember, and you will remember exactly when you expect it to pass. npm install does not install what your lock file records, so the build says nothing about reproducibility. And || true means the badge is green whether or not the tests pass — the most common self-inflicted wound in student CI, and worse than having no CI at all, because it lies to you every day.
Reproducibility is four commitments and only four. Pin the runtime version in a file the tooling reads, so you and CI cannot drift apart. Commit the lock file, and install from it — npm ci not npm install, poetry install not poetry add; the first reproduces a recorded state, the second resolves whatever is newest today. Externalize configuration into environment variables with sane defaults, never a value typed into source; the test is blunt — could you make this repository public right now without changing a line? One command from clone to running.
Which brings the week’s other permanent risk. If your project talks to a model API, a mapping service, or a database with a password, this is the week a live credential exists on your laptop for the first time. .gitignore goes in first, before there is anything worth ignoring. .env is ignored; .env.example is committed with every variable the app reads and placeholder values, and it doubles as your configuration documentation. Real keys live in your CI provider’s repository secrets and are referenced by name, never as a literal in a workflow file. Never echo a secret and never dump the environment in a CI step — masking is best-effort and will not follow a value through a base64 encode or a JSON dump. And the part everybody gets backwards: if a key is already committed, rotate it first. Revoke at the provider, issue a new one, then clean history. Rewriting history does not reach forks, clones, CI caches, or somebody else’s terminal scrollback. A student who leaks a key, rotates it within the hour, and logs the incident has behaved like a professional. A student who deletes the file and commits “removed secret” has published a working credential and told the world which commit to look at.
Two numbers this book will not give you, because they move: what CI minutes cost and which branch-protection features your plan includes. Hosted minutes are generally free for public repositories on standard runners, with a private-repository allowance that depends on the plan — read your provider’s current billing documentation (https://docs.github.com/en/billing) and write today’s date beside whatever you find. That sentence is the model for how to state every time-varying fact in your own documents.
Coach’s Note — The most dangerous artifact an assistant will hand you this week is not code. It is a CI workflow that passes without testing anything. Wrong code fails loudly. A green pipeline that verifies nothing fails silently for five weeks and then all at once. After you set CI up, break it on purpose. If the build does not turn red, your pipeline is decorative — and Milestone 3 asks for the evidence for exactly that reason.
3.8 — Proving the Skeleton Walks
“It works” is a claim. This week you turn it into a test that runs on somebody else’s computer.
The instrument is a smoke test: not a unit test, not a full end-to-end suite, but one script that starts the real system, asks it for something, and asserts on a value that could only have arrived by travelling the whole path. Take code/skeleton-smoke.sh, copy it to script/smoke, and change three lines. Its logic is the whole lesson. One: wait until something answers at all — proves the client → route hop. Two: ask for the real endpoint and keep the body — proves route → service. Three: assert on a string that exists only because a seeded row exists in the real store — proves service → store → response.
Step three is where students cheat without meaning to. So run the honesty test: stop your data store and run the smoke test. It must fail. If it passes, it is testing that a process is alive, not that your skeleton walks. Fix the test, not the database.
The acceptance bar for Milestone 3 is one deliberately harsh sentence:
A person who has never seen your project clones it, runs two commands, and watches one real request travel every hop of your architecture and come back — and CI does the same thing, unattended, on every push.
Record it in docs/skeleton-trace.md: the hop table with an honest yes/no per hop, the requirement traceability rows, the commit that first made it walk, the URL of a green CI run, and the URL of the red one you caused on purpose.
Then the honesty note about stubs, because it matters more here than in a long course. Some hops legitimately stay stubbed in Week 3 — a paid third-party API, a payment sandbox, a device you do not have. That is fine. What is not fine is writing yes for a hop you faked. Write stub, name the ADR or issue that says when it becomes real, and move on. A skeleton with one honestly-labeled stub is a professional artifact. A skeleton with one dishonestly-labeled stub is the beginning of a Week-7 catastrophe, because you will plan around a hop you believe works — and with five weeks left, you will not have time to be wrong about it twice.
3.9 — Interactive Lab: The Architecture & Skeleton Sketchpad
On this chapter’s page you will find The Architecture & Skeleton Sketchpad. Two linked panels, and you should use them on two different days.
Panel one — assemble the system, before you draw anything in your real tool. Drag out typed components — client, API service, worker, data store, cache, third-party API, AI provider — and join them with labeled edges. As you build, the widget generates the two artifacts your specification actually needs: a component responsibility table and an interface list. And it fires warnings when your drawing makes a claim you did not intend: a system with no data store, a client holding a third-party secret (that arrow means your API key is in a browser), a component with no owner. Do two passes. Build the system the brief drew. Then delete every container your Week-2 edits made unnecessary and watch the responsibility table shrink. The difference between those two drawings is the two-thirds rule, rendered.
Panel two — the same diagram becomes a skeleton trace. Mark which hops of one real request actually work today. The widget draws your skeleton with the missing bone highlighted, names the next task, and does the thing no whiteboard does: it computes whether the remaining hops fit in the hours you have left this week. Be honest when you mark it — a hop backed by a mock is not a working hop, and the widget’s entire value is that it will not argue with you about it. Run panel two on Tuesday and again on Friday. The gap between those two pictures is your week.
What it teaches is worth naming: a diagram is a set of claims, not a picture. Every box claims something will exist and be maintained. Every arrow claims a dependency, a protocol, a failure mode, and sometimes a secret crossing a boundary it should not. The sketchpad makes those claims visible — which is exactly what the design review will do to you in four days, with a human on the other side of the table.
3.10 — Why Lay a Foundation Before You Decorate?
“According to the grace of God given to me, like a skilled master builder I laid a foundation, and someone else is building upon it. Let each one take care how he builds upon it. For no one can lay a foundation other than that which is laid, which is Jesus Christ.” (1 Corinthians 3:10–11, ESV)
Paul reaches for a construction image and picks a very particular role in it. The Greek behind “skilled master builder” is architektōn — the one who lays out the work. And notice what he claims: not the tower, not the ornament, not the part anyone photographs. The foundation. The one component of a building that, done perfectly, nobody will ever see or mention.
That is a strange thing to boast about, and it is exactly this week’s problem.
Nobody is impressed by a walking skeleton. You cannot show it to your roommate. It renders one row of text. The temptation to skip it is not laziness — laziness would be easy to diagnose. The temptation is vanity. The feature demos; the foundation does not. So we build the part that can be admired and defer the part that merely holds. Every capstone that dies in the second half died of this, and the student who built it worked hard the entire time.
And an eight-week course sharpens the temptation, because compression provides such a respectable excuse. I don’t have time for CI. I don’t have time for a data model. I’ll wire it up properly once the features exist. Look at that reasoning honestly and it inverts: the less time you have, the less you can afford to discover in Week 6 that your architecture was a drawing. Speed is precisely why the foundation comes first. Jesus’s builder in Luke 14 is not mocked for lacking ambition — he is mocked because he “began to build and was not able to finish” (Luke 14:30, ESV). Ambition was never his problem. Sequence was.
Now hear the sentence in the middle of Paul’s image, because it is aimed directly at you: someone else is building upon it. That is the thesis of this whole course in five words. You are not building a thing you will keep. You are building a thing you will hand to somebody else — in Week 7 when you write the handoff guide, in Week 8 when a stranger clones your repository and tries to run it, and in every job you ever hold, where you leave and the code stays. The foundation you lay this week is not for your comfort. It is for a person you will probably never meet. That is what makes it a moral act and not merely an engineering one, and it is what this course means by vocation: ordinary work done truthfully for the sake of a neighbor.
Let me be careful, because it would be cheap to shrink this passage into a project-management tip. Paul’s point is not that good architecture saves you. He says it outright in the next breath: no one can lay a foundation other than that which is laid, which is Jesus Christ. Your docs/architecture.md is not your foundation in that sense, and nothing you write this week has anything to do with your standing before God; that was settled outside of you, by Christ, and it is not on the rubric. But the smaller picture holds, and here is why a Christian should expect it to: God is not chaotic, and the world he made rewards building on what is actually true. That is not a coincidence we have to explain away. It is what an ordered creation looks like from the inside.
There is a counter-argument worth taking seriously, because good engineers make it. Isn’t this over-building? Doesn’t agile practice say to defer decisions and not build what you do not yet need? Yes — and the walking skeleton is the most agile thing in this chapter, because it is the thinnest structure that can bear weight. It is not a framework. It is not an abstraction layer for requirements you imagined. It is one real path, built once, so that everything after it can be added incrementally instead of integrated desperately. Gold-plating decorates before there is a floor. The skeleton is the floor. They are opposites, not cousins.
And one quiet warning, in a week when a machine can pour a plausible-looking foundation in ninety seconds. Paul says: let each one take care how he builds. Not how fast. Care is a property of the builder, not of the building. A foundation that only looks like a foundation is worse than none at all, because it invites you to build on it. Reading every generated line before you commit it is not a bureaucratic ritual. It is what care looks like when the pouring is fast.
“One who is faithful in a very little is also faithful in much” (Luke 16:10, ESV). This week is the very little. A .gitignore before the first commit. A lock file. A commit message that says why. A smoke test that would actually fail. None of it will be praised, and all of it will hold.
3.11 — Where Your Hours Went This Week
Twenty hours, and this is the densest week in the course. If yours runs to twenty-four, log twenty-four — Week 4’s audit is calibrated against your real numbers, and a flattering log calibrates nothing.
| Hours | What |
|---|---|
| 1.5 | Reading this chapter, the Sketchpad lab, and the weekly quiz |
| 2.0 | Adopt-or-deviate: the drivers pass, the brief diff, novelty load, the seam check |
| 1.5 | Writing three one-page ADRs |
| 4.0 | The specification: containers, responsibilities, contracts, data model, the failure flow |
| 2.0 | The plan and the risk register on one screen, calibrated against Weeks 1–3 |
| 1.5 | Repository structure, the script/ contract, .env.example, secrets pass |
| 3.0 | Building the walking-skeleton slice — real code, every hop |
| 2.5 | CI: writing it, the pushes it took to go green, and the red build on purpose |
| 1.0 | Smoke test and docs/skeleton-trace.md |
| 1.0 | docs/hours-log.csv, docs/ai-usage.md, milestone write-up, commit and push |
| 20.0 |
If CI took five hours instead of two and a half, that is normal the first time and it is not wasted — you paid a one-time cost that now runs free on every push for five weeks. If the skeleton took eight, something in your architecture is harder than Wednesday’s document claimed. Write that into the risk register today, not in Week 6.
3.12 — Common Pitfalls
Pitfall: Adopting the brief’s stack without checking it against the project you actually scoped. Example: The brief drew a background worker for a nightly digest. You cut the digest in Week 2 and the worker is still in your diagram, in your plan, and eventually in your repository. Fix: Run the brief diff in §3.2 line by line: what justified this piece, and is that thing still in my requirements? Delete every container whose justification you removed. Deleting is free this week and costs four hours in Week 6.
Pitfall: A novelty load of three, defended as “a learning opportunity.” Example: New language, new framework, new data store, one developer, eight weeks. Fix: Spend one innovation token, on the piece closest to what makes your project interesting. Demote the rest to things you have shipped with, and write the ADR that names the one new thing. Learning curves compound; at 160 hours the arithmetic does not close.
Pitfall: The skeleton that does not walk — a good-looking screen over hard-coded data.
Example: A pantry page rendering const items = [{name: 'oat milk'}] because “the database part comes next.”
Fix: Delete the array. Seed one real row in the real store and make the page fetch it. One ugly row that travelled the whole path beats fifty pretty rows that never left the browser, and it is the difference between full marks and a third of them on the biggest line of the rubric.
Pitfall: CI that cannot fail, or that only runs when you ask it to.
Example: npm test || true, a suite that collects zero tests and exits 0, or on: workflow_dispatch so it runs only when you press the button — which you press only when you expect it to pass.
Fix: Trigger on push to every branch and on pull requests into main, then break it on purpose the day you set it up. A pipeline you have never seen turn red is a pipeline you have no evidence about.
Pitfall: An ADR with no negative consequences, or no ADR at all because “I’ll remember.” Example: “Consequences: faster development, better maintainability.” Two benefits, no costs. Or Week 7, writing the handoff guide, with no explanation for why there are two ways to authenticate. Fix: Every real decision costs something — name what got harder, the hours you budgeted for the mitigation, and a revisit trigger with a number in it. If you cannot find a cost, you did not decide; you agreed. Three records, ninety minutes, written today while the alternatives are still in your head.
Pitfall: A specification that restates the requirement instead of deciding anything. Example: “The system shall show expiring items. The expiry module will handle expiry.” Fix: Count the decisions per section. If a builder must still ask you a question to start, rewrite until the questions are answered — or mark them open, with a blocker, an owner, and a date. An unmarked hole is a landmine.
Pitfall: Committing generated scaffolding that contradicts your own specification.
Example: An assistant scaffolds controllers/, models/, views/; your docs/architecture.md describes a service layer that now exists nowhere.
Fix: Scaffold from your architecture document, not from a template. Name source folders after the components you specified. If the generated shape is genuinely better, say so in an ADR and update the document — never let the two silently disagree, because in Week 4 a reviewer reads both.
3.13 — Reps
The reps are in the exercises, and this week they are not warm-ups. Done in order, they are Milestone 3, built one bone at a time. Preview:
- Rep 1 — the brief diff: every suggested piece against your adapted requirements, with a keep/cut verdict.
- Rep 3 — write the three ADRs in ninety minutes, including the deviation record that says out loud what your project loses.
- Rep 5 — draw the risky path, then the failure table, and record what the failure branch changed in your interface contract.
- Rep 8 — the capacity arithmetic: work packages against 53 plannable hours, and the cut you make when it does not fit.
- Rep 11 — break CI on purpose, twice, and record exactly what it reported.
Then take the on-page Check Your Reps quiz. It is one of eight weekly quizzes that together carry 15% of your grade, and its real job is to tell you this week whether you are on pace. In an eight-week course a week is an eighth of the term, so falling behind is proportionally twice as expensive as in a long capstone — which means the early-warning system is worth twice as much too.
3.14 — This Week’s Milestone
Milestone 3 — Milestone 3: Technical Specification, Decision Records & Walking Skeleton. It lands in your repository as docs/architecture.md, three records in docs/adr/, docs/plan.md, docs/risk-register.md, docs/skeleton-trace.md, a script/ directory, a CI workflow, and the commit where one real request first travelled every hop.
Remember the grading contract, which is in Appendix D. The milestones are graded twice, and that is not mercy. These artifacts are the Week-8 deliverable, produced one week at a time, and the final rubric awards points for exactly them. A skeleton you skip this week is not a small penalty now. It is the same points, moved to a week with no hours left in it.
3.15 — Coach’s Final Word
Three weeks ago you had a brief. Two weeks ago it became requirements you could hand to a stranger. This week it became a shape — a set of components with seams between them, a written record of why each is there, a plan that admits what it costs, and one real request that travelled the whole path and came back.
You are going to feel, on Friday, like you did not accomplish much. One row of text. Some YAML. A folder rename. Sit with that feeling, then look at what actually changed: you can no longer be surprised by your own architecture, and you can no longer break your project without being told within four minutes. That is not a small week. In a course this short, that is the week the rest of it becomes possible.
Two things to carry into Monday. First, resist glamour. Every one of you has a technology you have been waiting for an excuse to use. Make it earn its place against your own requirements, spend your one token where it makes the project interesting, and be relentlessly boring everywhere else — because boring is what you spend so you can afford one interesting thing and still finish. Second, the review is in four days. Bring the honest version: the stub you labeled stub, the estimate that does not fit, the deviation you are not sure about. A reviewer can work with a document that admits what it does not know. Nobody can work with a document that pretends.
Lay the foundation. Take care how you build on it. Somebody else is going to.
See you on Monday.
Up next: the exercises builds the week rep by rep · Milestone 3 is Milestone 3 · then Chapter 4 — the design review, the baseline, and the halfway truth about your pace. Reference: Appendix A (workbench, environments, and diagramming tools), Appendix B (the Fast-Start Catalog and its suggested stacks), Appendix C (the Document Kit), Appendix D (the grading contract), Appendix E (glossary). Previous: Chapter 2.
Week 3 Knowledge Check
tracelens scan samples/one-line.log: six hops, no server, no browser, identical principle. Dropping a layer or mocking the store is precisely what stops a slice from being a skeleton. And it has to walk, not stand: standing is 'it compiles,' walking is 'somebody who is not me runs one command and watches a request travel all the way through and come back.' const items = [{name: 'oat milk'}], because 'the database part comes next.' How does the biggest rubric line score it?| Suggested piece | What justified it in the brief | Still true after my Week-2 edits? | Verdict |
|----------------------|--------------------------------|-----------------------------------|---------|
| A background worker | The nightly expiry digest | No — the digest was cut | ? | docs/plan.md, and what has to fit inside it?Available for the project, Weeks 4-8 .......... 71.0 h
Declared project buffer at 25% ................ 17.8 h
PLANNABLE ..................................... ____ h name: ci
on:
workflow_dispatch # only when I click the button
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install # not `npm ci`
- run: npm test || true workflow_dispatch alone means it runs only when you remember — and you remember exactly when you expect it to pass. npm install resolves whatever is newest today instead of reproducing your lock file, so the build says nothing about reproducibility. And || true makes the result green unconditionally. Wrong code fails loudly; a green pipeline that verifies nothing fails silently for five weeks and then all at once. Which is why Milestone 3 asks for evidence of a red run: after you set CI up, break it on purpose. If the build does not turn red, your pipeline is decorative.