How this gets built safely

The Development Process

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.

01The pipeline, end to end

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.

02What each stage actually catches

1Architect — spec + named invariants

Security invariants are written down, not assumed

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.

Real example: 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.
2Developer — builds + writes thorough tests

Thorough tests, per spec, gating every commit

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.

Real example: tests/test_jobs_v2.pytest_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.
3Qcoder — independent automated review

A fresh model call with no memory of intent

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).

Real example: the tool itself shipped through this exact pipeline — 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.
4Architect — reviews the actual diff

Does the diff match the spec — verified, not taken on faith

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.

Real example: during the /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.
5CI/CD — mechanical, every push, no human to forget

Secrets and test regressions, gated before deploy

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:

Real example — the actual gate order from the workflow file:
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.

03Where the credential and data-handling detail lives

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:

  • Security Practices — the public-facing page on how passwords, sessions, and least-privilege access between systems work.
  • /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.
Known, tracked gaps — not fixed, and not hidden: the 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.