Chapter 05 · Week 5

Choosing a Stack That Holds Together

Why does wisdom test a thing before trusting it?

Chapter 5 — Choosing a Stack That Holds Together

“Choose boring technology.” — Dan McKinley, “Choose Boring Technology” (2015)

“but test everything; hold fast what is good.” — 1 Thessalonians 5:21 (ESV)


Why This Matters

This week you put on the architect’s hat, and it is the first hat that lets you hurt your future self from a distance.

Everything so far has been reversible. A charter can be rewritten; a scoping decision revisited; a requirement re-worded on a Tuesday, and nobody bleeds. But the stack you pick this week — language, framework, data store, auth, host, CI, and whether an AI provider sits in your critical path — gets built into the thing. In Week 9 you lay a walking skeleton on top of it. In Week 12 you integrate against it with the schedule tight. By Week 14 you are making it reproduce on a machine that is not yours, in front of a grader who does not care how much you liked the framework.

You are at the front edge of the design phase. Weeks 3 and 4 produced a numbered functional specification and measurable non-functional requirements; Week 6 turns those into a technical specification of components, interfaces, a data model, and sequence flows. This week is the hinge. You cannot design components until you know what you are designing them in, and you have no honest basis for that choice until the requirements exist. Most students get the order backwards — they pick the stack in Week 1 from a job posting, then spend fifteen weeks bending requirements to fit it.

Here is the sentence to carry all week: a stack is not a set of technologies, it is a set of seams. Any competent senior can learn a new framework. What kills capstones is not the pieces but the joints between them — the ORM that will not speak to the database version the host provides, the auth library whose session model assumes a server you do not have, the CI runner that cannot see the private registry. Nobody writes those on a résumé, and they are where the hours go.

The AI thread runs hard this week from both sides. As a tool, an assistant is genuinely excellent at generating the option space — “what are seven ways to do session auth for a small web app?” — and at drafting a first-pass tradeoff table. It is also, on exactly this task, the most hallucination-prone thing in this course: version numbers, prices, free-tier limits, license terms, and rate limits are precisely the facts that change monthly and that a model will state with beautiful, ruinous confidence. As a workload, a hosted model in your project is a dependency with a bill, a rate limit, a data-retention policy, and a deprecation schedule you do not control. Evaluate it like any other vendor, and say in writing what your software does when it goes away.

Which brings the week’s question, and it is an old one: why does wisdom test a thing before trusting it? Paul told the Thessalonians to test everything and hold fast what is good. Not “trust nothing.” Not “trust the confident voice.” Test, then hold. We come back to that in 5.12, because it is this whole week’s engineering discipline compressed into six words.


5.1 — Start From the Requirements, Not From the Job Board

There are two ways to choose a stack, and only one survives a design review.

The first is selection by identity. You want to be a person who ships Rust, or knows Kubernetes, or has a graph database on their résumé — so you pick it, then go looking for reasons. This feels like engineering because it produces a table. It is not engineering, because the conclusion existed before the evidence. The second is selection by driver: you open docs/requirements.md, find the handful of requirements that genuinely constrain the technology, and let those pick the shortlist.

Most of your requirements do not constrain the stack. “The user can rename a pantry item” runs on anything. The ones that constrain are called architectural drivers, and a student project usually has four to eight. They come from four places:

Source of a driverWhat to look for in your own documentsPantryPilot example
Data shape and accessJoins, ordering, transactions, full-text search, geospatial, time seriesFR-011 matches recipes against items on hand — a three-way join
A hard number in an NFRLatency, concurrent users, uptime, data volume, memory ceilingNFR-P-02: main screen under 500 ms at 1,000 items
An external interfaceA third-party API, a device, a file format, an existing systemThe barcode/product lookup API
A constraint you cannot moveHosting you have access to, a license you must honour, a browser you must support, hours you actually have240 hours total; no budget for a paid host until Week 14

Do this literally. Print your requirements. Highlight every one that would change your technology choice if it changed. If you highlight thirty, you are highlighting wrong — go back and ask “would this genuinely be easier in a different stack?” If you highlight zero, that is a real and useful finding: your project is stack-indifferent, so pick the thing you already know and spend the saved hours on the product. That is not settling. That is a senior move.

Coach’s Note — Wanting a technology on your résumé is a legitimate goal. It is just not a requirement, so it does not get a row in the matrix. Put it where it belongs: in a personal-goals line in your charter, weighted at whatever you think it is worth, and then be honest when it loses to a driver. A capstone that ships with boring tools beats a capstone that stalls in Week 11 with exciting ones — and the interview question is “tell me about a hard tradeoff,” not “list your frameworks.”


5.2 — The Evaluation Matrix: Criteria, Weights, and Honest Scores

A weighted evaluation matrix is the standard artifact for a technology decision, and it is trivially easy to rig. Doing it honestly takes three disciplines. Discipline one: derive the criteria from the drivers, and name the requirement — a criterion that does not trace to a requirement identifier or a stated constraint is a preference.

Bad criterionWhy it failsThe good version
”Modern”Unfalsifiable; means “I like it""Has a maintained release within the last 12 months"
"Scalable”You will never approach a scaling limit at student scale”Handles FR-011’s join at 1,000 items under NFR-P-02’s 500 ms"
"Good documentation”Everyone claims this”Official docs contain a working quickstart for my exact deployment target"
"Fast”Fast at what?”Cold-start under 2 s on the host in ADR 0003”

Discipline two: set the weights before you see the scores. Highest-leverage rule in the section. Once you have scored the options you will unconsciously tune the weights until your favourite wins — so weight first, in a separate sitting, and commit the file. Then score. Discipline three: every score gets evidence. A number with no evidence cell is a feeling with a decimal point.

The provided template is code/tech-evaluation-matrix.csv — long format, one row per decision × option × criterion, with an evidence column that is not optional. Score 0–5; weights within a decision sum to 1.00. Then run the provided checker:

python3 score-stack.py tech-evaluation-matrix.csv
python3 score-stack.py tech-evaluation-matrix.csv --decision data-store

code/score-stack.py does four things you will not do reliably by hand: it verifies the weights sum to 1.00, catches an option you scored on only four of five criteria, refuses any score with an empty evidence cell, and warns you when the top two options are within 0.25 — which means the decision is a coin flip and you should take the one that is easier to reverse, not the one that scored higher.

Run it on the shipped example and the PantryPilot data-store ranking comes back 4.70 SQLite, 3.90 PostgreSQL, 2.10 MongoDB. Hold onto that result. In 5.10 you will watch the student choose PostgreSQL anyway — and write down why. A matrix is decision support. It is not the decision. The moment you let a spreadsheet be accountable for an architecture, you have delegated the one thing an architect cannot delegate.

Then do a sensitivity pass: halve the weight of your top criterion and rerun. If the winner changes, your decision rests on one number you probably guessed — say so in the ADR, or go get better evidence for it.


5.3 — Integration Risk: The Seams Are Where Projects Break

Here is the failure mode nobody warns you about. You evaluate the language, the framework, the database, the auth, the host, the CI. Each decision looks fine in isolation; every matrix comes back green. Then in Week 10 you discover the combination cannot be made to work in the hours you have — because the risk was never in the boxes. It was in the arrows.

Build a seam inventory — one row per boundary where two chosen pieces have to talk:

SeamThe thing that has to workCrossed it before?RiskSpike
App ↔ databaseDriver + migration tool against the host’s DB versionYes, on SQLite onlyMediumSP-01
App ↔ product APIAuth scheme, rate limit, response shape, terms of useNoHighSP-02
App ↔ AI providerSDK version, timeout, streaming, cost per callNoHighSP-03
CI ↔ testsTest runner needs a live database in the runnerNoMediumSP-01
CI ↔ hostDeploy credentials, build artifact formatNoMediumSP-04
Browser ↔ appSession cookie behaviour over the host’s TLS/proxyNoLow

Three rules for reading that table. One: rank by seams you have never crossed, not components you have never used. An unfamiliar library alone is a study problem — bounded, and the docs will get you there. An unfamiliar seam is an integration problem, and integration problems have no docs, because they are specific to your combination. That is why the third column exists.

Two: the seam inventory sees what the per-decision matrix cannot. Look again at PantryPilot. Scored alone, SQLite won the data store outright — but the hosting decision landed on a managed platform running two web workers, and two processes writing one SQLite file is a seam, not a database question. Neither matrix contained that fact, because it lives between them. Score each decision separately, then read the seams across all of them. That second pass is where the architect earns the hat.

Three: every High seam gets a spike this week or next, not in Week 12. A spike is the smallest experiment that turns an unknown into a known — time-boxed, answering exactly one question, producing a paragraph and a decision rather than a feature. Use code/spike-plan-template.md; it carries a worked PantryPilot spike so you can see the shape. A spike question is good or useless, and you can tell at a glance:

Useless spike questionGood spike question
”Try the barcode API.""Does the API return a usable product name for at least 8 of 10 items from my own kitchen, in under 1 second each, on the free tier?"
"See if the AI thing works.""For 20 realistic pantry lists, does the model return a parseable recipe object at least 18 times, and what is the p95 latency?"
"Set up CI.""Can a pull request run my three existing tests against a real database in the runner, in under 5 minutes, without a paid plan?”

The difference is that the good ones can fail. Write the failure criterion before you run the spike, or you will negotiate with yourself afterward — every architect who skipped that step has discovered they are a remarkably persuasive negotiator.

Coach’s Note — The most valuable thing a spike produces is permission to stop. Ninety minutes, one question, an answer, a paragraph in docs/spikes/, and the unknown is retired. Without the time box, a spike quietly becomes the project — I have watched a student spend eleven hours “spiking” an auth provider they had already decided to use. That is not de-risking. That is procrastinating in a technical dialect.


5.4 — Novelty Load, and the Rule of One New Thing

Count the pieces of your stack you have never shipped with. Not “read a tutorial about.” Shipped with — built something, deployed it, debugged it at 11 p.m. That count is your novelty load, and it is the best single predictor I know of whether a capstone finishes.

Novelty loadWhat it meansWhat to do
0–1Healthy. You will spend your hours on the product.Proceed. Spend the surplus on quality.
2Manageable if the two are not in the same seam.Spike both in Weeks 5–7. Budget learning hours explicitly in Week 7’s WBS.
3Danger. Learning curves compound; they do not add.Demote one to a familiar equivalent, or cut the requirement that demanded it.
4+This is how capstones die.Rebuild the stack around what you know. This is not a suggestion.

Why compounding? Because when something breaks across two unfamiliar pieces, you cannot tell which one is lying to you. With one new thing, the unfamiliar piece is the suspect and you go read its docs. With three, every debugging session starts with an hour of establishing which layer is even at fault. The cost is not 3×; it is worse, and it arrives in the weeks with the least slack.

Steal Dan McKinley’s framing. A team gets a small, fixed budget of innovation tokens — he puts it at roughly three — and every exciting new technology spends one. Everything else should be boring, in the specific and honourable sense of well-understood, with known failure modes and answers on the internet. You are one person with 240 hours, so budget one token: spend it on the piece closest to what makes your project interesting, and be relentlessly boring everywhere else. That is the rule of one new thing — one genuinely new technology, chosen deliberately, spiked early, and named in an ADR as the place your learning budget goes.

Coach’s Note — “But I want to learn.” Good — you should. Learn one thing well enough to defend it in a design review, ship it, and write honestly about what it cost. That is a stronger story in an interview than four half-learned tools and an unfinished repository. Depth reads as competence. Breadth without a shipped artifact reads as a tutorial list.


5.5 — Build, Buy, or Borrow

For every meaningful capability your requirements demand — auth, payments, search, email, PDF generation, image processing, the AI feature — you have three moves: build it yourself in your own code, buy a hosted service (someone else’s API, with a bill and a dependency), or borrow a library or open-source component you run yourself.

The decision hinges on one question: is this capability the thing your project is actually about? If it is, build it — it is your contribution, your learning, and the part of the demo that matters. If it is not, buying or borrowing is nearly always correct, because every hour spent building undifferentiated plumbing is an hour not spent on the thing you will be graded and interviewed on.

CapabilityDefault moveWhyWhen to flip
Password auth / sessionsBorrowSecurity-critical, well-solved, easy to get subtly wrongNever build it from scratch for a capstone
Email deliveryBuyDeliverability is an infrastructure problem, not a code problemIf email is out of scope, cut it
Full-text searchBorrow, then buyYour database probably already has enoughOnly if a requirement names ranking or fuzziness
PDF / report generationBorrowSolved, boring, tedious to writeIf report layout is the product
The AI featureBuy (a hosted model)Training your own is not a 240-hour taskIf the model is your contribution — then it is your one token
Your core domain logicBuildThis is the projectNever buy this

Two hazards. Buying is not free just because the tier is — a bought service is a seam, a bill, a rate limit, an outage you cannot fix, and a terms-of-service document that may forbid what you are doing with it. Buy deliberately, and record it in an ADR like any other dependency. And borrowing has a license (see 5.7); a library you install in Week 6 and discover is copyleft in Week 15 is a genuinely bad afternoon.

Whichever way you go: put it behind one interface. One module owns the product API; one module owns the model calls. If a dependency has to be swapped in Week 12 — and one of them will — you want the change to be a file, not an archaeology project. Reversibility is a design property: cheap to buy now, expensive to retrofit.


5.6 — Cost, Hosting, and the Free Tier That Expires in Week 12

Almost every student capstone runs on free tiers, and almost every student treats “free tier” as a permanent property of the universe. It is a business decision made by a company, and companies change them — Heroku withdrew its long-standing free product tiers in late 2022, and a great many student projects and tutorials died in a week. I will not print prices or limits in this book; they would be wrong by the time you read it. What I will give you is the discipline. Build a cost sheet at your actual scale — your scale is you, your grader, and maybe five friends, not a million users:

LineWhat to estimatePantryPilot shape (illustrative — get your own numbers)
Compute / app hostingOne small always-on or sleep-on-idle instanceSleep-on-idle acceptable except during the live presentation
DatabaseStorage in MB, not GBFar under any advertised cap; the risk is idle-suspend, not size
Bandwidth / requestsRequests per month across a semester of demosTrivial
Third-party APICalls per month × price per callVerify the rate limit, not just the price
AI providerCalls/month × tokens/call × price/tokenThe only line that can surprise you — see 5.8
Domain / TLSDo you actually need a custom domain?No. The host’s subdomain is fine for grading.
TotalA number you can say out loudAim for zero-to-single-digit dollars a month

Then build the watch list — the part everyone skips. One row per free service in docs/free-tier-watch.md, with six columns: service, what is free, verified on, where I read it, expiry or risk, what I do if it ends. Three rules for it:

  1. Every row has a “verified on” date and a link. Not a blog post. The vendor’s own pricing or documentation page.
  2. Every row has a fallback. If this becomes paid or disappears, what do I do? Answer now, while it costs one sentence.
  3. Prefer services that cannot silently bill you. If a card is required, set a hard spend limit where the vendor offers one, and check the account in Weeks 10 and 14.

For hosting, add the constraint most students miss: can your CI reach it, and can a grader open it without an account? A brilliant deployment behind a campus VPN your grader cannot reach is, for grading purposes, not deployed. Appendix A has the workbench paths known to work on a locked-down laptop, including the no-admin browser path.

On CI: as of 2026, GitHub Actions is generally free for public repositories on standard runners while private repositories draw against a monthly allotment — but the numbers move, so read GitHub’s billing documentation 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 single cheapest insurance policy in this course is making your repository public from Week 1 (with no secrets in it, ever). Public repos get the friendliest free-tier treatment almost everywhere, your grader can clone without a login, and it forces the habit of keeping credentials out of the tree. If your project must be private for a legitimate reason, write that reason in an ADR — including what it costs you.


5.7 — Licenses You Are Allowed to Ship

You will hand this repository to a grader, put it on your résumé, and possibly keep working on it. Two license questions matter, and they are different questions.

Question one: what license do you put on your project? Pick one deliberately and put the file at LICENSE in the repository root. The Week-16 rubric has a line for it. If you want the widest possible reuse and the least friction, a permissive license (MIT, Apache-2.0, BSD-3-Clause) is the ordinary choice; Apache-2.0 adds an explicit patent grant. If you want derivatives to stay open, copyleft (GPL-3.0) is the ordinary choice, and AGPL-3.0 extends that obligation to software offered over a network. Use the SPDX identifier — the canonical list is at spdx.org/licenses — and if you want a plain-language starting point, choosealicense.com is maintained by GitHub. Check whether your institution has a policy on student work; some do, and it is better to know in Week 5.

Question two: what are you allowed to do with what you borrowed? This is the one that bites. Build a license inventory:

DependencySPDX idTypeObligation on meShip?
web frameworkMITPermissiveKeep the noticeYes
chart libraryApache-2.0PermissiveKeep notice + NOTICE file if presentYes
a CLI tool used at build timeGPL-3.0-onlyStrong copyleftOnly runs during build; not linked into my productYes, and note why
a component I would link into my appAGPL-3.0-onlyNetwork copyleftWould obligate me to offer source to network usersDecide deliberately
an “available-source” server productnon-OSI (e.g. SSPL)Source-availableNot an OSI-approved open-source license; terms restrict offering it as a serviceRead the actual terms

Four things a senior should know cold:

  1. “Free to download” is not a license. The license is the license. Find the LICENSE file in the dependency’s repository and read the identifier.
  2. Copyleft’s reach depends on how you use it. A GPL tool you invoke as a separate program at build time is a very different situation from a GPL library you link into your application. If your project turns on getting this right, say so plainly in the ADR and ask your instructor — a wrong confident answer here costs real money in industry.
  3. Source-available is not open source. Widely used data products have moved between licenses in recent years — MongoDB’s server moved to the SSPL in 2018, and Redis and Elasticsearch have each changed terms more than once since. Do not trust your memory or a model’s. Open the project’s own license file today and date what you find.
  4. Assets have licenses too — fonts, icons, images, sample datasets, the recipe list you scraped from somewhere. A non-commercial or no-derivatives clause is a requirement, not a detail. Chapter 4 raised data rights as a non-functional requirement; here it becomes a shipping decision.

Your milestone asks for this inventory with SPDX identifiers and an explicit ship/no-ship call. Ten minutes now; a rewrite in Week 15 otherwise.


5.8 — The AI Provider Is a Dependency Like Any Other

If your project calls a hosted model, you have taken on a vendor. Evaluate it like any other vendor, with these six questions, and answer them in an ADR of its own. 1. What does a call cost, at your volume? Hosted models are typically billed per token, input and output priced separately. Do the arithmetic for your feature: calls per demo × tokens per call. Then multiply by ten, because you will call it far more while developing than in the demo. Prices change often; get the number from the vendor’s pricing page and date it.

2. What are the rate limits? Requests and tokens per minute, usually tiered by account age or spend. The failure mode that actually hits you is not the bill — it is being throttled while demoing in Week 16 because you tested the same feature forty times in the ten minutes before. 3. What happens to the data you send? If a user’s pantry contents, or worse a user’s personal information, goes to a third party, that is a privacy requirement from Chapter 4 landing on you now. Many providers offer some form of no-training or limited-retention handling for API traffic, sometimes only on certain plans or by opt-in. Read the actual terms and cite them; do not repeat what somebody said on a forum, and do not repeat what a model told you.

4. What happens when the model is deprecated? Specific versions get retired, sometimes on a few months’ notice. Pin the exact model identifier in configuration, never in scattered code, and write in the ADR what you do when it goes away. 5. What does your software do when the call fails? Not if. Timeouts, 429s, malformed JSON, an answer that is confidently wrong. Your technical specification in Chapter 6 will need a defined fallback path, and your requirement from Chapter 3 needs acceptance criteria that survive a probabilistic component. The design rule: the AI feature must be removable. If your app is unusable when the provider is down, the AI is not a feature, it is a single point of failure with a marketing department.

6. Who is accountable for the output? You are. Always. A recipe suggestion that tells a user to eat something spoiled is your defect, in your defect log, in Week 11.

Coach’s Note — Put the model behind one module with one function signature, and make the non-AI path the default in your walking skeleton in Week 9. Students who wire the model into the core loop first end up unable to demo anything when their key rate-limits during the presentation. Students who built the boring path first just turn the feature off and keep talking. Be the second student.


5.9 — Using an Assistant Here — and Why It Is the Riskiest Week to Trust One

Let me be precise, because the honest position is neither “don’t use it” nor “let it decide.”

What an assistant is genuinely good at this week:

  • Generating the option space. “List eight ways to handle session auth in a small web application, including unfashionable ones.” You get options you had not considered — exactly what you want before you narrow.
  • Naming criteria you forgot. “What non-obvious criteria should a solo developer weigh when picking a data store for a six-month project they must hand off?” Strong.
  • Arguing the other side. “I have chosen X. Make the strongest case for Y, and name three ways X will hurt me in month three.” Excellent, and hard to get from a friend who does not want to argue with you.
  • Drafting the ADR’s Context section from your own notes, which you then rewrite in your voice with your requirement identifiers.

What it gets wrong, reliably, on this exact task:

  • Version numbers. A training cutoff means it will state a stale “latest” version, or invent one.
  • Prices and free-tier limits. These change monthly and are among the most confidently hallucinated facts in the domain. License terms are the same: it will call a product “open source, MIT” that was relicensed years ago.
  • Compatibility claims. “Library A works fine with framework B version 5” is the kind of plausible sentence that is generated rather than known — and this is your seam, the highest-consequence claim on the page.
  • Rate limits and API shapes. It will produce a beautiful, plausible, non-existent endpoint.

So here is the rule for this week, the strictest in the book:

Every version, price, limit, license, and compatibility claim in your Milestone 5 documents carries a source and a date, or it does not go in.

Practically: keep a verification log — a table at the bottom of docs/tech-evaluation.md, or its own docs/verification-log.md. One row per claim: the claim, the official source URL, the date you checked. When a claim from an assistant turns out to be wrong, log that too, in docs/ai-usage.md, which you have kept since Week 1. By Week 16 you will have a personal, empirical hit rate for this class of question — a more useful thing to carry into industry than any framework.

The spine rule for this week: the assistant expands the option space; you verify the facts; you own the decision. The ADR has your name on it. There is no version of this where “the model recommended it” is an answer in a design review, and certainly none where it is an answer to a client.


5.10 — Architecture Decision Records: Writing Down Why, for the Person Who Comes After You

Six months from now someone — 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. Four rules:

  1. One decision per record. Numbered 0001, 0002, … in docs/adr/.
  2. Immutable. You never edit an accepted ADR to reflect a new decision. You write a new ADR and mark the old one superseded. The trail is the point — the record of a decision you later reversed is often the most valuable document in the repository.
  3. Written at decision time, while the alternatives are still in your head. An ADR reconstructed in Week 15 is fiction with a date on it.
  4. Consequences include the negative ones. Always. This is the tell.

Use the provided code/adr-template.md. Now the part this book promises — the bad one and the good one, side by side.

The bad ADR

# ADR 1: Database

**Status:** Accepted

We will use MongoDB because it is flexible and scales well and is very popular
in industry. It will make development faster since we do not need migrations.

What is wrong with it — roughly what a design reviewer will say to you in Week 8:

  • No date, no author, no requirement identifiers. Nothing connects it to the project, so a reader cannot tell what problem was being solved or whether the decision still applies.
  • No alternatives. “Considered nothing” is what this says.
  • Unfalsifiable claims. “Scales well” — at student scale you will never approach any modern database’s limit, so that criterion cannot discriminate between options. “Popular in industry” is a résumé argument in a technical document.
  • Zero negative consequences, no revisit trigger. There is no such thing as a free choice; this one has real costs and none are named, and nothing would ever cause anyone to reopen it.

The good ADR

# ADR 0002 — Use a relational database for PantryPilot

- **Status:** Accepted
- **Date:** 2026-02-11
- **Decider:** M. Alvarez (sole developer)
- **Requirements affected:** FR-007, FR-011, NFR-P-02, NFR-M-01
- **Related ADRs:** 0003 (hosting)

## Context

PantryPilot stores households, members, pantry items, product records from the
barcode API, and recipes. Two requirements drive this decision. FR-007 ("a member
sees every item expiring within seven days, ordered by expiry date") and FR-011
("a member sees which recipes are fully covered by items on hand") are both joins
across three tables with a date filter, and both sit on the critical path of the
main screen. NFR-P-02 gives that screen a 500 ms budget at 1,000 items; NFR-M-01
requires a new maintainer to restore a backup in under 15 minutes from
`docs/runbook.md`.

The data is small — a five-person household holds a few hundred items — so volume
is not a driver. Relational shape is. I have shipped with SQLite in two prior
courses, used PostgreSQL in coursework but never deployed one, and never used
MongoDB.

## Options considered

| Option | Weighted score | The detail that decided it |
|---|---:|---|
| SQLite | 4.70 | Best score, but single-writer under the two web workers in ADR 0003 |
| PostgreSQL | 3.90 | Real joins, one managed instance, `pg_dump` restore I can document |
| MongoDB | 2.10 | FR-011 becomes a hand-written join in application code; novelty load +1 |

Full matrix and evidence: `docs/tech-evaluation.md`.

## Decision

Use PostgreSQL as the single data store, reached through one repository module.
No second store and no cache layer until a measurement says otherwise.

**I did not take the top-scored option.** SQLite scored highest because each
decision was scored in isolation, and in isolation it is the better fit. The seam
review (`docs/tech-evaluation.md`, "Seam inventory") surfaced what no single
matrix could: the host chosen in ADR 0003 runs two web workers, and two processes
writing one SQLite file is a concurrency problem I would have to solve myself, in
a semester where I budgeted no hours for it. Spike SP-01 confirmed the write
contention in 40 minutes. PostgreSQL costs me a running service in development;
SQLite would have cost me a bug I cannot schedule.

## Consequences

**Positive**

- FR-007 and FR-011 are one query each; the planner does the work.
- Backup/restore is `pg_dump` / `pg_restore` — two lines in `docs/runbook.md`, satisfying NFR-M-01.
- Migrations are explicit files the Week-13 change log can point at.

**Negative**

- Local development now needs a running service. Mitigation: a committed container definition plus a setup section in `README.md`. Budget 2 h, Week 6.
- CI now needs a database in the runner (seam CI ↔ tests). Budget 2 h, Week 9.
- Managed database tiers carry caps or idle-suspend policies that vary by vendor and change over time; verified 2026-02-09, re-verify before Week 14. If the demo must be live, budget a paid tier for that month.
- Schema changes now cost a migration file — a feature in Week 12, a nuisance in Week 9.

## Revisit trigger

Revisit if the main-screen query misses NFR-P-02 at 1,000 items, or if ADR 0003
(hosting) is superseded.

## Verification

| Claim | Source | Checked on |
|---|---|---|
| Managed database tier terms | vendor pricing page | 2026-02-09 |
| Two web workers on the chosen plan | vendor documentation | 2026-02-09 |

Read the two again and notice what separates them. It is not length. It is that the second can be argued with — a reviewer can attack the weights, dispute the spike, question whether NFR-P-02 is the right budget. The first offers nothing to grab. A decision record that cannot be argued with has not recorded a decision.

Coach’s Note — The “I did not take the top-scored option” paragraph is the most senior thing in that document. Juniors follow the spreadsheet or ignore it. Seniors use it, then override it in the open, with the reason written down where the next person can check their work. Do that once this semester and you will have learned something that outlasts every framework in your stack.


5.11 — Interactive Lab: The Stack Compatibility Checker

Below this chapter on the website is The Stack Compatibility Checker. Use it before you write a single ADR. Assemble a candidate stack from the dropdowns — language and runtime, web framework, data store, auth, hosting, CI, and optionally an AI provider — and as you choose, the widget does five things:

  • Scores integration friction between the pieces, so the seams light up now rather than in Week 12.
  • Flags well-known incompatibilities and license conflicts in the combination.
  • Estimates monthly cost at student scale — order of magnitude, not a quote. Treat every number it gives you as a hypothesis to verify against the vendor, exactly like a number from an assistant.
  • Computes your novelty load from the pieces you mark as new, and warns you when it crosses the 5.4 threshold where capstones start dying.
  • Generates a spike plan — the smallest experiment that would de-risk each unknown it found.

Do it deliberately: build the stack you want first and read the friction score, then build the most boring stack that still satisfies your architectural drivers, and compare. The gap between those two numbers is the price of your preferences, quantified. Sometimes it is small and you should absolutely pay it; sometimes it is enormous, and you have just been handed a Week-12 crisis you get to skip. Then take the generated spike plans into code/spike-plan-template.md, sharpen each until it has a failure criterion, and commit them. That hand-off — from the widget to a real file with a time box and a Plan B — is the lab.


5.12 — Why Does Wisdom Test a Thing Before Trusting It?

“but test everything; hold fast what is good.” — 1 Thessalonians 5:21 (ESV)

Paul is closing a letter to a young church with a rapid burst of instructions, and this one lands almost casually in the middle of them. It is worth slowing down on, because it refuses both of the easy postures.

The first is credulity. Believe the documentation. Believe the benchmark on the landing page. Believe the model that answered instantly and in complete sentences. Credulity feels generous and humble — who am I to doubt? — but it is neither; it is outsourcing. Proverbs is blunt: “The simple believes everything, but the prudent gives thought to his steps” (Proverbs 14:15, ESV). The simple person is not evil. He is just going to be wrong, publicly, at the worst possible time — which for you is Week 14, on somebody else’s machine.

The second is cynicism. Trust nothing, build it all yourself, assume every vendor is lying and every abstraction leaks. This one wears the costume of rigor, but notice that it also refuses to do the work. The cynic and the credulous man arrive at the same place from opposite directions: neither actually tested anything. One accepted without evidence; the other rejected without evidence.

The verse cuts between them, and the grammar is the whole lesson. Test everything — that is the labor, and it is not optional. Hold fast what is good — that is the point of the labor. Testing is not the destination; it exists so you can commit, firmly and without anxiety, to the thing that survived. You cannot hold fast to what is good if you never sorted it from what was merely loud. This is why the discipline in 5.9 is not paranoia: verifying a price against the vendor’s page is not distrust of the assistant, it is what makes it safe to use one at all. Testing is the precondition of trust, not its opposite. Luke commends the Bereans for exactly this posture — receiving the word eagerly while “examining the Scriptures daily to see if these things were so” (Acts 17:11, ESV). Eagerness and examination in the same sentence. They were not suspicious people. They were serious people.

Two more things, and then back to work. Testing has a moral shape when you are the one being tested. It is easy to examine a vendor; it is much harder to examine your own preference honestly — to weight the criteria before you score, and then actually let your favourite lose. A rigged matrix is a lie told with arithmetic. Nobody will catch it. You will know. And testing is an act of love toward whoever comes after you. You hand this repository off in Week 14 to somebody who was not in the room when you decided any of this, and every untested assumption you leave becomes a trap with their name on it. The ADR you write this week — real negative consequences, honest revisit trigger — is a small act of care for a stranger. That is not a stretch of the text; it is what this work looks like done as a neighbor rather than as a performer.

Test everything. Then hold fast. Then write down why.


5.13 — Where Your Hours Went This Week

Roughly fifteen hours, in a week where the writing is most of the work:

HoursWhat
2.0Re-reading your requirements as an architect; extracting the four-to-eight architectural drivers
3.0Building the option space (assistant-assisted) and verifying every claim against vendor sources
2.5The Compatibility Checker lab, then weighting, scoring, score-stack.py, and the sensitivity pass
3.5Two time-boxed spikes on your highest-risk seams, written up
2.5Writing four ADRs
1.5Cost sheet, free-tier watch list, license inventory, hours and verification logs, weekly quiz

If your verification hours are near zero, you did not verify — you skimmed. That line predicts how Week 12 goes.


5.14 — Common Pitfalls

Pitfall: Choosing the stack before writing the requirements, then reverse-engineering the justification. Example: “I’m doing this in Rust with a graph database” decided in Week 1; the Week-5 matrix conveniently ranks Rust and the graph database first. Fix: Do the drivers pass in 5.1 from docs/requirements.md first, on paper. Set weights in a separate sitting from scoring. If your preference wins honestly, wonderful — the matrix now defends it in Week 8’s design review.


Pitfall: Evaluating each component in isolation and never looking at the seams. Example: Three green matrices for language, database, and host — and a Week 10 discovery that the host runs two workers and the chosen database is single-writer. Fix: Build the seam inventory in 5.3 across all your decisions, mark every seam you have never crossed, and spike the High ones now. Score components separately; read seams together.


Pitfall: A novelty load of four, defended as “a learning opportunity.” Example: New language, new framework, new database, new deployment platform, one developer, fifteen weeks. Fix: Spend one innovation token. Demote the rest to things you have shipped with. Write an ADR naming the one new thing and why it earned the token — that ADR is a better interview story than four half-learned tools.


Pitfall: Trusting an assistant’s versions, prices, limits, or license terms without checking. Example: A tradeoff table listing a “free tier” that ended two years ago, and a library described as MIT that was relicensed. Fix: Every time-varying claim gets a vendor source and a date, in a verification log. Log the assistant’s misses in docs/ai-usage.md too — build your own hit rate.


Pitfall: An ADR with no negative consequences — or no ADR at all, because “I’ll remember.” Example: “Consequences: faster development, better scalability, easier maintenance.” Three benefits, no costs. Or: Week 15, writing the handoff guide, with no explanation for why there are two ways to authenticate in the codebase. Fix: Every real decision costs something — name what got harder, the hours you budgeted for the mitigation, and the revisit trigger. If you cannot find a cost, you did not evaluate, you shopped. And write ADRs at decision time: four this week, dated and numbered in docs/adr/. Reconstruction in Week 15 is fiction, and a reader can smell it.


Pitfall: Building an AI feature into the critical path with no fallback. Example: The main screen calls the model on every load; during the Week-16 presentation the key rate-limits and the app shows an error. Fix: One module owns the model call. The non-AI path is the default in the walking skeleton. Define the timeout, the fallback, and the user-visible behaviour in the specification in Chapter 6. The AI feature must be removable.


5.15 — Reps

The reps are in the exercises. Not warm-ups this week — they are the milestone, built piece by piece. Preview:

  • Rep 1 — extract the architectural drivers from your own requirements, and defend the count.
  • Rep 2 — the job-board test: write down what you want to use and separate the résumé motive from the fit.
  • Rep 4 — weight your criteria, in a separate sitting from scoring, and commit the file before you score.
  • Rep 8 — count your novelty load honestly and decide where your one token goes.
  • Rep 9 — run a real 90-minute spike on your highest-risk seam, failure criterion written first.
  • Rep 12 — take five claims an assistant gave you, verify each against the vendor, and record your hit rate. Do the on-page Check Your Reps quiz when you finish the chapter — it is the ungraded rehearsal for Week 5 Quiz in Canvas, which is the graded one. Its real job is to tell you — this week, not in Week 14 — whether you are on pace.

5.16 — This Week’s Milestone

Milestone 5Milestone 5: Technology Evaluation & Architecture Decision Records. You will produce a docs/tech-evaluation.md with at least three real decisions evaluated against weighted, requirement-derived criteria; at least four ADRs in docs/adr/; a seam inventory with a spike for every high-risk seam; an honest novelty-load count; a cost sheet with a free-tier watch list; a license inventory with SPDX identifiers; and a verification log proving you checked the facts yourself.

Remember the grading contract: the milestones are graded twice, and that is not an accident. They carry 25% on their own, and these documents are the Week-16 deliverable, produced one week at a time, so the final rubric awards points for exactly these artifacts again. Skipping this week costs you the milestone now and the same points later, in a week with no hours left in it.


5.17 — Coach’s Final Word

Four weeks ago you had an idea. Three weeks ago it became requirements. This week it becomes a shape — a set of technologies with seams between them, and a written record of why each one is there.

The temptation this week is glamour. Every one of you has a technology you have been waiting for an excuse to use. I am not telling you to abandon it. I am telling you to make it earn its place against your own requirements, in a table you weighted before you scored, and then to write down honestly what it cost you. If it wins, use it and enjoy it. If it loses, let it lose. The engineer who can say “I wanted to use X, and here is the evidence that Y was the right call for this project” is the engineer people trust with decisions. Be boring on purpose everywhere else. Boring is not a lack of ambition — boring is what you spend so you can afford one interesting thing and still finish. Every hour you do not spend fighting an unfamiliar seam is an hour on the product, on tests, on the documentation that carries most of your grade.

Test everything. Hold fast to what is good. Write down why, for the person who comes after you — who, for the next eleven weeks, is you.

See you on Monday.


Up next: the exercises builds the evaluation rep by rep · Milestone 5 is Milestone 5 · then Chapter 6, where the stack you just chose becomes a buildable design. Reference: Appendix A (workbench and hosting paths), Appendix B (templates and worked examples, including the ADR), Appendix C (the grading contract), Appendix E (glossary). Previous: Chapter 4.

Interactive Lab — Week 5
The Stack Compatibility Checker

Pick one option per layer, then tick New to me for every layer you have never shipped. The checker scores the seams between your choices, counts your novelty load, reads your license against how you plan to ship, and writes the smallest experiment that would de-risk each unknown. Change one dropdown and watch the verdict move.

 

Integration friction0
Novelty load0
Cost pressure (heuristic)0

Licensing: what you are allowed to ship
    Seams to watch
      Spike plan — the smallest experiment per unknown

        Every number here is a planning heuristic, not a quote or a measurement. Prices move, tiers expire, and projects get relicensed — verify each claim against the vendor's own page and the actual license text, then record what you found (and the option you rejected) in ADR-001.

        Check Your Reps

        Week 5 Knowledge Check

        Question 1 of 5
        You commit your evaluation matrix on Sunday night like this, and score-stack.py exits 0 with every evidence cell filled. Which Milestone 5 rubric line have you nonetheless lost?
        $ git log --oneline -- docs/tech-evaluation.csv
        a1c9f02  docs: technology evaluation matrix (weights + scores)
        Why: Weights before scores is the highest-leverage rule in §5.2, and the separate commit is the only evidence that you followed it. Once you have seen the scores you will unconsciously tune the weights until your favourite wins — nobody will ever catch you, which is exactly why the discipline is worth having. Rep 4 puts it plainly: commit the weights with no scores at all, then score in a separate sitting, and show that history in the Week 8 design review.
        Question 2 of 5
        Your seam inventory contains this row. What does Chapter 5 tell you to do about it, and when?
        | Seam              | What has to work                       | Crossed before? | Risk | Spike |
        | App ↔ AI provider | SDK version, timeout, streaming, cost  | No              | High | ---   |
        Why: §5.3, rule three: every High seam gets a spike this week or next, not in Week 12. An unfamiliar library alone is a study problem — bounded, and the docs will get you there. An unfamiliar seam is an integration problem, and integration problems have no docs, because they are specific to your combination. Write the failure criterion before you run it ("does the model return a parseable object at least 18 times out of 20, and what is the p95 latency?"), or you will negotiate with yourself afterward — and honour the 90-minute timer, especially when you were almost there.
        Question 3 of 5
        You count four pieces of your stack you have never shipped with — built, deployed, and debugged at 11pm. What does the novelty-load table in §5.4 prescribe?
        Why: A novelty load of 4 or more is "how capstones die." Learning curves compound rather than add: with one new thing, the unfamiliar piece is the suspect and you go read its docs; with three or four, every debugging session starts with an hour of establishing which layer is even lying to you — and that cost arrives in the weeks with the least slack. You get one innovation token. Spend it on the piece closest to what makes your project interesting, name it in an ADR, and be relentlessly boring everywhere else.
        Question 4 of 5
        An architecture decision record's Consequences section reads, in full, as below. What will a Week 8 design reviewer say?
        ## Consequences
        
        Faster development, better scalability, easier maintenance.
        Why: §5.10, rule four: consequences include the negative ones, always — "this is the tell." Every real decision costs something, so name what got harder, the hours you budgeted for the mitigation, and a revisit trigger with a number in it ("revisit if the main-screen query misses NFR-P-02 at 1,000 items"). If you cannot find a cost, you did not evaluate — you shopped. And write it at decision time: an ADR reconstructed in Week 15 is fiction with a date on it, and a reader can smell it.
        Question 5 of 5
        An assistant hands you a tradeoff table listing a hosting provider's free tier, a library's license as MIT, and a compatibility claim about your ORM and your database version. What does Chapter 5's strictest rule require before any of it enters your Milestone 5 documents?
        Why: That is the rule from §5.9, and the chapter calls this the riskiest week in the course to trust an assistant — versions, prices, free-tier limits, license terms, rate limits, and compatibility claims are exactly the facts it states with beautiful, ruinous confidence. The compatibility claim is the worst of the three, because it lives on your seam. Keep a verification log with one row per claim, log the misses in docs/ai-usage.md too, and you will finish the semester with your own empirical hit rate. The spine rule does not move: the assistant expands the option space; you verify the facts; you own the decision.
        YOU FINISHED. NICE WORK.