# Publish-train rules — coupled controller/agent releases Standing gates for every artifact publish train (agent binary + controller golden + Day-0 manifest + floor). Each rule cites the incident that created it. The box-level backstop (rule 4) exists because rules 1–3 are operator discipline and discipline fails; the rules stay the primary control. ## 1. Manifest before floor (the GL-1 rule, restated) Publish and vouch the artifacts in the Day-0 manifest **before** any floor movement. A floor that points at an unvouched (or unpublished) version bricks self-updates: boxes are told to move to a version they cannot verify. (GL-1 supply-chain arc; see the go-live package records.) ## 2. The manifest screen carries the LIVE DB floor — save the floor field LAST The hub's Day-0 manifest UI also persists the global floor as a DB row (`hub_settings.min_controller_version` — `hub/internal/store/store.go`, `GetGlobalMinControllerVersion`). **The DB row overrides the env floor and acts immediately on the next report cycle** — it is not staged by the manifest vouch. **Incident:** publish train 0.81/0.113 (2026-07-11, `documentation/pilot/RUNBOOK-publish-0.81-0.113-2026-07-11.md`): the operator saved the manifest screen with the floor field filled; the floor acted at once and controller 0.113.0 reached Peti's box ~9 minutes **before** agent 0.81.0 — the exact forbidden skew, benign only because the box had zero NAS shares. **Rule:** fill the floor field **last**, in a separate save, only after rule 3's fleet check passes. ## 3. MinAgent fleet gate A controller release that depends on coupled agent behavior declares `MinAgent: X.Y.Z` in its CHANGELOG entry header line (felhom-controller convention, since v0.114.0; retroactively, 0.113.0's effective MinAgent was 0.81.0 for the NAS add). **The floor may not rise past a controller version whose MinAgent is not yet met fleet-wide:** check the hub dashboard — every reporting box must show agent ≥ MinAgent before the floor save. Manual at the current fleet size; hub-enforced per-box gating is the deferred option-3. ## 4. Box-level backstop: the controller's capability gate Since controller v0.114.0, a coupled feature entry point probes the agent's capability (`controller/internal/agentapi/features.go` — route probe: 2xx ⇒ supported, 404 ⇒ older agent, transport/5xx ⇒ indeterminate, never "too old") and refuses up front with an honest Hungarian message instead of failing mid-pipeline. This turns a violated ordering into a graceful refusal — it does **not** license sloppy trains: rules 1–3 remain the primary control. Convention for new coupled features: add a row to the `featureProbes` table + a `Supports` gate call at the feature's entry point, and declare MinAgent per rule 3. When the agent someday reports an explicit version in its envelope, `Supports` should prefer the version comparison (roadmap).