fleetd #422 follow-up: make the model gate's own state observable #434

Merged
ltms merged 1 commits from worker/422-gate-state-observability-9e79d6-11 into main 2026-09-10 08:32:55 +02:00
Member

Closes fleetd #422 follow-up.

Problem: PeerLauncher.disabledModels() returned an empty set both when a host has no models: block at all (nothing gated) and when a block exists with nothing currently off (gate armed, reporting zero). fleet_profiles/GET /profiles and the startup log could not tell the two apart.

Fix:

  • Added PeerLauncher.ModelGateState(configured, off) and a modelGateState() default method; disabledModels() now delegates to it, so no caller reads a different source.
  • CompositePeerLauncher.modelGateState() reads models0() once (the exact accessor the spawn gate itself reads) and distinguishes a null supplier (no models: block) from a real config-supplied block via identity against the private NO_MODELS_CONFIGURED sentinel.
  • New startup log line via Fleetd.modelGateCoverageLine, following the exhaustedPatternCoverageLine/errorPatternCoverageLine shape from #415.
  • New modelGateArmed field in FleetMcp.profilesView, reported unconditionally (never omitted) alongside the existing modelsOff set.

Deliberately did NOT reuse CompletionResolver.UnsetMeaning: that enum exists so two DIFFERENT keys (exhaustedPattern/errorPattern) can each state what their own empty-coverage means. Here there's only one gate and one accessor, so a small dedicated ModelGateState record (configured + off) was a better fit than threading an unrelated enum through it.

Tests:

  • FleetdModelGateCoverageLineTest (4 tests) — the three states plus pairwise-distinct wording, following FleetdPatternCoverageLineTest's shape.
  • FleetProfilesModelGateStateTest (3 tests) — asserts through FleetMcp.profilesView, the actual surface a lead reads.
  • CompositePeerLauncherTest: +2 tests — modelGateStateIsHotReloadedThroughARealConfigRef (real ConfigRef.reload() walking all three states live, no restart) and noModelsBlockConfigStillLoadsAndSpawnsNormally (proves the invariant: no models: block loads, validates, and still spawns).

Build: mvn clean install — Tests run: 1544, Failures: 0, Errors: 0, Skipped: 0. BUILD SUCCESS (main was at 1535; +9 new tests).

Mutation proof (see PR description / worker report for full table): 4 mutations run, each killing a distinct test or test set — including the two required ones (always-armed, ignoring no-block; always-not-configured, ignoring an off model), plus one each for the Fleetd and FleetMcp sites. All reverted; final build reconfirmed green after revert.

Closes fleetd #422 follow-up. **Problem:** PeerLauncher.disabledModels() returned an empty set both when a host has no models: block at all (nothing gated) and when a block exists with nothing currently off (gate armed, reporting zero). fleet_profiles/GET /profiles and the startup log could not tell the two apart. **Fix:** - Added PeerLauncher.ModelGateState(configured, off) and a modelGateState() default method; disabledModels() now delegates to it, so no caller reads a different source. - CompositePeerLauncher.modelGateState() reads models0() once (the exact accessor the spawn gate itself reads) and distinguishes a null supplier (no models: block) from a real config-supplied block via identity against the private NO_MODELS_CONFIGURED sentinel. - New startup log line via Fleetd.modelGateCoverageLine, following the exhaustedPatternCoverageLine/errorPatternCoverageLine shape from #415. - New modelGateArmed field in FleetMcp.profilesView, reported unconditionally (never omitted) alongside the existing modelsOff set. Deliberately did NOT reuse CompletionResolver.UnsetMeaning: that enum exists so two DIFFERENT keys (exhaustedPattern/errorPattern) can each state what their own empty-coverage means. Here there's only one gate and one accessor, so a small dedicated ModelGateState record (configured + off) was a better fit than threading an unrelated enum through it. **Tests:** - FleetdModelGateCoverageLineTest (4 tests) — the three states plus pairwise-distinct wording, following FleetdPatternCoverageLineTest's shape. - FleetProfilesModelGateStateTest (3 tests) — asserts through FleetMcp.profilesView, the actual surface a lead reads. - CompositePeerLauncherTest: +2 tests — modelGateStateIsHotReloadedThroughARealConfigRef (real ConfigRef.reload() walking all three states live, no restart) and noModelsBlockConfigStillLoadsAndSpawnsNormally (proves the invariant: no models: block loads, validates, and still spawns). **Build:** mvn clean install — Tests run: 1544, Failures: 0, Errors: 0, Skipped: 0. BUILD SUCCESS (main was at 1535; +9 new tests). **Mutation proof (see PR description / worker report for full table):** 4 mutations run, each killing a distinct test or test set — including the two required ones (always-armed, ignoring no-block; always-not-configured, ignoring an off model), plus one each for the Fleetd and FleetMcp sites. All reverted; final build reconfirmed green after revert.
agent added 1 commit 2026-09-10 08:19:17 +02:00
fleetd #422 follow-up: make the model gate's own state observable
CI / contract (pull_request) Successful in 45s
CI / build (pull_request) Successful in 1m53s
7fd914df1a
PeerLauncher.disabledModels() reported an empty set both when no
models: block exists and when a block exists with nothing off, so
fleet_profiles/GET /profiles and the startup log could not tell an
inert gate from an armed one reporting zero. Add
PeerLauncher.ModelGateState (configured + off), a modelGateState()
default method disabledModels() now delegates to, and a
CompositePeerLauncher override that reads models0() once and
distinguishes the null-supplier case (no models: block) from a real,
config-supplied block via identity against the NO_MODELS_CONFIGURED
sentinel — reusing the exact accessor the spawn gate itself reads, per
the fleetd #404 lesson.

Wires the state into a new startup log line (Fleetd.modelGateCoverageLine)
and a new modelGateArmed field in FleetMcp.profilesView, reported
unconditionally alongside the existing modelsOff set.
ltms merged commit 6b7caba248 into main 2026-09-10 08:32:55 +02:00
Sign in to join this conversation.