adding a FleetConfig field must not silently drop it #357
Closed
agent
wants to merge 0 commits from
worker/withdefaults-guard-561704 into main
pull from: worker/withdefaults-guard-561704
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/446-hot-exhausted-pattern-0af580-6
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/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#357
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/withdefaults-guard-561704"
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?
Why
PR #355 (parked, not merged, per lead's instruction — the fleet is moving off macOS so the
idle-sleep workaround it built is no longer the direction) surfaced a live bug while I was adding
a component to
FleetConfig:withDefaults()'s finalreturn new FleetConfig(...)call was stillwritten at the pre-addition arg count, so it silently bound to the freshly-added back-compat
constructor at that arity instead of the new canonical constructor — the new field came back
nullfrom every
load(). My own new tests for that field caught it; nothing else in the suite did.The lead re-measured this independently on
mainand named the mechanism a "defect factory":the exact pattern this file uses to keep old callers compiling — add a component (record grows by
one arg) and add a back-compat constructor at the OLD arity — also lets that new back-compat
constructor swallow
withDefaults()'s own literal-arity call, because that call is now a legaloverload match too. It compiles. Every other test passes, because nothing else exercises the new
field. The new component is defaulted away, silently, on every load. This has fired once already
(the sleep-guard branch) and will fire again on the next config key — which matters more right now
because a new Linux host is about to get a hand-written
fleetd.yaml.Goal: make it impossible for a component to be added to
FleetConfigand silently not survivewithDefaults().Scope:
fleetd/src/main/java/dev/ltms/fleet/config/FleetConfig.javaand its tests only. This PRdoes not add the
idleSleepGuardfield — that belongs to the parked PR #355 and does not ridealong here. This unit is built and tested against
mainas it stands today: 22 top-levelcomponents, back-compat constructors at 21/20/18/17/16/15/14 (re-verified fresh on this branch —
see "What I verified" below).
Mechanism chosen, and why
A reflective test, not a compile-time fix. New file:
fleetd/src/test/java/dev/ltms/fleet/config/FleetConfigWithDefaultsPreservesEveryComponentTest.java.It mirrors the exact reflective-construction pattern this file's ecosystem already uses in
ConfigRefTopLevelReportingCoverageTest(FleetConfig.class.getRecordComponents(), thengetDeclaredConstructor(exact component types)to resolve the TRUE canonical constructor — the sameway Jackson resolves it, never by argument count):
FleetConfigthrough that true canonical constructor, giving every one of the 22top-level components a real, distinctive, non-null value (non-blank for
placement, the oneString whose blankness has meaning).
withDefaults().Why this is a valid check for every component:
withDefaults()'s own comments document that itonly ever replaces a component when the incoming value is
null(or blank, forplacement) —broker/primary/leadHeartbeat/configReload/coordinator/worktreeGroup/memberLoginShellare passed through unconditionally, and
bind/guard/lifecycle/auth/fleet/quarantineCooldownSeconds/memberCredentials/placementare replaced only on null/blank input.A value that's never null or blank going in must never change coming out — for every current
component, with no exceptions.
Why not the compile-time route the brief also offered as a candidate: stopping
withDefaults()from binding to a back-compat overload at all, without deleting any back-compat constructor, would
need either a code-generation/annotation-processor step (out of proportion to one file) or routing
the call through some indirection that still has to be updated by hand every time a component is
added — which is exactly the same "a human has to remember" failure this bug already demonstrated
once. The reflective test instead makes the omission fail loudly, which is what constraint 2
asks for, and it does so using a pattern this codebase has already reviewed and trusted once.
Never hardcodes the arity: the test enumerates
COMPONENTS.lengthat runtime, so it keeps workinghowever many components the record grows to (constraint 3). No back-compat constructor is touched,
deleted, or restructured (constraint 1).
Exclusion list
EXCLUDED_FROM_SURVIVAL_CHECKis declared, and empty. Given every component a real, non-null(non-blank where relevant) value, all 22 current components are documented to survive
withDefaults()unchanged, so none needs excluding today.It's still declared, and its size is pinned by its own test —
exclusionListSizeIsPinned()assertsEXCLUDED_FROM_SURVIVAL_CHECK.size() == 0— so a futurecomponent that
withDefaults()is documented to transform unconditionally (unlike any fieldtoday) has one obvious, justified place to go, and growing that set to make a failure go away is a
visible diff to a pinned assertion, not a silent one. This is the "print the denominator" and "pin
the escape hatch" requirement from the brief, applied to a currently-empty case.
The coverage line the test itself prints, from a clean run:
Mutation proof
Copied
FleetConfig.javaaside withcp(nevergit checkout --) before mutating, and restoredfrom that copy afterward (verified byte-identical with
diff— see below).Mutated
withDefaults()'s final constructor call to dropworktreeGroup(a real, non-excludedcomponent — the exclusion list is empty, so there is no excluded component to test the negative
case against; see "What the empty exclusion list means for coverage" below):
Ran the FULL suite unpiped (
mvn clean install). New test failed, by name and line, naming thedropped component:
Same mutation also failed three pre-existing tests that already happened to name
worktreeGroupspecifically (
ConfigRefTest.changingWorktreeGroupIsReportedAsDeferred,FleetConfigTest.absentWorktreeGroupSurvivesTheBackCompatConstructorChain,FleetConfigTest.worktreeGroupKeyParses) plus three downstream tests whose behaviour depends onworktreeGroupreaching them (HerdrPeerLauncherAllowListWiringTestx2,ClaudeCodeLauncherTest/OpenCodeLauncherTestx3 as errors). That's expected —worktreeGroupalready has hand-written behavioural coverage elsewhere in the suite. It does not diminish this
test's purpose: the point of a reflective, component-enumerating test is to catch the next
component too, the one nobody happens to write a behavioural test for — which is exactly what
bit the sleep-guard branch, and exactly what six of the eleven
DEFERRED_KEYSmembers inConfigRefTopLevelReportingCoverageTest's own history (fleetd #337) turned out to be.Full mutated run:
Tests run: 1374, Failures: 6, Errors: 3, Skipped: 0/BUILD FAILURE.Restored from the
/tmpcopy and confirmed byte-identical:What the empty exclusion list means for coverage
The brief asks to also confirm the mutation does not fail for a component legitimately on the
exclusion list, and to say what it means if it doesn't. My exclusion list has no members, so there
is no such component to run that check against. What that means concretely: this test currently
checks all 22 top-level components with no exemption — the strongest form of that
guarantee, not a weaker one. If a future component genuinely needs excluding (because
withDefaults()is documented to transform it unconditionally, unlike anything today), adding itto
EXCLUDED_FROM_SURVIVAL_CHECKwill require bumpingexclusionListSizeIsPinned()'s pinned countin the same change, which is exactly the visible-diff requirement the brief asks for.
Shape report (read-only — nothing below was changed)
Is
FleetConfigthe only record in this codebase with a back-compat constructor ladder plus awithDefaults()-style rebuild method that calls its own constructor with a literal argument list?No — two more instances of the same shape exist, both untouched by this PR:
dev.ltms.fleet.config.FleetConfig.Profile(nested record, same file). Canonical 26-argconstructor, 7 back-compat constructors at older arities (documented "Backward-compatible
constructor" javadoc, same convention as the outer record), and its own rebuild method
withProfile(String p)— a literal 26-argnew Profile(...)call. Same shape, same latent risk,as of today it happens to be correct (matches the 26-arg canonical), same as
withDefaults()wascorrect at 22 args before the sleep-guard branch. Not fixed here — out of the stated scope
(
FleetConfig.java's outer record andwithDefaults()only).dev.ltms.fleet.session.MemberSession(fleetd/src/main/java/dev/ltms/fleet/session/MemberSession.java).Canonical 15-arg constructor, 2 back-compat constructors (12-arg "no charter receipt/agent
session id", 14-arg "no failure reason") — and five internal rebuild ("wither") methods —
withState,withActivity,bumpTurn,withAgentSessionId,withFailureReason— each endingin a literal 15-arg
new MemberSession(...)call. This is the closest sibling toFleetConfig.withDefaults()'s shape found in the codebase: five separate call sites, not one,each of which would silently rebind to a new back-compat constructor and drop a 16th field the
same way
withDefaults()did. Not fixed here — out of scope.No other record in
src/main/javacombines a back-compat constructor ladder (more than oneconstructor beyond the canonical, added to preserve an older arity) with an internal method that
rebuilds an instance of that same record via a literal-argument constructor call. Checked every
public recorddeclaration insrc/main/java(SpawnRequesthas a 2-rung back-compat ladder butno internal rebuild call; every other record has at most one convenience constructor, not a ladder,
or no extra constructor at all).
What I verified fresh on
mainbefore implementingMatches what the lead independently measured and reported in the brief.
Build
Unpiped. Final result:
(1372 on
main+ this PR's 2 new tests —exclusionListSizeIsPinnedandeveryComponentGivenARealValueSurvivesWithDefaults— = 1374.)Rules honored
git add -A— staged only the one new test file explicitly.git stashused..mcp.json,opencode.json,.autoenv,wiki/untouched.${VAR:+yes}).origin/main(b6b88c5), not from the parkedworker/sleepguard-82076d-1branch.worker/sleepguard-82076d-1or PR #355.git diff main -- fleetd/src/main/java/dev/ltms/fleet/config/FleetConfig.javais empty; the only change in this PR is the new test file.Pull request closed