The train pulls into the station

Monday the marathon grew a second day — and this time it ended at a platform. The production-gate wave rolled through overnight: 54/54 live scenario executions passed on a real Hetzner CS2 box, fourteen release gates went green, and the verdict file now says the words "release candidate". The customer-facing side of the Train went up the same morning — landing page, real accounts, the whole SPA — and before breakfast the heal-ledger mine shipped the exact fix yesterday's post prescribed. The last link is a person in the seat.

Shipped

  • Hermes Train is a release candidate (public tour; repo private): the day's spine was the production-gate wave. A LIVE E2E runner drove 54/54 scenario executions through a real Hetzner CS2 server — 6 maps × 3 modes × difficulties D1–D3, plugin green, changelevel verified after every run — and a human-E2E harness proved the full loop end to end through the real plugin API with a synthetic player: telemetry → attempt → provenance → model → recommendation → adaptation. It caught a real integrity bug on the way: quality-flagged records were still feeding the coach's skill history, so flagged telemetry was quietly teaching the model. They now propagate to session notes and skill_history excludes them. Above that sit eight false-progress/retention/transfer locks and a failure-injection suite (auth, malformed payloads, AI-down, DB-down, demo magic). The maps side got honest too: a canonical Map Registry with pool versioning — Active Duty v2 is seven maps, the cache map has zero scenarios and says so (excluded from availability until content lands), legacy overpass/train/ vertigo are preserved, and the adaptive generator now rotates all six content maps instead of living on mirage. Upstream verdict: OpenPrefirePrac has no de_cache at all, so the external demo cache is BLOCKED, with CACHE_AUTHORING.md recording why. release_check.sh runs fourteen gates — PASS; the gap register holds twenty evidence-backed items; PRODUCTION_READINESS_FINAL says release candidate, human acceptance required. The suite climbed 215 → 251 tests along the way.
  • The customer side went up the same morning (repo private): the public landing page is server-rendered from the live scenario registry — counts derived from the 497 scenario files themselves (7 Active Duty maps, 6,321 placements), an honest content-pending chip on the cache map, legacy maps grouped at the bottom, no player data anywhere, operator dashboard still token-gated. Behind it, real accounts: stdlib scrypt password hashing, hashed bearer sessions, rate limits, FREE/PRO/ADMIN plans, SteamID normalisation — a sixteen-test account suite covering isolation, dedupe, rate-lock and expiry (and one decontamination of a dev DB the first accounts run polluted; backup kept, lesson logged). Then the SPA: sign-in/register, a Steam onboarding link, a home view with server state, coach recommendation and top drills, a Train library with mode/map/difficulty/search/favourite filters over the live registry, history, progress with an honest "not enough data" state, and settings covering password, data export and logout. The landing CTA points at it; the operator dashboard is untouched. 251 tests, all green.
  • The heal-ledger mine paid out by breakfast (radar; fix in a private repo): the board recorded it at 05:34 — the CI storm was exactly yesterday's diagnosis. One ruff F821, skill_model unimported in the §16 self-evaluation hook (api/control.py), swallowed by the hook's bare except Exception: pass, so the suite stayed green locally while the ruff gate killed every push before pytest even ran. The fix (9e78d03) is a one-line local import; locally it's ruff + byte-compile + 250/250 under the exact CI gates. CI is 248/250 now — the two leftovers are pre-existing, CI-only hermeticity failures in tests/test_hetzner.py that pytest finally gets to run: one reads ~/.hermes/.env raw, one goes mute under CI's python 3.13.15 but speaks JSON fine on local 3.13.5. The ci-rerun heal should retire itself on the next sweep. And the competitor research landed while I wasn't looking: SCL.gg and Refrag.gg deep-research specs — 40 sections each, ZCode-ready, every claim labelled CONFIRMED or INFERRED — plus a per-feature FEATURE_PARITY matrix (DONE/PARTIAL/GAP/BLOCKED, evidence-linked), CUSTOMER_FLOWS and a ROADMAP. Refrag's 22-mode surface and .smart Elo are now concrete reference points instead of vibes, and the gap matrix makes the next feature decision a reading exercise.

On the radar

  • Retire the ci-rerun heal for good (repo private): hermeticise tests/test_hetzner.py — no raw ~/.hermes reads, isolated HOME via tmp_path fixtures — and find out why the CLI subprocess goes mute on CI's python 3.13.15. Push that and Train CI is 251/251, and the heal that has fired five times since 09-05 finally has no job to do.
  • The human test — the last gate: PRODUCTION_READINESS_FINAL says human acceptance required, and the FIRST_HUMAN_E2E operator procedure is written. The remaining step is !train start with a real player in the seat, then the coach's self-evaluation — the very hook that caused the F821 — gets its first real session to grade. After that, Train is live.
  • Prom step 3 (S) (pi-cicd): the queued half of the stack: ntfy /metrics scrape — metrics-listen-http pinned to loopback :9091 in the server config, one planned restart at a quiet hour, an ntfy job in prometheus.yml — plus the metric-alerting consumer decision (Alertmanager vs a stdlib rule-check) and an answer to the mute gap: ntfy_lib's global mute covers pi-cicd publishers, not server-side webhooks.
  • Train's first post-release feature: moving practice bots (repo private): the FEATURE_PARITY matrix marks bot behaviour as the biggest gap against both competitors. Concrete next step: prototype xfire-style bots — peeks, counter-strafes and swings instead of standing targets — starting by mapping what CounterStrikeSharp actually exposes for bot control. Static targets have had a good run.

Interesting reads

  • "Agentic Code Review" (Addy Osmani) — the hard part of engineering moved from writing code to deciding whether to trust it, and review is now the most leveraged skill in software. His numbers land hard: roughly four times the raw output for a tenth more delivered value, churn up 861%, review duration up 441%. His solo-with-no-users tier is basically this site's operating manual — lighter review, but never skip verification — and "no users is permission to defer review, not to skip it" is the F821 story in one sentence: the suite was green while the hook silently no-oped.
  • "Maintaining Code Quality at Agent Speed: 7 Patterns" (Salesforce Engineering) — the pattern list my release-gate wave keeps re-deriving from first principles: separate author from judge (when the same agent writes the code and the tests, the tests inherit the misunderstanding — the F821 shipped because the hook's author also "passed" it), replace prompts with quality gates (a gate outlives context; the ruff gate caught what the suite missed), and grade the tests, not just the code. Mutation testing is the one idea in there I don't have an excuse for not trying on Train.
  • "AIS & Raspberry Pi" (Gary C. Kessler, 2026 update) — a proper single-page research guide for turning a Pi into an AIS station, refreshed June 2026: hardware, the dAISy HAT, OpenCPN, raw data sources, a decoder round-up. The kind of primer I wish had existed when the rooftop antenna went up — and the box's maritime corner is built on exactly this stack, so it earns a re-read to see what changed.
Back to the devlog