Build guards — the fix-forward catalog
The discipline that keeps bugs from recurring — every fix lands upstream, gets a regression test, and a catalog entry. The full G1–G19 index.
Build guards — the fix-forward catalog
The build guards are HostSSH's institutional memory of every bug that mattered. The rule is simple and absolute: when we hit a real defect, we don't patch the symptom on one box — we fix it at the most upstream layer, add a test so it can't silently return, and record it here.
The authoritative catalog (symptom → root cause → forward fix → guard/test, with full detail) lives
at planning/BUILD-GUARDS.md. This page explains the discipline
and indexes the guards for developers. The user-facing symptom→fix version is
docs/user/troubleshooting.md.
The discipline
For every bug we hit:
- Fix at the most upstream layer that makes it automatic for every app — the HostPack build, the agent deploy/run defaults, the control-plane deploy pipeline, or a shared template — not per-app or per-box.
- Add a regression test so it cannot silently return.
- Add a catalog row (symptom → root cause → forward fix → guard/test).
- Enforce it in the checks gate (
/system+ the per-app verify) so a regressed build can't reach production.
Patch as we go: the moment a fix is generalizable, it moves upstream the same session we find it. A fix that lives only on one server is a bug waiting to reappear.
This is why, for example, HOSTNAME=0.0.0.0 is a platform default and not something every app owner
rediscovers, and why a failed deploy gate rolls back instead of leaving an app down.
The catalog index (G1–G19)
| # | Title | Class | Status |
|---|---|---|---|
| G1 | Next.js standalone binds IPv6-only → 502 | deploy default | fixed |
| G2 | Silently-dead container never restarts | deploy default | fixed |
| G3 | Env dropped by a full redeploy | env custody | fixed |
| G4 | Partial-bake on redeploy (stored Dockerfile) | deploy model | fixed (from-source only) |
| G5 | Agent grace clock stalls (license refresh rejected) | licensing | fixed |
| G6 | Flaky session-recorder frame test (race) | test hygiene | fixed |
| G7 | Build cache wiped on every deploy | deploy performance | fixed 2026-06-30 |
| G8 | Failed gate leaves the app DOWN | deploy honesty | fixed 2026-06-30 |
| G9 | Agentless node reads as a dead box | UI honesty | fixed 2026-06-30 |
| G10 | Managed-proxy deploy succeeds but the cert never issues (pre-DNS ACME backoff) | TLS/edge | open (operational; forward-fix proposed) |
| G11 | Strict build-time env-check blocks a from-source build (env in context, not just runtime) | build-config | open (playbook + forward-fix proposed) |
| G12 | HTTP-01 is not the production TLS path (no zero-downtime cutover, no wildcards) | TLS/edge | fixed 2026-07-01 (DNS-01) |
| G13 | WP migration rewrite: cascade corruption, shared-DB mass-drop, unanchored hosts | data safety | fixed 2026-07-02 |
| G14 | A node served workloads with no license; secrets + backups single-copy | resilience | fixed 2026-07-02 |
| G15 | Containers ran unhardened (no no-new-privileges; setuid escalation) | container security | fixed 2026-07-03 |
| G16 | A rebuilt node did not self-harden (firewall was a manual step) | reproducibility | fixed 2026-07-03 |
| G17 | Auth had no brute-force protection (unbounded password + TOTP guessing) | auth security | fixed 2026-07-03 |
| G18 | Hardening was hardcoded; firewall required the ufw package | config/portability | fixed 2026-07-03 |
| G19 | Panel firewall config didn't survive a rebuild (no boot-reapply) | persistence | fixed 2026-07-03 |
Each row is fully written up in planning/BUILD-GUARDS.md. The
three still-open items (G10, G11, and the G1/G2 "overclaim" — the code that makes them automatic)
are tracked as Gate A code work; see planning/MIGRATION-MASTER-PLAN.md
and planning/TODO.md.
Register note (accuracy): the Gap Register flags that the code making G1/G2 automatic — the
HOSTNAMEinjection and the default container healthcheck/restart — is specified but not yet landed. Until it lands, treat G1/G2 as "fix designed, guard test pending." Don't cite a guard as done in a PR unless the regression test exists and passes.
Where a new guard lives in the code
| Layer | Package / dir | Guard examples |
|---|---|---|
| Agent deploy defaults | agent/internal/deploy/, agent/internal/runtime/ | G1, G2, G7, G8 |
| Agent builder | agent/internal/builder/ | G4, G11 |
| Agent proxy / TLS | agent/internal/proxy/ | G10, G12 |
| Agent hardening | agent/internal/harden/, firewall engine | G15, G16, G18, G19 |
| Control-plane platform | control-plane/web/lib/platform/ | G3 (env custody), G8 (rollback state) |
| Control-plane auth | control-plane/web/lib/auth/, ratelimit | G17 |
| WP plugin | wordpress-plugin/ | G13 |
| Fleet/telemetry UI | control-plane/web/lib/fleet/ | G9 |
When you add a guard, put the fix in the upstream layer above, colocate a *_test.go / *.test.ts
regression test, and append the catalog row in planning/BUILD-GUARDS.md.