Chapter 09 · Week 9

The Walking Skeleton

Why lay a foundation before you decorate?

Chapter 9 — The Walking Skeleton

“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. The architecture and the functionality can then evolve in parallel.” — Alistair Cockburn, who named the practice

“Let each one take care how he builds upon it.” — 1 Corinthians 3:10 (ESV)


Why This Matters

For eight weeks you have written. A charter. A requirements specification with identifiers. Non-functional requirements and a definition of done. A technology evaluation and the architecture decision records that defend it. A technical specification. A work breakdown, a schedule, a risk register. A design review that found defects in your own thinking and a baseline that froze the spec.

This week you write code. Construction begins.

You are wearing two hats at once now, and they fight each other. The developer wants to build the interesting part — the recipe suggester, the anomaly scorer, the thing you told your friends about. The release engineer wants a system that installs, builds, tests, and runs on a machine that is not yours, every single time, automatically. The developer’s instinct is to start in the middle. The release engineer’s instinct is to start at the edges. This week the release engineer wins, and here is why: the developer will spend the next seven weeks producing code, and if there is no automated floor under that code, week twelve is where you find out that nothing you built actually fits together.

So you will build the thinnest possible thing that works end to end. Not a login page. Not a beautiful dashboard fed by hard-coded arrays. One real request, entering your system where a real user enters it, travelling through every architectural component you named in your technical specification, touching a real data store, and coming back out as something a human can see. It will do almost nothing. That is the point. What it proves is not a feature — it is that the architecture in your specification is a real architecture and not a drawing.

Then you will make a machine that is not yours run it on every push. Continuous integration is not a Week-14 polish item; it is the instrument that tells you, all term, whether the thing still works. Every push, a clean stranger’s computer clones your repository, follows your setup instructions, and tries to build and test your project. That is the clean-machine test you will be graded on in Week 13, run automatically, seven weeks early, hundreds of times, for free.

The AI thread runs hard through this week, from both sides. As a tool, an assistant will happily scaffold your repository, write your CI workflow, and generate a data-access layer in nine seconds — and this is the first week unreviewed generated code can enter your repository and stay there for the rest of the term. As a workload, if your project calls a model, this is the week its key exists for the first time, which means this is the week you can leak it. The spine rule holds without exception: the assistant accelerates, you read every line before it is committed, and your name is on the commit.

And the apologetic question sitting under all of it: why lay a foundation before you decorate? Nobody has ever been praised for a foundation. It does not demo. It does not photograph. Yet every builder who ever skipped one paid for it, and Scripture keeps returning to the image. We will take that question seriously in 9.11, because it is not really a question about concrete.


9.1 — The Walking Skeleton: End to End Before Feature-Complete

Two teams start the same twelve-week project.

Team A builds features. Week one: a gorgeous front end with hard-coded sample data. Week three: a database, tested in isolation. Week five: authentication, tested in isolation. Week eight: they begin wiring it together and discover that the front end assumes a shape the API does not return, the database driver does not work on the deployment target, and authentication needs a session store nobody planned. Weeks nine through twelve are integration. There is no time left for the interesting part.

Team B spends three days building one screen that shows one row from one real table, served by the real route through the real service layer, on the pinned runtime version, installed from scratch by a robot on every push. It is ugly and it does nothing. Then they add features to a system that already works.

Team B ships. Every time. This is not a story about talent.

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.

Here is the distinction students get wrong, so let us make it concrete with the running example.

PantryPilot — the book’s example project (yours will differ): a household tracker for food, expiry dates, and what can be cooked tonight. Its technical specification names a web client, an HTTP route layer, a domain service, a relational store, and one third-party product-lookup API.

Not a walking skeletonWhy notAn actual walking skeleton
A styled pantry page rendering a hard-coded arrayNever leaves the browser. Zero hops.The pantry page fetches GET /api/items, which reaches a real service, which queries a real items table with one seeded row, and renders it
A login screen that posts nowhereVertical slice of nothing; auth is the hardest hop and it proves the least earlyAnonymous read of one item — auth arrives in iteration one, on top of a path that already works
Unit tests for ItemService with the database mockedProves the class, not the system. Mocks agree with you.One end-to-end assertion that a value in the response could only have come from the database
”The API works, look at this curlHalf the path. The rendered result is a hop.A human loads the page and sees the word oat milk

And the same discipline in a completely different shape:

TraceLens — the book’s contrast example: a command-line tool that parses server logs and reports anomalies. No UI, no auth, no HTTP.

TraceLens’s skeleton: tracelens scan samples/one-line.log parses one argument, opens a real file with the real reader, runs one real rule through the real analyzer, formats with the real reporter, prints one anomaly line, and exits 0. Six hops, no server, same principle. If your project is a CLI, a batch job, a game, or a data pipeline, the hops differ and the rule is identical: every component in your specification appears in the path, and the path is real from end to end.

And the skeleton has to walk, not stand. Standing is “it compiles.” Walking is “somebody who is not me can run one command and watch a request go all the way through and come back.” You will prove that in 9.9.

Coach’s Note — The skeleton is where your technical specification stops being an opinion. If a hop you drew in Week 6 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 out in Week 9, with a change-control process and seven weeks of runway, instead of in Week 13 with neither. That is not a setback. That is the skeleton doing its job. Log it as a change request against the baseline you froze in Chapter 8 and keep moving.


9.2 — A Repository Structure a Stranger Can Navigate

Your repository is the graded artifact. In Week 16 a person who has never seen it will open it and form an opinion in about ninety seconds. Build for that person now, because restructuring later means rewriting every path in every document. Here is the structure real capstone repositories tend to have in Week 9 — read it and wince, because you have written one:

pantrypilot/
├── final_v2/
│   ├── app.js
│   ├── app_backup.js
│   ├── app_old_DONT_DELETE.js
│   ├── test.js
│   └── notes.txt
├── screenshot1.png
├── screenshot2.png
├── db.sqlite
├── node_modules/
├── .env
└── README.md          <- "PantryPilot. Senior capstone. By me."

Six things are wrong, and every one costs points in Week 16:

  1. final_v2/ — version control implemented in folder names, inside a version control system.
  2. app_backup.js, app_old_DONT_DELETE.js — dead code a stranger must read to discover it is dead. Git remembers. Delete them.
  3. db.sqlite — a binary database in history. It conflicts on every merge and grows the repository forever.
  4. node_modules/ — dependencies committed instead of a lock file. Enormous, machine-specific, and it hides the real reproducibility question.
  5. .enva live secret, now in permanent history. See 9.7.
  6. A README that tells a stranger nothing they could not read from the repository name.

Now the version you are building this week:

pantrypilot/
├── README.md                  # what it is, and how to run it in five minutes
├── LICENSE                    # chosen in Week 5; matters in Week 14
├── CHANGELOG.md               # starts now, empty but present
├── .gitignore                 # committed FIRST, before anything worth ignoring
├── .env.example               # every variable the app reads, with placeholders
├── .nvmrc                     # the runtime version, pinned, in a file
├── package.json
├── package-lock.json          # committed — this is the reproducibility
├── script/                    # the contract, one command per verb
│   └── setup, lint, test, build, start, smoke
├── src/
│   ├── web/                   # routes and templates  (the client + route hops)
│   ├── domain/                # services; the rules live here (the service hop)
│   └── data/                  # queries, migrations, seed (the data-store hop)
├── tests/
│   ├── unit/
│   └── e2e/
├── docs/
│   ├── requirements.md
│   ├── architecture.md
│   ├── skeleton-trace.md
│   ├── ai-usage.md
│   ├── hours-log.csv          # keep your own format all term; export to CSV here before you submit
│   └── adr/0001-choose-the-stack.md
└── .github/workflows/ci.yml

The filenames above are the Node-shaped version of the idea. Your stack will differ — pyproject.toml and poetry.lock, go.mod and go.sum, pom.xml, Cargo.toml and Cargo.lock. What must not differ is the three-part shape: documents in docs/, source in a source tree whose top-level folders match the components in your technical specification, and one directory of one-word commands anybody can run without reading your mind.

That last one deserves a name. GitHub has published this pattern for years as “Scripts to Rule Them All” (see github.com/github/scripts-to-rule-them-all): a small script/ directory with normalized verbs — bootstrap, setup, test, server, and so on — so that every project a person joins is operated the same way, no matter what it is written in. Adopt it. Your script/setup might be npm ci && npm run db:migrate && npm run db:seed or poetry install && alembic upgrade head or ./mvnw -q verify -DskipTests. The stranger does not care. They type ./script/setup and it works.

Coach’s Note — Name your src/ subfolders after the components in your technical specification, and name them identically. When a grader reads docs/architecture.md, sees “the ItemService owns expiry rules,” and finds src/domain/item-service, the document and the code are corroborating each other. When the folders are stuff/, utils/, and helpers/, they corroborate nothing, and the specification starts looking like fiction.


9.3 — Branching, Commits, and Messages That Explain Themselves

You are a team of one, so you are tempted to work on main and commit whatever. Resist, for two reasons that have nothing to do with collaboration. First, the pull request is where your CI runs and where you review yourself — a branch plus a PR gives you a diff, a checklist, a place for CI to report, and a moment where you look at your own work before it becomes history; committing straight to main throws away all four. Second, your commit history is evidence. In Week 16 a grader can see whether you worked fifteen hours a week for sixteen weeks or fifty hours in the last nine days. They will not have to guess. Neither will an employer.

The discipline for a team of one:

  • main is always green and always runnable. If main is red, that is the only thing you work on.
  • One short-lived branch per issue, named for the requirement: fr-7-add-item, nfr-2-page-budget.
  • Open a pull request even though you are the only reviewer. Wait for CI. Read your own diff top to bottom before you merge.
  • Merge often — a branch older than about two days is a merge conflict with a countdown timer.
  • Never force-push a branch that anything else depends on.

Now the part students dismiss and graders read closely. Commit messages.

What students writeWhat it costsWhat to write instead
fixIn Week 15, you will need to know which of your nine fix commits broke the expiry filter. Nobody can bisect this.fix(items): treat expiry as end-of-day, not midnight
update codeSays nothing. The diff already told us what changed.refactor(data): move seed rows out of the migration
asdfReads as carelessness, because it is.anything true
Final version 2 FINALVersion control, defeated by hand.tag a release instead: v0.1.0-skeleton
did the thing prof askedNobody in industry knows who your prof is.feat(api): return items expiring within 7 days (FR-4)

The convention worth adopting is Conventional Commits (conventionalcommits.org): a type, an optional scope, and an imperative subject — feat, fix, docs, refactor, test, chore, ci. It costs nothing, it makes your CHANGELOG.md nearly free in Week 12, and it makes history skimmable. The full shape:

feat(api): return items expiring within 7 days

Seven days was chosen with the roommate group in the Week 2 interviews;
longer horizons made the list unusable (FR-4 rationale, requirements spec).

Uses a half-open interval [today, today+7) so an item expiring today still
appears. Timezone is fixed to the server's local zone; NFR-6 (multi-timezone)
is out of scope for v1 and tracked in #31.

Refs #12

Subject line: imperative mood, roughly fifty characters, no trailing period. Blank line. Body wrapped near seventy-two columns, explaining why, not what. The diff is the what. Your future self, and the stranger in Week 16, need the why — and the why is the only thing that is gone forever if you do not write it down.

Coach’s Note — Here is the test. Open your repository’s history and read the last ten subject lines as a list, the way a grader will. Do they read like a changelog of a project — or like the diary of someone who was tired? Nobody has to be told which one they are looking at.


9.4 — A Development Environment That Reproduces on a Machine You Do Not Own

“It works on my machine” is the oldest failure in this industry, and in a capstone it is fatal, because the machine it has to work on belongs to your grader.

Reproducibility is four commitments, and only four:

1. Pin the runtime version in a file. Not in your head, not in the README prose — in a file the tooling reads. .nvmrc, .python-version, .tool-versions, the go directive in go.mod, the java.version property in your build file. CI reads the same file, so CI and you cannot drift apart.

2. Commit the lock file. package-lock.json, poetry.lock, Cargo.lock, go.sum, Gemfile.lock. And then install from it: npm ci, not npm install; poetry install, not poetry add. The difference is the whole point — the first reproduces a recorded state, the second resolves whatever is newest today. A build that resolves fresh dependencies is a build that can break overnight because of a change in somebody else’s package.

3. Externalize configuration. Everything environment-specific — database URL, port, base URL, API keys, feature flags — comes from environment variables with sane defaults, never from a value typed into source. This is the third factor of the twelve-factor methodology (12factor.net/config), and its test is blunt: could you open-source this repository right now without changing a line? If not, you have configuration in your code.

4. One command to go from clone to running. ./script/setup then ./script/start. 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.

Two versions of the same README section, so you can see the difference a grader sees. Bad — and this is the median capstone README in Week 9:

## Installation

Install the dependencies and run the app. Make sure you have the right
version of Node and that the database is set up. Then npm start.

Every sentence hides a decision the reader cannot make. Which dependencies. Which version. “Set up” how. And nothing tells them whether it worked.

Good — and it takes four minutes to write:

## Run it (about five minutes)

Requires: Node 20 (pinned in `.nvmrc`) and PostgreSQL 16 — or Docker, which
    `script/setup` will use to start one for you. No admin rights needed.

    git clone <this repo>
    cd pantrypilot
    cp .env.example .env      # the demo path needs no real keys
    ./script/setup            # installs from the lock file, creates and seeds the database
    ./script/start            # serves http://127.0.0.1:8080

Open http://127.0.0.1:8080/pantry . You should see exactly one item:
"oat milk — expires in 3 days". If you do not, run `./script/smoke`;
it prints which hop of the request path failed.

The good version does three things the bad one does not: it names the one prerequisite, it gives the exact commands, and — most importantly — it tells the reader what success looks like. A stranger who follows your instructions and sees something must be able to tell whether that something is right.

For students on a locked-down laptop with no administrator rights, the browser-only path from Appendix A — a cloud development environment plus a .devcontainer definition committed to the repository — is not a lesser option. It is arguably a better one, because the environment definition is in the repo where a grader can use it too. Whatever path you took in Week 1, the deliverable is the same: somebody else can get it running.


9.5 — Continuous Integration on Every Push, From Week 9

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. Martin Fowler and the extreme-programming crowd made the argument decades ago and it has never stopped being right. The tooling changes; the practice does not.

For your capstone, CI does something specific and enormous: it is a clean machine, running your setup instructions, on every push, for the rest of the term. You will be graded in Week 13 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. Six stages — learn what each one proves, because a stage that proves nothing is theater.

StageThe commandWhat a failure actually tells you
Checkoutactions/checkoutAlmost never fails. If it does, your history is broken.
Install./script/setupYour dependency list is incomplete, unpinned, or needs something only your laptop has. This is the stage that catches “works on my machine.”
Lint / format./script/lintStyle drift, unused imports, dead code, and — with a decent config — real bugs.
Test./script/testBehavior regressed.
Build./script/buildIt runs in your dev server but does not survive compilation, bundling, or packaging.
Smoke./script/smokeThe skeleton stopped walking. Something in the request 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 only means it never runs unless you remember, and you will remember exactly when you expect it to pass — a pipeline you trigger by hand is a script. 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 single most common self-inflicted wound in student CI, and worse than having no CI at all, because it lies to you every day.

The correct trigger set is push on every branch plus pull request into main. The correct stage behavior is: any failure fails the build, loudly, with a red mark on the commit. Then three rules for the rest of the term:

  1. A red main is a stop-work order. No new features until it is green. Not “I’ll fix it tomorrow” — tomorrow you will have built on top of broken.
  2. Keep the pipeline under about ten minutes. A slow pipeline gets ignored, and an ignored pipeline is not a pipeline. If it creeps up, cache dependencies and split the slow suite into its own job.
  3. Never merge red. Even to yourself. Especially to yourself. Better: use your host’s branch-protection or rulesets feature to make green CI a requirement for merging into main, so the rule enforces itself at 1 a.m. when your judgment is worst.

Two things this book will not give you numbers for, because they change: which protection features your plan includes, and what CI minutes cost. Hosted minutes are generally free for public repositories on standard runners, with an included private-repository allowance that depends on the plan — but before you point a pipeline at anything that bills, read your provider’s current billing and security documentation (docs.github.com/en/actions) rather than trusting a figure in a textbook, including this one.

Coach’s Note — The most valuable minute of your entire CI setup is the one where you break it on purpose. Push a failing test. Watch the red X appear, open the log, find the line. Now you know what failure looks like before a real failure happens at midnight in Week 12. Milestone 9 requires you to do this and show the evidence, for exactly that reason.


9.6 — Issues Traced Back to Requirement Identifiers

In Week 3 you gave every requirement an identifier. This is the week those identifiers start earning their keep, because they become the spine that connects five things: requirement → issue → branch → commit → test. That chain is called traceability, and it is the difference between “I think I built it” and “here is where I built it, and here is what proves it.”

The mechanism is boring and it works: the requirement identifier goes in the issue title.

Bad issue — a real one, from a real capstone:

Title: fix login
Body:  (empty)

Good issue:

Title: [FR-7] Add an item to the pantry from the web form

Requirement: FR-7, requirements spec v1.1 (baselined Week 8)
Slice: web form -> POST /api/items -> ItemService.add -> items table -> 201 -> refresh
Estimate: 3.0 h (WBS task 4.2, Week 7 plan)

Acceptance criteria
- [ ] Submitting name + expiry adds exactly one row to `items`
- [ ] The new item appears in the list with no manual page reload
- [ ] An empty name is rejected with a visible message and writes no row
- [ ] `./script/test` covers the reject-empty-name case
- [ ] The FR-7 row in `docs/skeleton-trace.md` reads "done"

Everything in the good issue was decided weeks ago. You are not inventing it now; you are transcribing it out of documents you already wrote — which is exactly what those documents were for. If transcribing is hard, that is a defect in the specification, and Week 9 is a fine time to discover it.

Keep the traceability table current in docs/skeleton-trace.md — Part 2 of code/skeleton-trace.md is the template. Ninety seconds a week, and it answers, at any moment, the two questions that decide your grade: which requirements have nobody working on them, and which requirements have nothing verifying them. One rule with teeth: no issue without a requirement identifier. If you want to build something that has no identifier, you have found either a missing requirement (write a change request against the Week-8 baseline) or scope creep (say no). Both outcomes are good. What is not good is quietly building an unrequested feature in Week 10 and running out of hours in Week 14.


9.7 — Secrets, Configuration, and the First Commit You Cannot Take Back

Git history is permanent, public, and copied. If your project talks to a model API, a payment sandbox, a mapping service, or a database with a password, then this is the week a live credential exists on your laptop for the first time, and therefore the week you can leak it. Work the checklist in code/secrets-checklist.md before your first push. The order is not arbitrary:

  1. .gitignore goes in first — before there is anything worth ignoring.
  2. .env is ignored; .env.example is committed with every variable the app reads and placeholder values. It is your configuration documentation as well as your safety net.
  3. The app must start on .env.example values plus a local database. If it cannot, your README’s five-minute promise is false.
  4. Real keys live in your CI provider’s repository secrets and are referenced by name — ${{ secrets.PRODUCT_API_KEY }} — never as a literal in the workflow file.
  5. Never echo a secret. Never print the whole environment in a CI step. Providers mask registered secret values in logs on a best-effort basis, and that masking will not follow the value through a base64 encode, a JSON dump, or a string split.

And the part everybody gets backwards. If a key is already committed, rotate it first. Revoke it at the provider, issue a new one, and then clean the history. Rewriting history does not reach forks, clones, CI caches, or the terminal scrollback on somebody else’s laptop. Rotation is the fix; history cleanup is tidying up afterward. A student who leaks a key, rotates it within the hour, and writes the incident into their defect log has behaved like a professional. A student who deletes the file, commits “removed secret,” and moves on has published a working credential and told the world exactly which commit to look at.

Coach’s Note — Some hosts run secret scanning that can block a push containing a recognizable token format. Treat it as a smoke detector, not a fire-prevention plan: it only knows patterns it has been taught, and your database password is not one of them. The checklist is the plan.


9.8 — Scaffolding With an Assistant, and Reading Every Line

This is the first week an assistant is genuinely, enormously useful to you — and the first week it can quietly damage a repository you have to defend in seven weeks.

What it is good at, this week: producing a first-draft CI workflow for your specific stack (ask for it, diff it against code/ci-starter.yml, keep the union of what you understand); writing the boring glue — a migration, a seed script, a .gitignore for your ecosystem, an argument parser; explaining a build error you have never seen, faster than a search engine; and reviewing your structure — “here is my repository tree and my technical specification, where do they disagree?” is a genuinely good prompt.

How it hurts you, this week specifically:

  • It scaffolds a different architecture than the one you specified. Generated quick-starts carry the shape of whatever is most common in the training data, which is very unlikely to be the shape in your docs/architecture.md. You end up with a repository that contradicts your own specification, and you will not notice until a grader does.
  • It hard-codes secrets and hosts. Quick-start code is full of apiKey = "sk-..." and localhost:3000 because quick-starts optimize for a demo, not for a repository somebody else will run.
  • It invents configuration keys, action versions, and flags that look exactly right and do not exist. Pin versions you have actually looked up; never copy a version tag on faith.
  • It writes tests that assert nothing — a test file with one assert true, or a suite that collects zero tests and exits 0. Green, meaningless, and now your CI is lying to you (see 9.5).

The rule for the rest of this course, and it is the same rule as always: generated code becomes yours at the moment you commit it. Not when you paste it. When you commit it. From that instant it is your bug, your license problem, your leaked key, and your answer in the Week-16 defense.

So, three habits starting now:

  1. Read every generated line before it is staged. If you cannot explain what a line does, either learn it or delete it. There is no third option that survives a viva.
  2. Never let generation and commit be the same motion. Generate into the editor, read it, change something you disagree with, then commit. The change is the tell — if you never change anything, you are not reading.
  3. Log it. docs/ai-usage.md gets a dated line: what you asked for, what you kept, what you rejected and why. Two minutes a session. In Week 16 that file is the difference between disclosed, professional AI use and an integrity conversation. Chapter 10 develops that log in full.

Coach’s Note — The most dangerous generated artifact this week is not code. It is a CI workflow that passes without testing anything. Code that is wrong fails loudly. A green pipeline that verifies nothing fails silently for seven 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.


9.9 — Proving the Skeleton Walks

“It works” is a claim. This week you make it 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/smoke-test.sh, copy it to script/smoke, and change three lines to match your project.

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 data store — proves service → data store → response.

Step three is where students cheat without meaning to. If your smoke test passes with the database stopped, it is not testing the skeleton — it is testing that a process is alive. Try it: stop the database and run the smoke test. It must fail. If it passes, fix the test, not the database.

The acceptance bar for Milestone 9 is deliberately harsh, and it is one 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 the result in docs/skeleton-trace.md, built from code/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 the green CI run, and the URL of the red one you caused on purpose.

Honesty note on stubs. Some hops legitimately stay stubbed in Week 9 — a paid third-party API, a payment sandbox, a device you do not have yet. That is fine. What is not fine is writing yes in the “real?” column 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-14 catastrophe, because you will plan around a hop you believe works.


9.10 — Interactive Lab: The Walking Skeleton Checklist

Below this chapter on the website is The Walking Skeleton Checklist. Do it before you write this week’s code, and again after.

Part one — trace the path. Mark which hops of the request path actually work in your project today: client, route, service, data store, response, rendered result. The widget draws your skeleton and highlights the missing bone — the first hop that is not real — and names the next task. Be honest when you mark it. A hop backed by a mock is not a working hop, and the widget’s whole value is that it will not argue with you about it.

Part two — order the pipeline. Drag the stages — checkout, install, lint, test, build, deploy — into a working order, then inject a failure and watch what a red build blocks downstream. There is a panel on why a pipeline that only runs locally is not a pipeline; read it even if you think you already agree, because the argument is sharper than “it’s good practice.”

What it teaches: that breadth is what you are buying in Week 9, not depth; that the missing bone is always the next task and never the interesting one; and that the ordering of your pipeline encodes what you believe about cost — cheap, fast checks first, so the expensive ones never run on code that was already broken. Take a screenshot of Part one after your skeleton walks; it goes in your milestone submission as your own before-and-after.


9.11 — 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 metaphor and picks a very particular role in it. The Greek word 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, if it is 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 we defer the part that merely holds. Every capstone that dies in Week 13 died of this, and the student who built it worked hard the whole time.

Scripture is stubborn on the point. The wise builder in Luke 6 is the one who “dug deep and laid the foundation on the rock” (Luke 6:48, ESV) — and the striking detail is that on a clear day, the two houses look identical. The difference is invisible until the flood, and then the difference is total. The man in Luke 14 who begins to build without counting the cost 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 in Week 9: “someone else is building upon it.”

That is the entire thesis of this 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 14 when you write the handoff guide, in Week 16 when a stranger clones your repository and tries to run it, and in your career when you leave a job and your code stays. The foundation you lay this week is not for your comfort. It is for the person who comes after you, and you will probably never meet them. That is what makes it a moral act and not merely an engineering one.

There is a counter-argument worth taking seriously, because good engineers make it: isn’t this over-building? Doesn’t agile practice tell us to defer decisions, to not build what we 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 have 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 there is a quiet warning in the passage that is worth naming in an AI-saturated week. Paul says: let each one take care how he builds. Not how fast. Care is a property of the builder, not of the building. An assistant can produce a plausible repository in ninety seconds — and plausible is precisely the failure mode, because a foundation that only looks like a foundation is worse than no foundation at all: 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, in part). This week is the very little. .gitignore before the first commit. A lock file. A commit message that says why. A test that would actually fail. None of it will be praised, and all of it will hold.


9.12 — Common Pitfalls

Pitfall: The skeleton that does not walk — a beautiful UI 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.


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: The committed secret. Example: .env in the first commit, or a generated quick-start with the API key inline, pushed at 1 a.m. Fix: .gitignore first, always. If it is already pushed: rotate the credential first, then clean history, then log the incident. Deleting the file is not a fix; it is a signpost pointing at the commit that still has it.


Pitfall: A generated repository structure 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, that is a change request against the Week-8 baseline — update the document, do not let the two silently disagree.


Pitfall: Issues with no requirement identifier. Example: A board full of “fix login”, “make it faster”, “cleanup”, none of which map to anything you promised to build. Fix: No issue without an identifier. If it has no identifier, it is either a missing requirement (write the change request) or scope creep (close it). Both answers take one minute and save a week.


Pitfall: “I’ll add CI later, once there’s something worth testing.” Example: The workflow file first appears in Week 14, alongside the first attempt to deploy. Fix: Add it in Week 9 with three trivial stages. CI is cheapest to introduce when the project is small and more expensive every week you wait — exactly backwards from how students schedule it.


9.13 — Where Your Hours Went This Week

Fifteen hours, honestly logged. A realistic Week-9 shape, so you can compare against your own log and see where you are slower or faster than the plan:

WorkHours
Repository restructure, .gitignore, secrets audit, .env.example2.0
The script/ contract and a bootstrap that runs from clean2.0
Building the walking skeleton slice (real code, all hops)4.5
CI workflow: writing it, and the four pushes it took to go green2.5
Issues created and traced to requirement identifiers; board updated1.5
Smoke test, the deliberate red build, docs/skeleton-trace.md1.5
Hours log, milestone write-up, docs/ai-usage.md, weekly quiz1.0
Total15.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 seven weeks. If the skeleton took nine hours, something in your architecture is harder than your Week-6 specification claimed; write that into the risk register today instead of discovering it in Week 12. Log the real numbers either way — Week 7’s estimates get graded against reality in Week 15, and an honest overrun teaches you more about estimating than a flattering one ever will.


9.14 — Reps

This week’s reps are in the exercises, and they are not warm-ups — done in order, they are Milestone 9. Preview:

  • Rep 1 — draw your request path as a hop table and mark, honestly, which hops are real today.
  • Rep 3 — rewrite five genuinely bad commit messages into ones that would survive a Week-15 bisect.
  • Rep 5 — the stranger’s machine: delete your dependencies and re-bootstrap from your own README, on a timer.
  • Rep 8 — break CI on purpose, twice (a failing test, then a lint error), and record exactly what it reported.
  • Rep 11 — run the secrets audit across your entire history and write your rotation plan before you need it. Do the on-page Check Your Reps quiz when you finish the chapter — it is the ungraded rehearsal for Week 9 Quiz in Canvas, and the early-warning system for whether this week actually landed.

9.15 — This Week’s Milestone

Milestone 9Walking Skeleton & Continuous Integration.

You will ship a repository that a stranger can clone and run, containing one real request path that works end to end, verified by a smoke test, built and checked by CI on every push, with issues traced to requirement identifiers and no secrets anywhere in history.

Remember the grading contract: the milestones are graded twice. They carry 25% together, and they are also the final deliverable, produced one week at a time. The Week-16 submission is 50% of your grade, and it awards points for exactly these artifacts a second time. A skeleton you skip this week is not a small penalty now — it is milestone points lost and the same artifact points moved to a week when there is no time left to earn them. See Appendix C for the full contract.


9.16 — Coach’s Final Word

Eight weeks of documents. Today, code — and not the code you wanted to write. You wanted the recipe suggester. What you are going to build is one row of text that travelled a long way, plus a robot that checks your work while you sleep. It will not impress anyone at dinner. In seven weeks it will be the reason your project exists and someone else’s does not.

Here is what you actually bought. The right to add features to a system that works, instead of the obligation to make a working system out of features. A stranger’s computer that checks every push. A repository somebody else can navigate. And the finding — whatever it is — that your Week-6 architecture was right, or was wrong in one specific place you now have seven weeks to fix instead of one.

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 skeleton rep by rep · Milestone 9 is this week’s milestone · then Chapter 10 — Iteration One, where the developer hat goes on at full speed and the skeleton grows muscle. Reference appendices: Appendix A (workbench and environments), Appendix B (document kit), Appendix C (grading contract), Appendix E (glossary). Previous: Chapter 8.

Interactive Lab — Week 9
The Walking Skeleton Checklist

Two drills. In Part 1, tick only the hops a real request completes today, on a clean checkout — the widget finds the first gap and names the next task. In Part 2, reorder the CI stages with the arrow buttons, break one on purpose, and watch what a red build stops. Every verdict below recalculates as you go.

Part 1 · Does one real request travel the whole system?

A hop only counts once a request you did not hand-craft has actually passed through it.

gap

Something issues the request — a form, a script, a test client.

gap

The request reaches a registered endpoint, not a 404.

gap

The route calls your own logic instead of answering inline.

gap

A real read or write crosses the storage boundary.

gap

The result comes back in the shape the client agreed to.

gap

A human sees the answer in the interface you will demo.

Part 2 · Is that a pipeline, or a habit?

Put the stages in a workable order, then break one. The arrow buttons are keyboard operable.

  1. 1 install queued
  2. 2 checkout queued
  3. 3 deploy queued
  4. 4 lint queued
  5. 5 build queued
  6. 6 test queued

The ordering rules and the Week 9 gate are teaching heuristics, not your rubric. Real pipelines run stages in parallel and name them differently; the dependencies between them are what generalise.

Check Your Reps

Week 9 Knowledge Check

Question 1 of 5
Chapter 9 says a walking skeleton is thin in a very specific sense. Which statement gets it right?
Why: Breadth, not depth. The skeleton must touch every architectural component you named in Week 6 and come back with a real result — one row, one line of output. That is what turns your specification from a drawing into an observed fact. Collapsing a layer to make it thinner defeats the entire purpose, because the hop you deleted is precisely the one you have not proved. And it has to walk, not stand: standing is 'it compiles'; walking is 'somebody who is not me can run one command and watch a request go all the way through and come back.'
Question 2 of 5
This is a student's entire CI workflow. Chapter 9 calls it three lies in nine lines. Which line makes the build report success whether or not the tests actually pass?
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
      - run: npm test || true
Why: All three lies are real, and each fails differently. workflow_dispatch alone means the pipeline runs only when you remember — and you remember exactly when you expect it to pass, which makes it a script, not a pipeline. npm install resolves whatever is newest today rather than installing from your lock file, so the build says nothing about reproducibility. But || true is the one Chapter 9 calls worse than having no CI at all, because it lies to you every day: code that is wrong fails loudly, while a green pipeline that verifies nothing fails silently for seven weeks and then all at once. This is why Milestone 9 makes you break your pipeline on purpose and submit the red run URL.
Question 3 of 5
Rep 1 asks for an honest hop table in docs/skeleton-trace.md. A hop is yes only if the production component actually runs. Which row below is the clear violation, and why?
| # | Hop                  | Component        | Real? | Evidence                          |
|---|----------------------|------------------|-------|-----------------------------------|
| 1 | Client / entry point | pantry page      | yes   | page loads in the browser         |
| 2 | Route / dispatch     | GET /api/items   | yes   | handler logs each request         |
| 3 | Service / domain     | ItemService.list | yes   | called by the route on every hit  |
| 4 | Data store           | items table      | yes   | fixture file seed/items.json      |
| 5 | Response             | JSON body        | yes   | returned to the page              |
| 6 | Rendered result      | list view        | yes   | "oat milk" visible on the page    |
Why: Rep 1 states the rule without wiggle room: a mock is no, a hard-coded array is no, a TODO is no. Writing yes in row 4 is the mistake that quietly ends capstones, because from that moment you plan around a hop you believe works. If a hop legitimately has to stay stubbed this week — a paid third-party API, a device you do not have — that is fine and professional: write stub, name the ADR or issue that says when it becomes real, and move on. The dishonest yes is what Chapter 9 calls 'the beginning of a Week-14 catastrophe.' Also note the smoke test that would expose row 4 in ten seconds: stop the data store and run it. It must fail.
Question 4 of 5
You discover that a live API key was committed and pushed three days ago. What does Chapter 9 tell you to do first?
Why: This is the part everybody gets backwards. Rewriting history does not reach forks, clones, CI caches, or the terminal scrollback on somebody else's laptop — so rotation is the fix and history cleanup is tidying up afterward. Deleting the file and committing 'removed secret' is worse than doing nothing: the credential is still live, still in history, and you have now posted a signpost telling the world which commit to look at. A student who leaks a key, rotates it within the hour, and writes the incident into their defect log has behaved like a professional. And note the ordering rule that prevents all of this: .gitignore goes in first, before there is anything worth ignoring.
Question 5 of 5
Milestone 9's deliverable list includes evidence that CI can fail. What exactly are you required to produce this week?
Why: Deliverable 5, worth 6 rubric points, and Rep 8 asks you to do it twice — once with a failing assertion, once with a lint error — because the usual culprits (|| true, a suite collecting zero tests, a lint config with everything disabled) hide behind different stages. Branch protection is a genuinely good idea, but in this milestone it is a Medium-tier option, not the evidence. The reason the requirement exists is stated in the Coach's Note: the most valuable minute of your CI setup is the one where you break it on purpose, so that you learn what failure looks like before a real failure picks the time — midnight in Week 12. If you have not caused a red build yet this week, you are behind on this milestone, and the gradebook is about to say so.
YOU FINISHED. NICE WORK.