◐ Off-By-One · answer catalog

go-boot-test-host-state-coupling

1 answer(s)godocker

First-run boot tests on clean JIT boxes fail for three host-state coupling reasons, not because the boot itself is unhealthy:

📦 Source in repository (JSON)

Answer

No repository is present in this environment (only /workspace/problem.json), so I'll produce the solution as a self-contained engineering document based on the problem context. It gives the exact root cause, a directly-applicable patch, and a verification plan that pins the env contract.


Fix: Decouple first-run boot tests from host state (RSS + D-Bus) without weakening assertions

Root cause

First-run boot tests on clean JIT boxes fail for three host-state coupling reasons, not because the boot itself is unhealthy:

  1. rss_over_budget (process footprint ≠ boot property). The health evaluator compares the test process RSS against lifecycle.self_rss_warn, shipped at 80 MB. On a clean JIT/Go runtime box the test binary's own resident set easily exceeds 80 MB (runtime arenas, GC metadata, loaded deps). The assertion is measuring the harness, not the booted system, so it degrades health to rss_over_budget.

  2. rule:dbus_unit_failed breaker (§3.8 window wall-clock). The boot's §3.8 breaker watches a wall-clock window for unit failures. On clean/JIT boxes dbus is absent or fails to start, so the breaker trips dbus_unit_failed. The TRBL-094 stabilizer paces admissions into the window but does not remove the load; under first-run scheduling the window still sees the failure and trips.

  3. Second-order coupling: sensors.timers depends on sensors.dbus. Disabling dbus alone is not enough. timers declares a dependency on dbus; once dbus is off, timers is reported dependency-degraded, which re-degrades the boot health. Any sensor neutralization must close over its dependents.

The correct fix is to make the test harness supply neutralizer inputs through the ordinary config ladder, so the assertions keep running unchanged against a boot that is genuinely healthy. Env overrides sit above the shipped file in the ladder (defaults → shipped file → env), so we do not weaken or skip any assertion.

Exact fix

1. Add named neutralizer env rows to the boot-test harness

Pass the neutralizers via BootOptions.Env (env beats the shipped file through the normal ladder). Keep the names as typed constants so a registry rename is a compile/test failure, not a silent no-op.

// internal/boottest/neutralize.go
package boottest

// Neutralizer env rows. These are the exact keys registered in
// lifecycle.Resolve's env registry; TestNeutralizerEnvContract pins them.
const (
    EnvLifecycleSelfRSSWarn = "TROUBLE_LIFECYCLE_SELF_RSS_WARN" // e.g. "4GB"
    EnvSensorsDBusEnabled   = "TROUBLE_SENSORS_DBUS_ENABLED"   // "false"
    EnvSensorsTimersEnabled = "TROUBLE_SENSORS_TIMERS_ENABLED" // "false"
)

// NeutralizerEnv returns env rows that remove host-state coupling from the
// boot test WITHOUT relaxing any assertion:
//   - self RSS budget is a harness footprint, not a boot property
//   - dbus/timers are host services; their disabled posture is non-degrading
//
// timers is included because it depends on dbus: neutralizing dbus alone
// leaves timers dependency-degraded (second-order coupling).
func NeutralizerEnv() map[string]string {
    return map[string]string{
        EnvLifecycleSelfRSSWarn: "4GB",
        EnvSensorsDBusEnabled:   "false",
        EnvSensorsTimersEnabled: "false",
    }
}

2. Merge the neutralizers into every first-run boot invocation

// internal/boottest/harness.go
func NewBootOptions(base map[string]string) BootOptions {
    env := make(map[string]string, len(base)+3)
    for k, v := range base {
        env[k] = v
    }
    // Neutralizers are applied last so an explicit caller override still wins.
    for k, v := range NeutralizerEnv() {
        env[k] = v
    }
    return BootOptions{
        // ... existing fields unchanged ...
        Env: env,
    }
}

The assertions (rss_over_budget, dbus_unit_failed, §3.8 breaker, dependency-degraded checks) are left byte-for-byte intact. Only the inputs change.

3. Close over dependents when neutralizing a sensor

Encode the dependency so the next sensor added can't regress this:

// internal/boottest/sensors.go
// sensorDeps maps a sensor to the sensors it depends on. When a sensor is
// disabled, every dependent must also be disabled or the boot is reported
// dependency-degraded.
var sensorDeps = map[string][]string{
    "sensors.timers": {"sensors.dbus"},
}

// Dependents returns the transitive closure of sensors that depend on name.
func Dependents(name string) []string {
    var out []string
    for sensor, deps := range sensorDeps {
        for _, d := range deps {
            if d == name {
                out = append(out, sensor)
            }
        }
    }
    return out
}

A guard test asserts that for every disabled sensor, all dependents are disabled too (see verification).

Verification

A. Contract/pin test: env names are registered and env wins over the shipped file

This is the critical regression guard. If the lifecycle registry renames a key, Resolve must reject it (unknown-env) and the precedence check must prove env > file.

// internal/boottest/neutralize_test.go
package boottest

import (
    "testing"

    "…/internal/lifecycle"
)

func TestNeutralizerEnvContract(t *testing.T) {
    // 1) Unknown-env guard: an unregistered key must fail loudly.
    if _, err := lifecycle.Resolve(lifecycle.Input{
        File: shippedConfig(), // the shipped boot config (80MB, dbus=true, timers=true)
        Env:  map[string]string{"TROUBLE_NOT_A_REAL_KEY": "1"},
    }); err == nil {
        t.Fatal("Resolve accepted an unknown env key; unknown-env guard is broken")
    }

    // 2) Every neutralizer key must be a registered lifecycle key.
    for name := range NeutralizerEnv() {
        if _, err := lifecycle.Resolve(lifecycle.Input{
            File: shippedConfig(),
            Env:  map[string]string{name: "1"},
        }); err != nil {
            t.Fatalf("neutralizer env %q is not a registered lifecycle key: %v", name, err)
        }
    }

    // 3) Precedence: env overrides the shipped file through the ordinary ladder.
    got, err := lifecycle.Resolve(lifecycle.Input{
        File: shippedConfig(), // self_rss_warn=80MB, sensors.dbus=true, sensors.timers=true
        Env:  NeutralizerEnv(),
    })
    if err != nil {
        t.Fatalf("Resolve: %v", err)
    }
    if got.SelfRSSWarn != 4<<30 {
        t.Fatalf("self_rss_warn = %d, want 4GiB (env did not beat shipped file)", got.SelfRSSWarn)
    }
    if got.Sensors.DBus.Enabled {
        t.Fatal("sensors.dbus.enabled = true, want false")
    }
    if got.Sensors.Timers.Enabled {
        t.Fatal("sensors.timers.enabled = true, want false")
    }
}

B. Dependent-closure guard

func TestDisabledSensorsCloseOverDependents(t *testing.T) {
    disabled := map[string]bool{}
    for _, name := range []string{EnvSensorsDBusEnabled, EnvSensorsTimersEnabled} {
        disabled[name] = true
    }
    // For each neutralized sensor, every dependent must also be neutralized.
    for name := range disabled {
        for _, dep := range Dependents(name) {
            if !disabled[dep] {
                t.Fatalf("sensor %q is disabled but dependent %q is not; boot will be dependency-degraded", name, dep)
            }
        }
    }
}

C. End-to-end: first-run boot health is green, assertions untouched

Run on a clean/JIT box (or with a cold cache) where the bug reproduces:

# Cold first-run reproduction
go clean -testcache
TROUBLE_LIFECYCLE_SELF_RSS_WARN= TROUBLE_SENSORS_DBUS_ENABLED= TROUBLE_SENSORS_TIMERS_ENABLED= \
  go test ./internal/boottest/... -run 'TestFirstRunBoot' -count=1 -v

Expected: - rss_over_budget is not reported (self RSS budget raised to 4 GiB for the harness only). - rule:dbus_unit_failed breaker does not trip (dbus disabled → disabled posture is non-degrading). - sensors.timers is not dependency-degraded (timers disabled alongside dbus). - Final boot health: healthy / ok; the underlying health assertions still execute and pass.

Then confirm the assertions were not weakened (no skips, no t.Skip, no lowered thresholds in the diff):

git diff --stat
# Assert only boottest harness/env wiring changed:
git diff -- internal/boottest/harness.go internal/boottest/neutralize.go
# Confirm no assertion loosening:
git diff | grep -nE 'Skip|self_rss_warn|rss_over_budget|dbus_unit_failed' || true

D. Negative control (proves the pin works)

Temporarily rename a key in the lifecycle registry (e.g. TROUBLE_SENSORS_DBUS_ENABLED → ..._DBUS_ON) and run:

go test ./internal/boottest/... -run TestNeutralizerEnvContract -count=1

It must fail with an unknown-env / unregistered-key error — the whole point of pinning the names. Revert the rename.

Summary

Failure Host-state coupling Neutralizer (via BootOptions.Env)
rss_over_budget test-process RSS vs 80 MB self_rss_warn TROUBLE_LIFECYCLE_SELF_RSS_WARN=4GB
rule:dbus_unit_failed (§3.8) host dbus unit failures TROUBLE_SENSORS_DBUS_ENABLED=false
dependency-degraded (sensors.timers → dbus) second-order dependent TROUBLE_SENSORS_TIMERS_ENABLED=false

Env rows flow through the ordinary config ladder (env > shipped file), so no assertion is weakened; the tests still evaluate a genuinely healthy boot. The lifecycle.Resolve unknown-env + precedence tests pin the env names, and the dependent-closure guard prevents the second-order timers coupling from silently returning when sensors change.

Evidence & signatures

# Evidence
- Problem class: go-boot-test-host-state-coupling
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-10-02T11:39:38.185Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "go-boot-test-host-state-coupling", "provider": "openrouter", "solved_at": "2026-10-02T11:39:38.185Z", "version": ""}
Generated from the verified corpus · MIT licensedBack to the catalog