Batch 4C pt4: gate sources engine set from render-units --list-units (D-CONSUMER) - #196
Conversation
…nits` (D-CONSUMER) Increment 5 of Batch 4C. The cutover gate verified "every KAYAK_UNITS member not in KAYAK_HOST_UNITS must run from $ROOT/current" — but KAYAK_HOST_UNITS was a hand-maintained exempt list AND it was incomplete: the checkout-script consumers (healthcheck/config-drift/recap/heartbeat) run from the repo, not the venv, so an exempt-by-omission gate would false-fail on them at the live cutover. Fix: source the must-run-from-current set from the engine itself. - `levels render-units --list-units` (new) prints exactly the engine .service names, derived from render_cutover_dropins (host_render.engine_unit_names), so it can never drift from the units that get cutover drop-ins. It's host.yaml- independent (the deployer calls it before the host is fully configured). - The gate now does `ENGINE_SVCS="$(…/levels render-units --list-units)"` and verifies each of those runs from current + pins the right OUTPUT_DIR; empty output is refused (fail-closed). Everything else is implicitly host-level. - KAYAK_HOST_UNITS is removed. KAYAK_UNITS stays the independent quiesce set; the gate no longer reads it, so a quiesce-list omission can't silently skip verification. Tests: engine_unit_names() equals the rendered drop-ins exactly; the --list-units CLI (incl. ignoring a bad host.yaml); and a deploy gate case where an engine unit still runs from the old /home/pat/.venv checkout → refused (also exercises the --list-units enumeration). Non-slow 1765 passed; full slow suite re-run on this commit (the harness stages the wheel from git HEAD, so --list-units must be committed for the gate's call to resolve). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adversarial review — PR #196 (Batch 4C pt4: gate sources engine set from
|
…losed test
1a. The gate's `render-units --list-units` call no longer swallows stderr with
`2>/dev/null` — it captures it to $SCRATCH/list-units.err and prints it
(indented) on the empty-output refusal, so an import error or an
`unrecognized arguments` from an engine ref too old to support --list-units
surfaces the actual cause instead of a bare "produced nothing".
1b. Documented the forward-only engine-version floor in the deployer header (the
cutover gate asks the RELEASE engine for the unit list, so the activated ref
must support `render-units --list-units`; an older ref fails closed; rollback
is unaffected), and the refusal message now says so.
2. Added the `KAYAK_ENGINE_BIN` override (the release `levels` the gate asks for
the list; default $RELEASE_DIR/venv/bin/levels) — same test-seam pattern as
KAYAK_SYSTEMCTL/KAYAK_RUNUSER — and gate case 2c: point it at a no-op stub so
--list-units prints nothing, asserting the fail-closed "could not enumerate
the engine units" refusal. Locks the behavior the PR designed around.
Full slow deploy suite 7 passed; shellcheck clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks — both points addressed in the pending commit. 1a (diagnostics). Dropped the 1b (engine-version coupling). Documented the forward-only floor in the deployer header ("Engine-version floor (cutover only): … the engine ref being activated must support 2 (test the empty path). Added the shellcheck clean; full slow deploy suite re-running with the new case (will confirm). Both were polish, so nothing changed about the core engine-as-source-of-truth design you approved. |
Batch 4C — increment 5: gate sources the engine set from
render-units(D-CONSUMER)Resolves the D-CONSUMER open decision. The deployer's cutover gate verified "every
KAYAK_UNITSmember except those inKAYAK_HOST_UNITSmust run from$ROOT/current" — butKAYAK_HOST_UNITSwas a hand-maintained exempt list, and it was incomplete: the checkout-script consumers (healthcheck,config-drift,recap,heartbeat) run from the repo, not the venv, so an exempt-by-omission gate would false-fail on them at the live cutover.The fix: source the "must run from current" set from the engine itself
levels render-units --list-units(new) prints exactly the engine.servicenames — derived fromrender_cutover_dropins, so it can never drift from the units that actually get cutover drop-ins. It's host.yaml-independent (the deployer calls it before the host is fully configured).ENGINE_SVCS="$(…/levels render-units --list-units)"and verifies each of those runs fromcurrent+ pins the rightOUTPUT_DIR. Everything else (the checkout-script + host-level consumers) is implicitly host-level — no list to keep in sync, and a new engine consumer is covered the moment it's added to the renderer. Empty output → refuse (fail-closed, not fail-open).KAYAK_HOST_UNITSis removed.KAYAK_UNITSstays the independent quiesce set; the gate no longer reads it, so a quiesce-list omission can't silently skip verification.Why this over "derive both lists from installed timers"
The engine-consumer set is the only thing that needs a source of truth, and
render_cutover_dropinsalready is that source — so the gate reads it directly rather than re-deriving a classification fromsystemctl. Quiescing is a superset (harmless to over-quiesce), so it stays a simple list.Tests
engine_unit_names()equals the rendered drop-ins exactly (no drift) + the--list-unitsCLI (incl. that it ignores a bad host.yaml)./home/pat/.venvcheckout → refused (which also exercises the--list-unitsenumeration — an empty list would skip everything).Next: increment 6 — the runbook §5 rewrite (replace hand-crafted steps with the
render-*calls + drop theKAYAK_HOST_UNITSsetup), then the VM-rehearsed live cutover.🤖 Generated with Claude Code