Day 28 — Load Testing, Security & Capstone Design
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.
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.jsonTargets 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 reporthey
go install github.com/rakyll/hey@latest
hey -z 30s -c 50 http://127.0.0.1:8080/v1/helloGood 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.jsAny one tool is enough. Prefer constant rate then step up.
Theory 3 — Experiment design
- Establish baseline at low RPS (or low VUs).
- Increase rate until p99 or errors break.
- Hold steady; capture pprof +
/metrics.
- Change one variable (pool size,
GOMAXPROCS, query, cache flag).
- 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.txtAmbient 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"
doneLOAD.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 || trueWhile 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 -20Story 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).
- Pick vegeta or hey or k6; install; record version.
- Start service; confirm one manual
curlsucceeds.
- 5s smoke attack at low rate.
echo "GET http://127.0.0.1:8080/healthz" \
| vegeta attack -duration=5s -rate=10 \
| vegeta reportLab 2 — Rate matrix on a real endpoint
- Target a non-health route.
- Run ≥3 rates × ≥15–20s each.
- Save reports under
load/or paste intoLOAD.md.
- Mark the cliff rate if any.
Lab 3 — Correlate observability
- Under the highest “interesting” rate, capture CPU pprof or mutex profile.
- Snapshot
/metricsbefore/after.
- Optional:
curl /debug/pprof/goroutine?debug=1 | headfor leak signals.
curl -s "http://127.0.0.1:6060/debug/pprof/goroutine?debug=1" | head -40Ensure pprof is localhost-bound (Day 65/81)—do not expose it to the world for this lab.
Lab 4 — One mitigation experiment
- Form a hypothesis from pprof/metrics.
- Change one variable.
- Re-run the cliff rate.
- Record before/after in
LOAD.md.
- 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
/metricsnetwork-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.WithTimeoutC. Input & data
- SQL via placeholders only (
$1/?)—never string concat
os/execwith fixed binaries +CommandContext; nosh -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)
.envgitignored;.env.exampleonly
- 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)
replacedirectives 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)
GOMEMLIMITaligned 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) // neverGood:
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' || trueTheory 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 binaryPrefer 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 storyAPIs 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 muxPrefer 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 endpointLab 1 — Walk the full checklist
Suggested workspace: primary service/CLI.
- Copy the Theory 1 sections into
SECURITY-CHECKLIST.md.
- Mark PASS / GAP / N/A with notes.
- Link Day 72
SECURITY-SCAN.mdand Day 80LOAD.md.
- 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
- Re-run
govulncheck ./....
- Run secret grep; triage hits.
- Confirm
.envignored:git check-ignore -v .env || true.
- 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 404If 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 scriptDay 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.mdcomplete
- ≥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.
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 day68Theory 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 spamIf 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 drainWorked 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 + SIGTERMWorked 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.shLab
Suggested workspace: new or existing capstone module root.
- Choose A, B, or C; write it at the top of
CAPSTONE.md.
- Fill all template sections—especially non-goals and milestones.
- List concrete demo steps (must be executable on Day 90).
- Inventory reusable code from earlier days.
- Optional: open issues/TODO comments matching milestones—no feature coding required today beyond spikes ≤30 min if blocked on feasibility.
- 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.mdcomplete
- 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.