How this gets built safely
This is a solo-developer project, but no change reaches players by going straight from an idea to production. Every change — no exceptions — moves through the same five-stage pipeline below. This page names each real stage, what it actually catches, and points at the file, test, or commit that proves it's true today, not aspirational.
One fixed sequence, always in this order. Nothing skips a stage, and nothing reaches production without passing through all of them:
flowchart LR
A["Architect
spec + named invariants"]
B["Developer
builds + writes
thorough tests"]
C["Qcoder
independent
automated review"]
D["Architect
reviews the actual diff,
records a verdict"]
E["CI/CD
secret scan + pytest
gate the deploy"]
P(["Production"])
A -->|"spec committed"| B
B -->|"tests green,
diff ready"| C
C -->|"findings reported"| D
D -->|"verdict: conforms"| E
E -->|"both checks pass"| P
B -.->|"kick-back:
spec ambiguous"| A
D -.->|"kick-back:
does-not-conform"| B
The dashed arrows are a kick-back: work sent backward instead of forward, because a later stage found something wrong with an earlier one — an ambiguous spec, or a diff that doesn't actually match what was promised. A kick-back isn't a failure of the pipeline; it's the pipeline working as intended, catching the problem before it reaches players instead of after.
Each stage catches a different failure mode, and each one is real and checkable today — not a description of an ideal process. The sections below give a real, current pointer for every stage.
Before a Developer writes a line of code, the spec states the properties the change must preserve — not as an afterthought, but as invariants a violation of which is a kick-back, not a trade-off to weigh.
specs/SPARK_QUEUE_V2_CONTRACT.md §2 states, up front, that the self-hosted model machine ("the Spark") accepts no inbound connections and never gets access to participant data — entries holds emails and phone numbers, and the worker's database credential must not be able to reach that table. These aren't implicit assumptions the Developer is trusted to remember; they're named, so a review has something concrete to check the diff against.
The standing rule: happy path with real assertions (not just a status code), every guard tested under the wrong condition, and at least one real failure case — not decorative coverage.
tests/test_jobs_v2.py — test_claim_next_job_concurrent_exactly_one_wins spins up 8 real threads racing to claim the same job and asserts exactly one wins; test_append_job_event_concurrent_no_duplicates_no_gaps spins up 20 real threads appending events and asserts a gap-free, no-duplicate sequence. Both are genuine race conditions, caught and fixed because a thorough test was written — not despite one.
Once tests are green, the Developer runs tools/review_with_qcoder.py against every changed .py file. Each file gets a separate API call — the reviewer sees only the file's current content and its diff hunk, never the reasoning behind the change, so it can't be talked into agreeing with a flawed approach. It checks for correctness bugs, SQL injection, XSS, leaked secrets, missing @admin_required/@login_required guards, and this codebase's own conventions (db.query() vs db.execute(), raw SQL in templates, espnTeamId/gameYear casing).
tools/review_with_qcoder.py, commit 9152c97, recorded conforms in BUILD_BACKLOG.md. It exits non-zero at or above a configurable severity threshold (HIGH by default), so a bad finding is a deterministic failure to fix, not something to explain away. Honest gap: this stage is currently run by the Developer session, by convention, before reporting a spec done — it isn't (yet) wired into .github/workflows/deploy.yml as its own automated job the way the CI/CD stage below is.
A Developer's report of what they did is a claim, not a verdict. The Architect reads the real diff and independently verifies the specific things the report asserts, rather than recording a verdict because the report sounded confident.
/about/* rewrite, a Developer's report claimed an MCP hop had been removed from the daily-commentary path. The Architect didn't take that on faith — grepped the codebase for importers of mcp_client, found none, and only then confirmed the finding was real (see BUILD_BACKLOG.md's entry for that spec). That's the standard every verdict in this file is held to.
The first four stages are review by people (and one independent model call). This last stage doesn't depend on anyone remembering to run it — .github/workflows/deploy.yml fires on every push to main and the deploy job cannot start unless both checks upstream succeed:
secret-scan: git ls-files -z | xargs -0 python3 scripts/scan_secrets.py
test: pytest -q
deploy:
needs: [secret-scan, test] # <- won't run unless both jobs above succeeded
If either job fails, deploy simply never runs — there's no path from a red check to a live deploy. See How It's Built §3 for the full push-to-production sequence once the gate passes.
This page is about the pipeline a change moves through. The specifics of how credentials, passwords, and data access are actually handled live on their own pages, and — same rule as everywhere else on this site — they're written to show the known gaps alongside the strengths, not just the strengths:
/admin/docs/security and /admin/docs/credentials — the operational depth behind that page: what each credential can and can't reach, and the exact rotation procedure for each one. These live behind the admin login, since they document access boundaries in more detail than makes sense to publish openly.spark_poller database credential is due for rotation (a documented, exercised procedure — not a one-time setup), and admin authentication is a single shared password with no per-user accounts and no TOTP/MFA yet. Both are named and tracked rather than left as an unstated assumption, the same honesty standard How It's Built §11 applies to its own architecture tradeoffs. A page that only shows strengths reads as less credible, not more.