Day 28 — Load Testing, Security & Capstone Design

Updated

July 30, 2025

Day 28 — Load Testing, Security & Capstone Design

Day 80 — Load test

Stage VIII · ~3h
Goal: Run a controlled load test (vegeta, k6, or hey) against your service, record latency/throughput/error rate, correlate with metrics/pprof, and name one bottleneck with evidence. Go 1.26 service recommended.

Why this day exists

Unit benchmarks (Day 66) miss:

  • Real HTTP stack
  • Pool exhaustion
  • Lock contention under concurrency
  • GC under multi-core load
  • Proxy/middleware overhead

Load tests validate Stage VII observability investments (metrics, pprof) and feed Day 81–89 hardening. Capstone demos that never saw concurrent clients are fiction.

Important

Only attack local or systems you own. Do not load-test third-party production APIs. Cap duration; watch machine temperature/throttling.


Theory 1 — What to measure

Metric Why
Throughput (RPS / RPS achieved) Capacity
Latency p50 / p95 / p99 User experience
Error rate Correctness under load
Saturation CPU, memory, goroutines, DB conns
Tail behavior Cliffs when queues form

Stop conditions

Stop or reduce rate when:

  • Error budget exceeded (e.g. >1% non-2xx)
  • Latency SLO miss (define a lab SLO, even if informal)
  • Resource limit (CPU pegged, OOM, DB too many connections)
  • Machine thermal throttle (note it—laptop science is noisy)

Theory 2 — Tools (pick one)

vegeta (simple HTTP, constant rate)

go install github.com/tsenart/vegeta@latest
echo "GET http://127.0.0.1:8080/v1/hello" \
  | vegeta attack -duration=30s -rate=100 \
  | vegeta report

echo "GET http://127.0.0.1:8080/v1/hello" \
  | vegeta attack -duration=30s -rate=100 \
  | vegeta report -type=json > load.json

Targets file for headers/auth:

GET http://127.0.0.1:8080/v1/items/1
Authorization: Bearer <token>
vegeta attack -targets=targets.txt -duration=30s -rate=50 | vegeta report

hey

go install github.com/rakyll/hey@latest
hey -z 30s -c 50 http://127.0.0.1:8080/v1/hello

Good for quick concurrency knobs (-c workers). Compare carefully to vegeta’s constant rate.

k6 (scripted scenarios)

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = { vus: 20, duration: '30s' };

export default function () {
  const res = http.get('http://127.0.0.1:8080/v1/hello');
  check(res, { 'status 200': (r) => r.status === 200 });
  sleep(0.1);
}
k6 run load.js

Any one tool is enough. Prefer constant rate then step up.


Theory 3 — Experiment design

  1. Establish baseline at low RPS (or low VUs).
  2. Increase rate until p99 or errors break.
  3. Hold steady; capture pprof + /metrics.
  4. Change one variable (pool size, GOMAXPROCS, query, cache flag).
  5. Re-measure; write the story.
# terminal 1
./service

# terminal 2 — during attack
curl -o cpu.pb.gz "http://127.0.0.1:6060/debug/pprof/cpu?seconds=20"
curl -s localhost:8080/metrics > metrics-under-load.txt

Ambient conditions (record them)

Factor Your note
Bare metal vs Docker
DB local vs remote
Laptop power / thermal
GOMAXPROCS
Other heavy processes

Without this, day-over-day comparisons lie.


Theory 4 — Common bottlenecks in Go services

Symptom Suspect
High CPU in json / encoding Payload size; buffer reuse
Goroutines climb without bound Leak or blocking without timeout
DB too many connections SetMaxOpenConns; pool vs load
Lock contention in pprof mutex profile Shared map without sharding
GC CPU high Allocation rate (Days 66–67)
Low CPU, high latency External dependency / lock / sleep
Errors only at high RPS Queue overflow, timeouts, pool starvation
Fine on /healthz, dies on /v1/work Health not representative—good

DB pool knobs (reminder)

db.SetMaxOpenConns(10)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(30 * time.Minute)

Change one knob per experiment.


Theory 5 — Choosing the target endpoint

Endpoint Use?
/healthz Smoke only—not primary load target
Authenticated read Good baseline
Authenticated write Stronger; watch disk/DB
Cheap CPU burn /v1/work Isolates app CPU
Cacheable GET Watch hit-rate interaction (Day 58)

Prefer a realistic path your users hit. Document auth setup in LOAD.md.


Worked example — vegeta rate matrix

for r in 50 100 200 400; do
  echo "=== rate $r ==="
  echo "GET http://127.0.0.1:8080/v1/work" \
    | vegeta attack -duration=20s -rate=$r \
    | vegeta report \
    | tee "load-rate-$r.txt"
done

LOAD.md table:

Rate Success % p50 p95 p99 Notes
50 baseline
100
200 cliff?
400

Worked example — step load + metrics correlation

curl -s localhost:8080/metrics > /tmp/m0.txt

echo "GET http://127.0.0.1:8080/v1/work" \
  | vegeta attack -duration=15s -rate=50 \
  | vegeta report | tee /tmp/r50.txt
curl -s localhost:8080/metrics > /tmp/m50.txt

echo "GET http://127.0.0.1:8080/v1/work" \
  | vegeta attack -duration=15s -rate=200 \
  | vegeta report | tee /tmp/r200.txt
curl -s localhost:8080/metrics > /tmp/m200.txt

grep demo_request /tmp/m50.txt /tmp/m200.txt || true

While rate=200 runs (separate terminal):

curl -o /tmp/cpu-load.pb.gz "http://127.0.0.1:6060/debug/pprof/cpu?seconds=15"
go tool pprof -top /tmp/cpu-load.pb.gz | head -20

Story template (required prose)

At 50 RPS p99 was 20ms with 0% errors.
At 200 RPS p99 jumped to 180ms and error rate 2%.
pprof showed time in encoding/json.Marshal and database/sql.(*DB).conn.
Metrics: demo_request_errors_total +N; db pool wait (if exposed).
Hypothesis: MaxOpenConns=2 saturates under concurrency.
Next experiment: raise MaxOpenConns from 2 to 10 and retest once.
Result after change: ...

Theory 6 — Mitigations worth one experiment

Lever How
DB pool SetMaxOpenConns
Timeouts Server/client deadlines reduce pileups
Caching Cache-aside on hot GET (if correct)
Payload Smaller JSON; avoid chatty fields
GOMAXPROCS Usually leave default; note if container CPU limit
Reduce allocs Only after pprof says so

One mitigation + retest beats five unmeasured “optimizations.”


Lab 1 — Tooling and smoke

Suggested workspace: instrumented service from Days 69–78 (or Stage VI gate service).

  1. Pick vegeta or hey or k6; install; record version.
  2. Start service; confirm one manual curl succeeds.
  3. 5s smoke attack at low rate.
echo "GET http://127.0.0.1:8080/healthz" \
  | vegeta attack -duration=5s -rate=10 \
  | vegeta report

Lab 2 — Rate matrix on a real endpoint

  1. Target a non-health route.
  2. Run ≥3 rates × ≥15–20s each.
  3. Save reports under load/ or paste into LOAD.md.
  4. Mark the cliff rate if any.

Lab 3 — Correlate observability

  1. Under the highest “interesting” rate, capture CPU pprof or mutex profile.
  2. Snapshot /metrics before/after.
  3. Optional: curl /debug/pprof/goroutine?debug=1 | head for leak signals.
curl -s "http://127.0.0.1:6060/debug/pprof/goroutine?debug=1" | head -40

Ensure pprof is localhost-bound (Day 65/81)—do not expose it to the world for this lab.


Lab 4 — One mitigation experiment

  1. Form a hypothesis from pprof/metrics.
  2. Change one variable.
  3. Re-run the cliff rate.
  4. Record before/after in LOAD.md.
  5. If no improvement, that is still a valid result—document it.

Lab 5 — Write LOAD.md evidence pack

# Load test — day80

Date:
Service:
Go: go1.26.x
Tool: vegeta|hey|k6 version:
Target URL:
Auth: none|bearer ...

## Environment
- Host:
- DB:
- GOMAXPROCS:

## Results table
| Rate | Success % | p95 | p99 | Notes |
|------|-----------|-----|-----|-------|

## Profiles / metrics paths
- 

## Bottleneck (one sentence)
- 

## Experiment
- Change:
- Result:

Hour-by-hour suggestion

Time Focus
0:00–0:20 Tool install + smoke one request
0:20–1:20 Rate matrix + reports
1:20–2:10 pprof/metrics under load
2:10–2:40 One mitigation experiment
2:40–3:00 LOAD.md write-up

Common gotchas

Gotcha Fix
Load tester on same starved laptop Note limits; lower rate
Keep-alive differences across tools Compare tools fairly; stick to one
Warm vs cold caches Run twice; discard first
Hitting readiness during deploy Steady-state only
Optimizing without profile Capture pprof first
/healthz only Not representative
Attacking shared staging Only local/owned systems
Auth failures counted as “app slow” Fix tokens first
Forgetting to pin duration Always set -duration / -z
pprof on public interface Bind 127.0.0.1:6060

Checkpoint

  • Tool installed and command recorded
  • ≥1 sustained attack (≥20–30s) on non-health route
  • Report numbers saved
  • Metrics or pprof under load
  • Bottleneck named in LOAD.md
  • Optional mitigation + retest documented
  • Safety: only local/owned target

Commit

git add .
git commit -m "day80: load test bottleneck evidence"

Write three personal gotchas before continuing.


Tomorrow

Day 81 — Security checklist: a structured pass over auth, input, deps, and runtime exposure before design-doc day—load evidence informs which timeouts and limits matter most.


Day 81 — Security checklist

Stage VIII · ~3h
Goal: Run a structured security pass over your Go service/CLI—fix or ticket the top issues—and leave a checklist artifact suitable for the capstone evidence pack. Baseline Go 1.26.

Why this day exists

govulncheck (Day 72) is necessary but not sufficient. Most real incidents involve:

  • Broken authZ (IDOR)
  • Injection (SQL, command)
  • Secret leakage
  • Overexposed debug endpoints
  • Insecure defaults left from “dev mode”

This is a checklist day, not a crypto course. Capstone Day 89 will re-run the high-signal items. Day 80 load results should inform which timeouts and limits you tighten today.


Theory 1 — Threat-oriented checklist for this volume

Work top to bottom. Mark each item PASS / GAP / N/A with a one-line note.

A. Transport & network

  • TLS terminated correctly in real deploy (awareness from Day 60)
  • No plain secrets on public interfaces
  • pprof not public (Day 65)—localhost or build-tag only
  • /metrics network-restricted or acceptable risk documented
  • Timeouts on server and clients (ReadHeaderTimeout, outbound ctx)
srv := &http.Server{
    Addr:              cfg.Addr,
    Handler:           mux,
    ReadHeaderTimeout: 5 * time.Second,
    ReadTimeout:       15 * time.Second,
    WriteTimeout:      30 * time.Second,
    IdleTimeout:       60 * time.Second,
}

Outbound:

client := &http.Client{Timeout: 10 * time.Second}
// or per-request context.WithTimeout

B. Authentication & authorization

  • Authn present on mutating/sensitive routes
  • Authz checks per resource (IDOR awareness)
  • Password hashing uses bcrypt/scrypt/argon2—not homemade
  • Tokens validated (exp, signature, audience) if JWT used
  • Admin routes separate and locked down
  • “Dev auth bypass” defaults to off
// IDOR sketch: always scope by principal
item, err := repo.GetForUser(ctx, userID, itemID)
if errors.Is(err, domain.ErrNotFound) {
    // 404 even if exists for someone else — avoid oracle if policy requires
}

C. Input & data

  • SQL via placeholders only ($1 / ?)—never string concat
  • os/exec with fixed binaries + CommandContext; no sh -c + user string
  • Request body size limits
  • Strict content-type for JSON APIs (at least on mutating routes)
  • Path traversal checked if serving files
  • Upload / batch limits (Day 56 batch max)
r.Body = http.MaxBytesReader(w, r.Body, 1<<20) // 1 MiB
if ct := r.Header.Get("Content-Type"); !strings.HasPrefix(ct, "application/json") {
    // 415 or 400 per your policy
}

D. Secrets & config

  • No secrets in git history (scan quickly)
  • .env gitignored; .env.example only
  • Logs redact DSNs/tokens
  • CI secrets via platform secret store
  • JWT/signing keys from env, min entropy for lab

E. Dependencies & supply chain

  • govulncheck ./... clean or accepted with expiry
  • Minimal module graph (no abandoned critical deps)
  • replace directives reviewed
  • Toolchain is 1.26.x (stdlib patches)

F. Container & runtime

  • Non-root user
  • Read-only root FS where possible
  • Dropped capabilities (K8s securityContext awareness)
  • GOMEMLIMIT aligned with memory limit
  • No privileged docker flags in compose notes

G. Abuse & reliability as security

  • Timeouts everywhere (connection pileup is an availability attack)
  • Rate limit or at least document absence on login (stretch)
  • Panic recovery in HTTP boundary without swallowing auth bugs
  • Graceful shutdown (Day 78) reduces half-open weirdness
  • Load-test cliff notes linked from Day 80

Theory 2 — SQL injection quick proof

Bad:

q := "SELECT * FROM users WHERE name = '" + name + "'"
row := db.QueryRow(q) // never

Good:

row := db.QueryRowContext(ctx, `SELECT id, name FROM users WHERE name = ?`, name)

If you find concatenation—fix today. Grep assist:

rg -n 'Query(Context)?\([^)]*\+|Exec(Context)?\([^)]*\+' --glob '*.go' || true
rg -n 'fmt\.Sprintf\(`.*(SELECT|INSERT|UPDATE|DELETE)' --glob '*.go' || true

Theory 3 — Command injection quick proof

Bad:

exec.Command("sh", "-c", "grep "+userInput+" /var/log/app.log")

Good:

cmd := exec.CommandContext(ctx, "/usr/bin/grep", "--", userInput, "/var/log/app.log")
cmd.Path = "/usr/bin/grep" // fixed binary

Prefer pure Go implementations over shelling out. If you must shell out, fixed argv only—no string-built scripts.


Theory 4 — Security headers & CORS (light)

For browser-facing responses:

w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Frame-Options", "DENY")
// CSP when you have a real frontend story

APIs still benefit from careful CORS—default deny. Never combine Access-Control-Allow-Origin: * with credentialed requests.

// explicit allowlist sketch
if origin := r.Header.Get("Origin"); allowed[origin] {
    w.Header().Set("Access-Control-Allow-Origin", origin)
    w.Header().Set("Vary", "Origin")
}

Theory 5 — Debug endpoint exposure matrix

Endpoint Safe default
/debug/pprof/* 127.0.0.1 only or build tag debug
/metrics private network; no secrets in labels
/healthz OK public (no sensitive data)
/readyz OK public; do not leak DSNs in body
Error JSON No stack traces to clients
// separate mux/server for pprof
go http.ListenAndServe("127.0.0.1:6060", nil) // pprof registered on DefaultServeMux in import side effect — prefer dedicated mux

Prefer a dedicated debug server mux over hanging pprof on the public :8080 handler.


Theory 6 — Secret hygiene workflow

# triage carefully — expect false positives in tests/docs
rg -i 'api[_-]?key|secret|password\s*=|BEGIN (RSA |OPENSSH )?PRIVATE' \
  --glob '!**/*_test.go' \
  --glob '!**/testdata/**' || true
Finding Action
Real secret in tree Rotate; remove; rewrite git history if pushed
Example value in docs Use obviously fake (sk_test_xxx)
Test password Fine if non-production

.gitignore must include .env, *.pem, credentials.json patterns you actually use.


Worked example — SECURITY-CHECKLIST.md

# Security checklist — day81

Target: myservice @ <git sha>
Date:
Go: go1.26.x

## Scores
| Area | Status | Notes |
|------|--------|-------|
| Timeouts | PASS | ReadHeaderTimeout set |
| Authn | PASS | Bearer on /v1/* |
| Authz | GAP | list endpoint returns all users — ticket |
| SQL | PASS | placeholders only |
| Body limits | PASS | MaxBytesReader 1MiB |
| pprof | PASS | localhost only |
| metrics | PASS | private net note |
| govulncheck | PASS | clean (see SECURITY-SCAN.md) |
| Secrets | PASS | no .env committed |
| Container | PASS | nonroot distroless |
| Load cliffs | NOTE | see LOAD.md — pool at 200 RPS |

## Top fixes completed today
1. MaxBytesReader on JSON routes
2. ReadHeaderTimeout on http.Server

## Accepted risks
1. /metrics on same port — private network only — review: YYYY-MM-DD

## Tickets / follow-ups for capstone
1. IDOR on list endpoint

Lab 1 — Walk the full checklist

Suggested workspace: primary service/CLI.

  1. Copy the Theory 1 sections into SECURITY-CHECKLIST.md.
  2. Mark PASS / GAP / N/A with notes.
  3. Link Day 72 SECURITY-SCAN.md and Day 80 LOAD.md.
  4. Do not skip N/A without a reason (CLI vs service).

Lab 2 — Fix at least two real gaps

Pick high-signal fixes:

Priority Example fix
P0 SQL concat → placeholders
P0 pprof public → localhost
P1 Missing ReadHeaderTimeout
P1 Missing MaxBytesReader
P1 Auth bypass default on
P2 Security headers on browser routes
P2 Redact Authorization in logs
go test ./... -count=1
govulncheck ./...

Lab 3 — Re-scan supply chain + secret grep

  1. Re-run govulncheck ./....
  2. Run secret grep; triage hits.
  3. Confirm .env ignored: git check-ignore -v .env || true.
  4. Update checklist with results.

Lab 4 — AuthZ spot check (services)

Write one test or manual script:

# user A token creates item
# user B token GET/PUT that item → 403 or 404

If you only have a single-user lab, document IDOR as residual risk for multi-tenant future.


Lab 5 — Capstone handoff bullets

In checklist footer:

## Capstone (Days 82–89) must preserve
- Timeouts on http.Server
- Authn on mutating routes
- Placeholder SQL only
- pprof not public
- govulncheck in lint script

Day 89 harden day will re-open this file—make it honest.


Hour-by-hour suggestion

Time Focus
0:00–0:40 Full checklist pass
0:40–1:40 Two (or more) concrete fixes
1:40–2:10 govulncheck + secret grep
2:10–2:40 AuthZ spot check / residual risks
2:40–3:00 SECURITY-CHECKLIST.md polish

Common gotchas

Gotcha Fix
Checklist theater without fixes Minimum two concrete fixes
Disabling auth in “dev” left on Config flag default secure
CORS * with credentials Never
Trusting client-side role claims Server-side authz
Logging Authorization headers Redact middleware
500 bodies with err.Error() Generic client message
“N/A because CLI” skipping file perms Still check exec and secrets
Accepting metrics exposure forever Review date

Checkpoint

  • SECURITY-CHECKLIST.md complete
  • ≥2 fixes landed and tested
  • govulncheck re-run
  • pprof/metrics exposure stated
  • Secret grep triaged
  • Known gaps listed for capstone
  • Capstone handoff bullets written

Commit

git add .
git commit -m "day81: security checklist and fixes"

Write three personal gotchas before continuing.


Tomorrow

Day 82 — Capstone design doc: pick option A/B/C and write the plan before code sprawl. Bring security non-negotiables into the design (“auth on mutating routes,” “no public pprof”).


Day 82 — Capstone design doc

Stage VIII · ~3h
Goal: Choose one capstone option (A agent / B microservice / C CLI), write a design doc with scope, architecture, milestones (Days 83–88), risks, and demo criteria—before building.

Important

Pick exactly one option. Syllabus rule: avoid A+B+C. Depth beats breadth.

Why this day exists

Days 83–88 are six build days. Without a design doc you will:

  • Start three products
  • Skip tests until Day 89 panic
  • Demo vapor

Writing forces trade-offs while Stage I–VII skills are still loadable into one system. Design day is not optional ceremony—it is the cheapest bug you will prevent.


Theory 1 — Capstone options

Option A — Control / observability agent

Session-2 flavored. A long-running agent that:

  • Watches processes, containers, or host metrics
  • Exposes a small control API (HTTP)
  • Emits slog + Prometheus metrics
  • Runs containerized
  • Optional: light actions (restart policy stub, label-based filters)—keep safe

Success looks like: agent runs, scrapes metrics, API authenticates, container ships, demo scripted.

Risks: host permissions, docker socket exposure, accidental dangerous actions. Prefer read-mostly + dry-run.

Option B — Domain microservice

Deepen/rebuild a coherent domain service:

  • REST (optional gRPC)
  • DB + migrations
  • Auth
  • Tests (unit + integration)
  • Metrics + graceful shutdown
  • Container

Success looks like: end-to-end user story on API, data persists, Stage VII quality retained.

Risks: domain sprawl (CRM, payments, multi-tenant). Keep one noun graph small.

Option C — Serious CLI product

Multi-command tool with:

  • Polished help/exit codes
  • Tests for core logic
  • Release artifacts (multi-OS/arch)
  • Optional: config files, plugins-lite, Nix packaging note
  • Optional: works as kubectl-style plugin

Success looks like: demo.sh walks main flows; binaries installable; README excellent.

Risks: endless flag surface. Prefer three commands done well.


Theory 2 — Design doc template

Create CAPSTONE.md (or docs/capstone.md):

# Capstone design

## 1. Choice
Option: A | B | C
One-sentence pitch:

## 2. Problem & user
Who uses it?
What job does it do in 60 seconds?

## 3. Non-goals
- ...
- ...

## 4. Architecture
### Packages
### Diagram (ASCII)
### Data stores / external systems

## 5. Public surface
### API routes or CLI commands
### Auth model
### Config surface (env/flags)

## 6. Quality bar
- [ ] go test ./...
- [ ] vet + staticcheck
- [ ] govulncheck
- [ ] metrics / profiles (as applicable)
- [ ] container or release binaries
- [ ] graceful shutdown (services)

## 7. Milestones (map to days)
| Day | Goal | Done when |
|-----|------|-----------|
| 83 | Skeleton + modules + CI script | ... |
| 84 | Core domain path | ... |
| 85 | Persistence / adapters | ... |
| 86 | API or CLI UX complete | ... |
| 87 | Observability + hardening hooks | ... |
| 88 | Integration polish + demo script | ... |
| 89 | Evidence pack | ... |
| 90 | Retro | ... |

## 8. Risks
| Risk | Mitigation |
|------|------------|
| Scope creep | Non-goals list |
| New dependency rabbit hole | Prefer stdlib |

## 9. Demo script outline
1.
2.
3.

## 10. Open questions
-

ASCII architecture examples

Option B:

[cli/main] → [adapter/http] → [app/service] → [port interfaces]
                                  ↓
                            [adapter/sqlite]

Option A:

[loop ticker] → [collector] → [store snapshot]
       ↓
[http control API] ← auth → [actions (optional dry-run)]
       ↓
[/metrics]

Option C:

main → cli router → domain packages
                 → config file
                 → testdata fixtures

Theory 3 — Scope budgeting

Six build days ≈ 18 focused hours. Budget:

Bucket Share
Vertical slice happy path 40%
Tests 20%
Operability (metrics, config, shutdown) 20%
Polish / demo / docs 20%

If a feature is not on the demo script, it is a non-goal.

Vertical slice definition

A vertical slice is one user job that touches the real stack:

Option Example slice
A Snapshot once + serve /metrics + /healthz
B Create resource + persist + GET by id
C init + apply --dry-run + status

Horizontal “all packages empty” is not a slice.


Theory 4 — Reuse vs rewrite

Prefer reuse of Stage VI–VII service as option B core. Option A/C may start fresh modules but steal packages (config, flags, http middleware).

# good
capstone/ reuses internal/auth patterns from earlier labs

# bad
greenfield everything including custom JSON stack

Inventory in the design doc:

## Reuse inventory
- config.Load pattern from day77
- middleware from day54
- Dockerfile from day68

Theory 5 — Demo criteria are acceptance tests

Write demo steps that a stranger could run:

## Demo (must work Day 90)
1. `go test ./...`
2. `./demo.sh` exits 0
3. Shows: create book, loan, return (B example)
4. `curl /metrics` has http_requests
5. SIGTERM drains without error spam

If a step needs a cloud account, redesign for local Docker/SQLite.


Worked example — Option B pitch (sample)

## Choice
Option B — “Shelf” small library loan API

Pitch: Track books and loans for a single library branch via REST + SQLite.

Non-goals: payments, multi-tenant SaaS, mobile apps, gRPC.

Commands demo:
1. migrate + run
2. create book, create member, loan, return
3. metrics scrape, kill -TERM drain

Worked example — Option A pitch (sample)

## Choice
Option A — “Pulse” process snapshot agent

Pitch: Periodically samples local process list + exposes Prometheus metrics and a read-only HTTP API.

Non-goals: remote shell, auto-kill without dry-run, multi-cluster.

Demo:
1. run agent
2. curl /v1/snapshot
3. curl /metrics
4. docker run + SIGTERM

Worked example — Option C pitch (sample)

## Choice
Option C — “shipctl” release checklist CLI

Pitch: Validate a directory of deploy manifests and produce a go/no-go report.

Non-goals: full CD platform, cluster apply by default.

Demo:
1. shipctl init
2. shipctl check ./testdata/ok
3. shipctl check ./testdata/bad (exit 1)
4. multi-arch build-release.sh

Lab

Suggested workspace: new or existing capstone module root.

  1. Choose A, B, or C; write it at the top of CAPSTONE.md.
  2. Fill all template sections—especially non-goals and milestones.
  3. List concrete demo steps (must be executable on Day 90).
  4. Inventory reusable code from earlier days.
  5. Optional: open issues/TODO comments matching milestones—no feature coding required today beyond spikes ≤30 min if blocked on feasibility.
  6. Commit the design doc even if empty module.

Decision timer

If stuck >20 minutes between two options: pick B if you have a Stage VI service; else C if you prefer local tools; A if you care about agents/ops.

Spike rules (optional ≤30 min)

Allowed: verify a library compiles, open a SQLite file, list processes once.
Forbidden: building Day 86 features “just to see.”


Common gotchas

Gotcha Fix
Design doc as essay without milestones Table Days 83–88
Building during design day Stop; capture spike notes only
Optional everything One auth story, one data story
Ignoring quality bar Copy Stage VII gate into §6
Demo requires cloud accounts Prefer local Docker/SQLite
Three options “slightly started” Delete two; one CAPSTONE.md choice
Unmeasurable done-when Binary checks: “curl returns 200”

Checkpoint

  • Single option chosen
  • CAPSTONE.md complete
  • Non-goals explicit
  • Day-by-day milestone table
  • Demo outline written
  • Reuse inventory listed

Commit

git add .
git commit -m "day82: capstone design doc"

Write three personal gotchas before continuing.


Tomorrow

Day 83 — Capstone build 1: repository skeleton, tooling, and the first runnable vertical spike per your design.