fleetd #446: make exhaustedPattern hot, name the fix, report it in fleet_profiles #457
Closed
agent
wants to merge 0 commits from
worker/446-hot-exhausted-pattern-0af580-6 into main
pull from: worker/446-hot-exhausted-pattern-0af580-6
merge into: fleet:main
fleet:main
fleet:worker/fleetd-612-unita-87807e-1
fleet:worker/612-b3-mcpwirings-da2b58-3
fleet:worker/612-b2-cb185-176d3a-2
fleet:worker/612-b1-completion-457459-1
fleet:worker/612-agaps-73a926-2
fleet:worker/608-sleeps-3a64ff-3
fleet:worker/621-b4520b-1
fleet:worker/618-b83894-2
fleet:worker/fleetd-615-e05481-5
fleet:worker/lead-autocompact-5f1ab2-3
fleet:worker/fleetd-613-f85deb-3
fleet:worker/fleetd-608-flaky-nudge-test-d0c2d1-3
fleet:worker/lead-context-gauge-ad404f-1
fleet:worker/gauge-wiring-9158c1-4
fleet:worker/redeploy-slowstart-ead0e5-5
fleet:worker/charter-bytes-13668c-6
fleet:worker/rollover-outcome-291483-2
fleet:worker/589-f64303-2
fleet:worker/593-1a8025-5
fleet:worker/589-fcd2aa-1
fleet:worker/568-9fdaa2-3
fleet:worker/571-attempted-outcome-5739f7-2
fleet:worker/581-completionresolver-cas-sites-0542b7-6
fleet:worker/562-loop-health-wiring-test-99611c-5
fleet:worker/562-surface-loop-health-7df5cc-4
fleet:worker/575-waiter-cleanup-sites-62ad80-1
fleet:worker/572-answer-lock-release-46a9ae-5
fleet:worker/567-probe-channel-leak-a38fc5-6
fleet:worker/551-record-before-send-7cbf56-1
fleet:worker/561-listener-fanout-survives-a-throw-61d538-2
fleet:worker/555-redeploy-main-flow-seam-65c2f5-2
fleet:worker/556-injector-owns-registration-e027a5-1
fleet:worker/552-post-restart-mktemp-abort-bc2672-4
fleet:worker/553-onstatus-completion-leak-0da881-2
fleet:worker/550-shasum-linux-196132-1
fleet:worker/538-loop-dies-on-error-4a5eeb-6
fleet:worker/426-health-coverage-ef1fd4-4
fleet:worker/504-failed-reported-clean-3cfd66-3
fleet:worker/537-capturedlog-close-e4c437-2
fleet:worker/459-broken-link-targets-cadc17-5
fleet:worker/535-appender-leak-fe74c1-1
fleet:worker/512-part2-shutdown-detection-434701-9
fleet:worker/529-logger-level-sweep-2a5533-8
fleet:worker/528-drain-gate-call-site-5de83d-7
fleet:charter/forge-mcp-vs-token
fleet:worker/521-swap-guard-unpinned-28e931-5
fleet:worker/519-probe-test-harness-d25ab8-4
fleet:worker/525-logger-level-leak-1b4eb0-6
fleet:worker/518-fleetmcp-resolver-wiring-8ef96c-1
fleet:worker/512-drain-complete-line-7edd71-3
fleet:worker/517-abort-branch-and-jar-id-41b641-2
fleet:worker/500-9e52c9-3
fleet:worker/509-4912f4-2
fleet:worker/511-9a4b23-1
fleet:worker/493-479f45-2
fleet:worker/505-03f8b2-1
fleet:worker/492-followup-detect-unclear
fleet:worker/501-a31fa0-7
fleet:worker/498-451d1c-5
fleet:worker/494-1015ce-2
fleet:worker/492-209647-1
fleet:worker/489-001902-2
fleet:worker/480-relative-handover-path-906323-1
fleet:worker/480-b-handover-skill-45bf1f-5
fleet:worker/474-followup-source-pin-f54a55-17
fleet:worker/474-charter-check-on-reload-f54a55-17
fleet:worker/466-quarantine-repeatcount-report
fleet:worker/393-opencode-skill-seeding-71854b-13
fleet:worker/469-canonical-tool-names-2a472a-16
fleet:worker/466-quarantine-escalation-5ae9c1-15
fleet:worker/464-charter-tool-name-guard-a85635-12
fleet:worker/463-listfleet-default-fails-open-f1c76c-11
fleet:worker/458-invariant-5-by-purpose-862f9a-10
fleet:worker/439-coordinator-row-gate-bc032a-8
fleet:worker/449-herdr-protocol-576015-4
fleet:worker/450-abstract-spawn-599e1c-5
fleet:worker/437-ack-refuses-177d91-1
fleet:worker/444-placement-window-feb56a-2
fleet:worker/440-helddurable-derived-d462d7-13
fleet:worker/425-rework-placement-resolve-c58ba1-9
fleet:worker/421-lead-peek-held-msgs-cdbad2-10
fleet:worker/435-fixed-policy-cap-fe11de-12
fleet:worker/422-gate-state-observability-9e79d6-11
fleet:worker/431-memberregistry-live-readers-cdbad2-10
fleet:worker/424-architect-slot-hot-038b41-7
fleet:worker/422-model-gate-spawn-c29f48-6
fleet:worker/425-default-profile-live-f55534-8
fleet:worker/415-coverage-wording-2cbf9c-5
fleet:worker/416-3ad1da-1
fleet:worker/418-588283-3
fleet:worker/deterministic-stamp-race-409-3cb7b6-10
fleet:worker/armed-reads-live-config-404-ed931f-9
fleet:worker/reply-peer-refusal-391-5a34bd-7
fleet:worker/models-allowlist-aa9e9b-3
fleet:worker/ttl-stamp-race-399-f1122f-8
fleet:worker/scrub-receipt-400-316b3e-5
fleet:worker/exhaustion-detection-395-105105-6
fleet:worker/scrub-abort-394-316b3e-5
fleet:fix/scrub-uid-abort
fleet:worker/task-scrub-517574-2
fleet:worker/t386-clock-bd5b78-4
fleet:worker/t384-scrub-813790-5
fleet:worker/t381-cc-748314-2
fleet:worker/t373-336973-2
fleet:worker/t365-3920c5-3
fleet:worker/t358-6e989b-1
fleet:worker/t355-8b321c-1
fleet:worker/fleetd-369-hermetic-git-tests-e8b19a-3
fleet:worker/fleetd-368-stale-lead-binding-f5682e-2
fleet:worker/fleetd-360-deploy-units-0d3793-1
fleet:worker/359-dead-lead-tabs-f1253b-4
fleet:worker/362-worktree-skills-c03e51-3
fleet:worker/361-coord-visibility-655144-1
fleet:362-plugin-visibility-and-drift
fleet:worker/errscan-bed2ca-2
fleet:worker/amqp-log-identity-bed2ca-2
fleet:worker/withdefaults-guard-561704
fleet:worker/sleepguard-82076d-1
fleet:worker/fd334-9ee1b6-5
fleet:worker/fd348-f1ab27-4
fleet:worker/fd335-a71c35-1
fleet:worker/fd342-174a17-2
fleet:worker/fd345-490d0f-3
fleet:worker/fleetd-337-5ec7d4-21
fleet:worker/fleetd-341-af5a6b-24
fleet:worker/fleetd-339-5ca0a2-23
fleet:worker/fleetd-338-83a4a1-22
fleet:worker/fleetd-333-281f46-18
fleet:worker/fleetd-329-11bdbb-16
fleet:worker/fleetd-330-2770fb-17
fleet:worker/fix-326-50506e-15
fleet:worker/fix-324-3e9bbf-14
fleet:worker/fix-323-b8287d-13
fleet:worker/fix-316b-bd0860-11
fleet:worker/fix-318-76ca36-9
fleet:worker/fix-317-486aec-8
fleet:worker/fix-315-ce47c5-6
fleet:worker/fix-307-275890-6
fleet:worker/fix-308-b4f664-7
fleet:worker/fix-309-ec3939-8
fleet:worker/fix-310-7a3974-9
fleet:worker/fix-302-52ad0e-9
fleet:worker/fix-298-ce1acb-8
fleet:worker/fix-297-66bd11-7
fleet:worker/fix-296-104622-6
fleet:worker/fix-293-bare-closetab-eb22b5-3
fleet:worker/fix-280-gone-ask-lapse-bca98e-2
fleet:worker/fix-290-reapidle-guard-coverage-9b0dd1-1
fleet:worker/fix-285-trust-seed-8f3565-10
fleet:worker/fix-284-backend-error-seat-85912c-11
fleet:worker/fix-282-chained-ask-e6d0bb-8
fleet:worker/fix-283-teardown-leaks-f40dfa-9
fleet:worker/fix-281-pin-handler-actions-4921ac-7
fleet:worker/audit-rendezvous-lifecycle-d072ae-2
fleet:worker/audit-health-placement-1a2476-6
fleet:worker/audit-teardown-exits-e207a5-3
fleet:worker/audit-launcher-asymmetry-27e370-4
fleet:worker/audit-rest-authz-6ca53c-5
fleet:worker/investigate-275-abandon-asking-fdef52-8
fleet:worker/fix-274-worktree-leak-b0095d-7
fleet:worker/fix-273-exhausted-pattern-9665b5-6
fleet:worker/fleetd-267-model-check-bd8068-1
fleet:worker/fleetd-131-archunit-18b834-7
fleet:worker/fleetd-266-sshagent-rename-a014ff-6
fleet:worker/fleetd-184-uid-claim-8e1f31-4
fleet:worker/fleetd-184-warn-b381ee-10
fleet:worker/fleetd-184-docs-be1d12-9
fleet:worker/fleetd-257-9bf010-7
fleet:worker/fleetd-103-23a113-6
fleet:worker/fleetd-247-342356-5
fleet:worker/fleetd-116-04dea8-4
fleet:worker/fleetd-252-a830e0-3
fleet:worker/fleetd-111-7e8673-9
fleet:worker/fleetd-155c-f8ef4b-8
fleet:worker/fleetd-176-b928ca-3
fleet:worker/fleetd-249-7a7878-2
fleet:worker/cb248-composition-root-b-9acdf7-15
fleet:worker/cb148-envrc-default-fa6c82-12
fleet:worker/cb201-unit5-wiring-6c12e6-8
fleet:worker/cb241-fallback-echo-1175e9-11
fleet:worker/cb149-trust-dialog-2392a5-9
fleet:worker/cb134-148-overlay-visible-c9b986-10
fleet:worker/cb234-session-id-keyed-04e1fc-1
fleet:worker/cb201-unit3-nudge-abdf5c-6
fleet:worker/cb201-unit2-policy-c1102c-5
fleet:worker/cb201-unit4-outcome-a13bfa-7
fleet:worker/cb201-unit1-classifier-91b9b1-4
fleet:worker/cb201-227-refine-831980-3
fleet:worker/cb175-model-readback-0f085f-1
fleet:worker/cb222-charter-tmpdir-17f013-1
fleet:worker/cb226-architect-slot-race-cd3aa8-3
fleet:worker/cb224-worktree-root-group-024523-2
fleet:worker/cb-123-role-demotion-c600f7-2
fleet:worker/cb-219-opencode-roots-1f677e-1
fleet:worker/cb214-claude-session-id-b9eab4-4
fleet:worker/cb213-zdotdir-wrong-process-dd6de4-3
fleet:worker/cb211-exhaustion-classification-9546e0-2
fleet:worker/cb137-ambiguous-task-4df3d8-4
fleet:worker/cb209-agentsessionid-4dfdb6-2
fleet:worker/cb185-hostenvnames-2692b5-3
fleet:worker/cb206-opencode-sqlite-128718-2
fleet:worker/cb185-worktree-group-fc0c99-1
fleet:worker/cb-137-ask-ticket-e7760c-2
fleet:worker/cb-172-broker-uri-d36ae4-4
fleet:worker/cb-175-model-readback-76ead6-3
fleet:worker/cb-161-pane-ancestry-293510-1
fleet:worker/cb-164-rebase-885863-8
fleet:worker/cb-164-empty-scrape-false-success-1a80af-3
fleet:fix/cb-197-ticket-ttl-from-completion
fleet:worker/cb-189-remote-url-coverage-4692f3-1
fleet:worker/cb-185-blockers-027756-4
fleet:worker/cb-192-gap-log-11b631-2
fleet:worker/cb-633-fix-5f4396-3
fleet:worker/cb185-router-d6436d-3
fleet:worker/cb185-router-routing-gaps-9e9d33-3
fleet:worker/cb185-paneids-992586-2
fleet:worker/cb-633-allow-list-union-ed374b-1
fleet:worker/cb-157-credential-in-remote-url-496e44-2
fleet:worker/cb-641-health-herdr-evidence-8f1f54-6
fleet:worker/cb-640-health-msg-evidence-99c9cd-1
fleet:worker/cb-642-fleets-status-skill-bbbc40-5
fleet:cb-634-ide-mcp
fleet:worker/lead-comms-wiring-c014b9-7
fleet:worker/lead-mailbox-c19577-6
fleet:worker/autocompact-window-82bc2f-5
fleet:worker/cb-634-probe-18056f-4
fleet:worker/cb635-broker-urienv
fleet:worker/cb-632-config-retry-8e0efa-7
fleet:lead/cb-622e-claude-md
fleet:lead/cb-622-followup
fleet:worker/cb-622a-165dff-1
fleet:lead/cb-622d-opencode-mount
fleet:worker/cb-622b-717c67-2
fleet:worker/cb-622c-ab7759-3
fleet:worker/cb-617b2-20ca4b-3
fleet:worker/cb-617a-5c2f4a-1
fleet:worker/cb596-4e49ef-3
fleet:worker/cb586-10500c-1
fleet:worker/cb-606-b9343a-25
fleet:worker/cb604-1445f8-24
fleet:worker/cb582-477374-21
fleet:worker/cb584-8c2281-22
fleet:worker/cb600-e6b9a9-20
fleet:worker/cb602-ce257f-19
fleet:worker/cb601-b42837-18
fleet:worker/cb598-6c7ba7-17
fleet:worker/cb599-740fe4-16
fleet:worker/cb597-282224-15
fleet:worker/cb590fix-185e9a-10
fleet:worker/cb528-recovery-race
fleet:worker/cb594-96bead-8
fleet:worker/cb590-916766-2
fleet:worker/cb527-997d99-3
fleet:worker/cb592-env-leak-3cbf9c-1
fleet:worker/cb588-async-ticket-nudge-3218f7-5
fleet:worker/cb578b-9dcb13-6
fleet:worker/cb581-d24826-5
fleet:worker/m2-u5-ef8c42-15
fleet:worker/cb578a-516499-2
fleet:worker/cb576-01a04b-17
fleet:worker/cb579-lead-tab-acba06-20
fleet:worker/cb580-terminal-health-ed6058-21
fleet:worker/cb577-f36fdc-18
fleet:worker/cb573b-3db06f-16
fleet:worker/cb568c-f36fdc-18
fleet:worker/cb568-drop-cause-c3ac1c
fleet:worker/cb575-cancelled-notification-c3ac1c
fleet:worker/m4-sol-a2cbec-3
fleet:worker/cb574-async-ask-c3ac1c
fleet:worker/cb573-health-model-8ca857-14
fleet:worker/cb572-unknown-target-7f2e35-13
fleet:worker/u4-700706-9
fleet:worker/u3-b9fcb6-6
fleet:worker/u2-ef5b68-4
fleet:worker/u1-469dce-1-clean
fleet:worker/u1-469dce-1
fleet:worker/cb-564-health-events-70cf7e-2
fleet:worker/cb-565-recycle-drops-role-98e58f-3
fleet:worker/cb-563-missing-reply-df2866-1
fleet:worker/cb-562-readiness-gate-silent-6c23c9-3
fleet:worker/cb-560-architect-presence-da8155-1
fleet:worker/cb-561-architect-silent-off-a71cab-2
fleet:worker/cb-548-bind-architect-slot-fe1b8c-1
fleet:worker/parity-overlay-settings-5fb711-1
fleet:secrets-central-store
fleet:cb-559-hot-key-correction
fleet:cb-557-fleet-role-pools
fleet:worker/cb-553-maxload-explicit-spawn-305ee3-6
fleet:worker/cb-551-idle-lead-heartbeat-f1633c-1
fleet:worker/cb-544-drain-preserves-worktree-925fad-3
fleet:worker/cb-552-docs-sync-1cb9cf-4
fleet:worker/cb-548-rendezvous-guard-rebased
fleet:worker/cb-548-rendezvous-guard-116b53-10
fleet:worker/cb-548-authz-v2-586df6-8
fleet:worker/cb-548-authz-264363-5
fleet:salvage/cb-528b-codex-home
fleet:salvage/cb-528a-codex-launcher
fleet:CB-518-primary-flow
fleet:feature/peer-launcher-spi
fleet:cb-103-injector
No Reviewers
Labels
Clear labels
blocked
needs-live-proof
ready-to-delegate
silent-default
Cannot start until something else lands. The body says what.
Merged and green, but never shown working on the running daemon. Not the same as done.
Scope, files and acceptance criteria are written. A worker can be briefed from the body alone.
A feature that compiles, passes tests, and ships turned off. Nine recurrences and counting.
No Label
Milestone
No items
No Milestone
Projects
Clear projects
No project
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: fleet/fleetd#457
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "worker/446-hot-exhausted-pattern-0af580-6"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes fleetd #446: the model gate can be turned off at runtime, but its usage-limit detector could not be turned on at runtime.
What changed
exhaustedPatternis now HOT. NewLiveExhaustedPatterns(fleetd/src/main/java/dev/ltms/fleet/inject/LiveExhaustedPatterns.java) reads the pattern off the live config supplier per lookup, instead ofFleetd.maincompiling it once into a frozenMap<String,Pattern>at startup. CompiledPatterns are cached by profile name, not pattern text — pattern text would grow unboundedly as an operator tunes a regex across several reload cycles; profile names are bounded by the small, restart-gated set of configured profiles (adding/removing a profile is itself a deferred key).patternFor()backsCompletionResolver's classification;armed()backsfleet_profiles'exhaustionDetectionArmed— both read the SAME object, the fleetd #404 single-accessor ruleCompositePeerLauncher.modelGateState()'s javadoc states for the model gate's own armed/off pair.WARNING names the fix. In
Fleetd.java, on a BACKEND_EXHAUSTED detection, a new log.warn names the profile's model and the exact edit (enabled: falseon that model'smodels.allowentry — hot, no restart — remove it again once the window resets), or, when the profile has nomodel:configured, says quarantine alone is holding it off.Observable in
fleet_profiles.FleetMcp.QuarantineSourcegainsmodelFor/reasonFor; a quarantined profile's row infleet_profilesnow carriesmodelandreasonfields, so a lead can see why a limit hit and which model it points to without reading the daemon log.fleet_list'scapacityViewis intentionally NOT touched — scoped tofleet_profilesonly.Deliberately not done (per ticket)
enabled: false, sincemodels:is already hot.exhaustedPattern/errorPatternvalues added to any profile (fleetd.yamlis gitignored/live and untouched).BackendQuarantineitself is untouched (~15+ existing test call sites use its 1-argquarantine(credentialId)); the exhaustion reason is tracked in a new side-channelMap<String,String> quarantineReasonByCredentiallocal toFleetd.java, populated at the same call site as the existingquarantine.quarantine(...)call.errorPatternstays Deferred on purpose — out of scope.Tests
LiveExhaustedPatternsTest(new): direct unit proof of hotness + caching (read, mutate the backing config, read again, see the change;assertSame/assertNotSameprove the cache hits on unchanged text and recompiles on changed text).FleetdExhaustionDetectionArmedWiringTest(rewritten): builds oneQuarantineSourceBEFORE aConfigRef.reload(), re-invokes the SAMEexhaustedPatternArmed()function AFTER the reload (no new object), asserts the answer flips false→true with no restart. This is the inverse of the pre-#446 version of this file, which assertedassertFalseon the identical scenario.ConfigRefTest/ConfigRefProfileCoverageTest: updated forexhaustedPatternmoving from Deferred toLAUNCH_SETTINGS_EXCLUDED/Hot.Mutation proof (criterion 1): reverted ONLY the compile-site fix —
LiveExhaustedPatternssnapshottingprofiles.get()once at construction instead of reading it live per call, simulating the pre-#446Fleetd.mainstartup compile — and re-ranLiveExhaustedPatternsTest+FleetdExhaustionDetectionArmedWiringTest: 5 failures (Tests run: 10, Failures: 5,BUILD FAILURE). Restored the fix: same two classes back to 10/10 green,BUILD SUCCESS.Full suite:
mvn -B clean test→Tests run: 1586, Failures: 0, Errors: 0, Skipped: 0,BUILD SUCCESS.Scope note
Swept for other fields with the same shape as #404/#446 (a reported/observable state computed from a different source than the enforced behaviour) — grepped
FleetMcp.javaandCompositePeerLauncher.javaforarmed/Armed. Found only the two already-known pairs (modelGateArmed#404,exhaustionDetectionArmed#446), both now following the single-accessor rule. No new instances found in this quick sweep; not an exhaustive audit of the whole config surface.Follow-up (commit
30d6872): pin criteria 2 and 3 with mutation-tested assertionsThe lead ran a mutation battery against the merged PR (d02dd1b) and found criterion 1
solidly pinned, but criteria 2 and 3 shipped correct and untested: deleting
row.put("model", model)orrow.put("reason", reason)inFleetMcp.profilesView,or renaming the
"usage-limit fix:"log tag, all left the full 1586-test suite green.What changed
exhaustionSinkWARNING text out of two inline SLF4J{}-placeholderlog.warncalls into two static methods —Fleetd.usageLimitFixWarning(profile, model)and
Fleetd.usageLimitFixWarningNoModel(profile, quarantineCooldownSeconds)— the sameextracted-static-method idiom as
exhaustedPatternCoverageLine/errorPatternCoverageLine.log.warnnow takes each method's return value as a single, already-formatted argument, soa test asserts on the exact string
fleetd.outreceives. No behaviour change: same text,same two branches, same call site — proved by reverting the extraction (
git diffemptyvs. committed content) and confirming both formatted strings identical before/after.
FleetdUsageLimitFixWarningTest: pins the leading"usage-limit fix:"grep tag plusthree specific facts (profile name, model name,
enabled: falseundermodels.allow) —not the whole sentence — and the no-model fallback branch.
FleetProfilesQuarantineModelReasonFieldsTest: exercisesQuarantineSourcewith amodelFor/reasonForthat actually return values (every prior test used.none()or the3-arg form defaulting both to
null), assertingmodel/reasonare present when supplied,and absent (not
null, not blank) when the functions returnnullor a blank string.Break-and-restore proof (all three, verbatim)
M1 — delete
row.put("model", model);→FleetProfilesQuarantineModelReasonFieldsTestfails:Restored →
Tests run: 3, Failures: 0,BUILD SUCCESS.M2 — delete
row.put("reason", reason);→ same test fails:Restored →
Tests run: 3, Failures: 0,BUILD SUCCESS.M4 — rename
"usage-limit fix:"to"MUTANT no fix named:"at both call sites →FleetdUsageLimitFixWarningTestfails:Restored →
Tests run: 3, Failures: 0,BUILD SUCCESS.Full suite after restore
mvn -B clean test, redirected to a file (not piped), exit code checked separately from theoutput:
exit=0,Tests run: 1592, Failures: 0, Errors: 0, Skipped: 0,BUILD SUCCESS(1586 prior + 6 new).
Log-pinning idiom chosen: extracted static method returning a
String, notListAppenderChose the
exhaustedPatternCoverageLineidiom overExhaustedPatternGapReportTest'sListAppendercapture. This warning is built once, at one call site, from a single method withno branching inside the message itself — there's no real log plumbing (multiple call sites,
conditional formatting inside the logger call) left to prove once the text is extracted, so an
appender would only add setup/teardown around the identical assertion the extracted method
already gives for free.
Round 3 (final round)
Round 2's mutation battery (M4a, M1, M2) was re-run against the merged PR and all three stayed
KILLED. A new mutant, M5, SURVIVED: replacing
exhaustionSink'slog.warnternary result with aliteral string left the full 1592-test suite green, because nothing exercised the real call site
that builds the quarantine
ExhaustionSinkmain() wires — only the two extracted static methodsin isolation (
FleetdUsageLimitFixWarningTest).Fix: extracted the inline lambda in
main()into a new staticFleetd.exhaustionSink(...)factory (same testability-refactor class the lead approved in round 2), behaviourally unchanged.
Added
FleetdExhaustionSinkWarningTest, which drives this factory's return value directly andasserts on the real text a
ListAppenderattached toFleetd's own logger captures — chosen overa source-text assertion (the
FleetMcpAuthzTestidiom) because it answers both of the two gaps thelead named from one mechanism, by reading what the sink actually logged for each shape of profile:
(ternary result →
"usage-limit fix: MUTANT"). Full clean suite:Tests run: 1594, Failures: 1, Errors: 0, Skipped: 0,BUILD FAILURE, exit=1 — failure atFleetdExhaustionSinkWarningTest.profileWithModelGetsTheActionableFix:140. Restored; full cleansuite:
Tests run: 1594, Failures: 0, Errors: 0, Skipped: 0,BUILD SUCCESS, exit=0.clean suite:
Tests run: 1594, Failures: 2, Errors: 0, Skipped: 0,BUILD FAILURE, exit=1 — bothnew test methods independently failed (the with-model case got the no-model fallback text and
vice versa). Restored; full clean suite:
Tests run: 1594, Failures: 0, Errors: 0, Skipped: 0,BUILD SUCCESS, exit=0.Kept the round-2 extraction (
usageLimitFixWarning/usageLimitFixWarningNoModel) and its testunchanged, per the lead's instruction — M4a proved they still earn their place.
Files changed this round:
fleetd/src/main/java/dev/ltms/fleet/Fleetd.java(extraction only, nobehaviour change),
fleetd/src/test/java/dev/ltms/fleet/FleetdExhaustionSinkWarningTest.java(new).Merged as
1fb6176. Closing this PR by hand: the merge was made locally with--no-ffand pushed, which did not trip the forge's auto-close.Proof the branch is in
main, not just asserted:git merge-base --is-ancestor origin/worker/446-hot-exhausted-pattern-0af580-6 origin/mainsucceeds, with branch tip7772b41. A control branch that is not merged correctly fails the same test.Full verification, the three mutation cells (M5 killed, M6 killed, M7 survived) and the build on the merged tree —
Tests run: 1601, Failures: 0,BUILD SUCCESS— are on #446, now closed. M7's residue is recorded on #460.Pull request closed