FixedPlacementPolicy is the DEFAULT placement policy (PlacementPolicies.fromName
returns it for an absent/blank name) and it built its own inline candidate
filter instead of calling PlacementPolicyUtil.available(). That filter checked
quarantined/coolingOff/unreachable/excluded() but never modelOff(), so an
unqualified fleet_spawn on any fleet without an explicit placement: policy
could still land on a profile whose model the operator turned off.
- Add ctx.modelOff() to both filter sites: the default-profile fast path and
the fallback walk over ctx.candidates().
- Add a modelOff refusal reason to the default-profile reasons list, worded as
an operator decision ("turned off in models.allow"), matching
enforceModelEnabled. Quarantine and cooling off still take priority when a
profile is also model-off, matching CompositePeerLauncher's explicit-spawn
check order.
- Update the two stale "excluded from automatic selection" messages to name
model-off, consistent with PlacementPolicyUtil.emptyException.
- Update the class javadoc: four exceptions -> five, with a new bullet for
model-off (fleetd #422).
Tests: PlacementPolicyTest gains fixedSkipsModelOffDefault (fast-path),
fixedFallbackWalkSkipsModelOffCandidate (fallback walk),
fixedThrowsWhenDefaultAndEveryCandidateModelOff (all-off refusal wording), and
fixedReportsQuarantineNotModelOffWhenBothApply (priority). CompositePeerLauncherTest
gains fixedPlacementSkipsAnOffModelProfileToo, an integration-level mirror of
the existing placementSkipsAnOffModelProfileAndRoutesToAnotherOne but under
PlacementPolicies.fixed(). The two existing weighted()-based tests are
untouched.
Ships the two halves left out of the earlier allow-list ticket in one PR,
since apart they are inert: a gate with no flag always allows, and a flag
nothing reads does nothing.
- FleetConfig.Models.ModelEntry gains `enabled` (default on; absent/true =
on, false = off). Turning a model off never removes it from `allow:` —
validateModels() checks membership only, so an off model stays valid
config and a still-configured profile naming it does not refuse reload.
Models.offIds() is the one live accessor both the gate and the status
report read.
- CompositePeerLauncher.enforceModelEnabled is a FOURTH, independent
spawn-refusal reason (operator intent) — never layered onto
BackendQuarantine/BackendOutagePolicy, which are backend-reported outage.
Wired into the explicit-profile branch. modelOffProfiles() feeds the same
off-model exclusion into PlacementContext for unqualified spawns via
PlacementPolicyUtil (a new modelOff set, counted into its own bucket in
emptyException so "all off" is named as the cause, not generic).
Both read models0(), a live Supplier<FleetConfig.Models>, so a reload
reaches the very next spawn — no restart.
- PeerLauncher.disabledModels() (default empty) lets fleet_profiles/
GET /profiles report off models by reading the exact same accessor the
gate reads (the fleetd #404 lesson: a status field must read the source
the behaviour reads).
- ConfigRef: `models` reclassified from deferred to hot-excluded — nothing
about it is baked into a startup-built object anymore; membership is
re-validated in full on every reload via validateAll(), and the on/off
half is read live everywhere. Tally: 5 cold, 13 deferred, 3 split, 4
hot-excluded (25 total). ConfigRefTopLevelCoverageTest and
ConfigRefTopLevelReportingCoverageTest updated with no new exclusion
added just to force green.
Tests: FleetConfigTest (old-style fixture stays on; an off model is still
valid config; one model disables every profile naming it),
CompositePeerLauncherTest (explicit refusal wording distinct from
quarantine/cool-off; unqualified spawn skips an off candidate and names
model-off when every candidate is off; a real ConfigRef.reload() proves
the hot path; disabledModels() matches the gate).