No validator compares a lead's exact tab to a member label — the collision guard checks the vestigial tabPrefix #677
Closed
opened 2026-10-03 20:59:19 +02:00 by ltms
·
2 comments
No Branch/Tag Specified
main
worker/702-4f5c7f-2
worker/715-5c43fc-1
worker/721-70f9ea-5
worker/718-99362b-2
worker/task-15-af0d10-12
worker/task-16-50a702-13
worker/task-12-4d0479-9
worker/task-13-823ce2-10
worker/705-ticket-owner-af9928-8
worker/703-list-collaborators-9c06c2-7
worker/669-example-truth-0b303d-6
worker/669-collab-deliverability-9ba859-3
worker/669-collab-reload-report-2a21bd-4
worker/669-7e80a6-1
worker/669-unit-d-efbbd7-1
worker/669-1b786a-1
worker/669-1d1d9f-1
worker/692-4afb9d-2
worker/689-02fced-13
worker/693-cf23fa-14
worker/677-fix-lead-collision-f69073-12
worker/638-fix-overmask-dbb1bf-11
worker/675-5b7478-4
worker/669-unit-a-70cc8f-3
worker/677-8cdaaf-5
worker/638-a7b391-1
worker/683-4536d6-2
worker/651-a75bbe-8
worker/680-20607d-7
worker/664-c12e95-3
worker/668-08534d-4
worker/672-0f2469-2
worker/670-7d1022-1
worker/661-ac7c28-2
worker/664-37fb9b-3
worker/663-remove-3arg-read-3f6783-1
worker/659-remove-dead-backcompat-ba5e6f-1
worker/637-revision-60a488-23
worker/656-redact-regression-tests-892903-19
worker/637-context-gauge-threshold-466eb5-16
worker/639-redact-line-numbers-de4ac4-17
worker/641-set-reformat-guard-6f96a4-18
worker/642-herdr-guard-scope-5de0e4-15
worker/650-javadoc-scope-95f3b3-14
worker/612-01e9f7-13
worker/612-a-r4-quarantine-outage-7ab0e8-5
worker/612-a-r9-r11-capacity-coverage-peers-cfcc79-7
worker/612-a-r10-loophealth-ccc872-8
worker/612-a-r12-turnregistrar-9e3bb7-9
worker/612-a-r5-leadconfigdir-9e70cf-6
lead/config-edit-redact-anchor-wording
worker/config-edit-seam-ca8dc1-1
worker/612-r67-630-lifecycle-290b8d-3
worker/629-625-ports-seams-da7d5d-4
worker/612-r12-exhaustion-f37cd7-1
worker/612-r38-amqp-24b083-2
worker/fleetd-612-unita-87807e-1
worker/612-b3-mcpwirings-da2b58-3
worker/612-b2-cb185-176d3a-2
worker/612-b1-completion-457459-1
worker/612-agaps-73a926-2
worker/608-sleeps-3a64ff-3
worker/621-b4520b-1
worker/618-b83894-2
worker/fleetd-615-e05481-5
worker/lead-autocompact-5f1ab2-3
worker/fleetd-613-f85deb-3
worker/fleetd-608-flaky-nudge-test-d0c2d1-3
worker/lead-context-gauge-ad404f-1
worker/gauge-wiring-9158c1-4
worker/redeploy-slowstart-ead0e5-5
worker/charter-bytes-13668c-6
worker/rollover-outcome-291483-2
worker/589-f64303-2
worker/593-1a8025-5
worker/589-fcd2aa-1
worker/568-9fdaa2-3
worker/571-attempted-outcome-5739f7-2
worker/581-completionresolver-cas-sites-0542b7-6
worker/562-loop-health-wiring-test-99611c-5
worker/562-surface-loop-health-7df5cc-4
worker/575-waiter-cleanup-sites-62ad80-1
worker/572-answer-lock-release-46a9ae-5
worker/567-probe-channel-leak-a38fc5-6
worker/551-record-before-send-7cbf56-1
worker/561-listener-fanout-survives-a-throw-61d538-2
worker/555-redeploy-main-flow-seam-65c2f5-2
worker/556-injector-owns-registration-e027a5-1
worker/552-post-restart-mktemp-abort-bc2672-4
worker/553-onstatus-completion-leak-0da881-2
worker/550-shasum-linux-196132-1
worker/538-loop-dies-on-error-4a5eeb-6
worker/426-health-coverage-ef1fd4-4
worker/504-failed-reported-clean-3cfd66-3
worker/537-capturedlog-close-e4c437-2
worker/459-broken-link-targets-cadc17-5
worker/535-appender-leak-fe74c1-1
worker/512-part2-shutdown-detection-434701-9
worker/529-logger-level-sweep-2a5533-8
worker/528-drain-gate-call-site-5de83d-7
charter/forge-mcp-vs-token
worker/521-swap-guard-unpinned-28e931-5
worker/519-probe-test-harness-d25ab8-4
worker/525-logger-level-leak-1b4eb0-6
worker/518-fleetmcp-resolver-wiring-8ef96c-1
worker/512-drain-complete-line-7edd71-3
worker/517-abort-branch-and-jar-id-41b641-2
worker/500-9e52c9-3
worker/509-4912f4-2
worker/511-9a4b23-1
worker/493-479f45-2
worker/505-03f8b2-1
worker/492-followup-detect-unclear
worker/501-a31fa0-7
worker/498-451d1c-5
worker/494-1015ce-2
worker/492-209647-1
worker/489-001902-2
worker/480-relative-handover-path-906323-1
worker/480-b-handover-skill-45bf1f-5
worker/474-followup-source-pin-f54a55-17
worker/474-charter-check-on-reload-f54a55-17
worker/466-quarantine-repeatcount-report
worker/393-opencode-skill-seeding-71854b-13
worker/469-canonical-tool-names-2a472a-16
worker/466-quarantine-escalation-5ae9c1-15
worker/446-hot-exhausted-pattern-0af580-6
worker/464-charter-tool-name-guard-a85635-12
worker/463-listfleet-default-fails-open-f1c76c-11
worker/458-invariant-5-by-purpose-862f9a-10
worker/439-coordinator-row-gate-bc032a-8
worker/449-herdr-protocol-576015-4
worker/450-abstract-spawn-599e1c-5
worker/437-ack-refuses-177d91-1
worker/444-placement-window-feb56a-2
worker/440-helddurable-derived-d462d7-13
worker/425-rework-placement-resolve-c58ba1-9
worker/421-lead-peek-held-msgs-cdbad2-10
worker/435-fixed-policy-cap-fe11de-12
worker/422-gate-state-observability-9e79d6-11
worker/431-memberregistry-live-readers-cdbad2-10
worker/424-architect-slot-hot-038b41-7
worker/422-model-gate-spawn-c29f48-6
worker/425-default-profile-live-f55534-8
worker/415-coverage-wording-2cbf9c-5
worker/416-3ad1da-1
worker/418-588283-3
worker/deterministic-stamp-race-409-3cb7b6-10
worker/armed-reads-live-config-404-ed931f-9
worker/reply-peer-refusal-391-5a34bd-7
worker/models-allowlist-aa9e9b-3
worker/ttl-stamp-race-399-f1122f-8
worker/scrub-receipt-400-316b3e-5
worker/exhaustion-detection-395-105105-6
worker/scrub-abort-394-316b3e-5
fix/scrub-uid-abort
worker/task-scrub-517574-2
worker/t386-clock-bd5b78-4
worker/t384-scrub-813790-5
worker/t381-cc-748314-2
worker/t373-336973-2
worker/t365-3920c5-3
worker/t358-6e989b-1
worker/t355-8b321c-1
worker/fleetd-369-hermetic-git-tests-e8b19a-3
worker/fleetd-368-stale-lead-binding-f5682e-2
worker/fleetd-360-deploy-units-0d3793-1
worker/359-dead-lead-tabs-f1253b-4
worker/362-worktree-skills-c03e51-3
worker/361-coord-visibility-655144-1
362-plugin-visibility-and-drift
worker/errscan-bed2ca-2
worker/amqp-log-identity-bed2ca-2
worker/withdefaults-guard-561704
worker/sleepguard-82076d-1
worker/fd334-9ee1b6-5
worker/fd348-f1ab27-4
worker/fd335-a71c35-1
worker/fd342-174a17-2
worker/fd345-490d0f-3
worker/fleetd-337-5ec7d4-21
worker/fleetd-341-af5a6b-24
worker/fleetd-339-5ca0a2-23
worker/fleetd-338-83a4a1-22
worker/fleetd-333-281f46-18
worker/fleetd-329-11bdbb-16
worker/fleetd-330-2770fb-17
worker/fix-326-50506e-15
worker/fix-324-3e9bbf-14
worker/fix-323-b8287d-13
worker/fix-316b-bd0860-11
worker/fix-318-76ca36-9
worker/fix-317-486aec-8
worker/fix-315-ce47c5-6
worker/fix-307-275890-6
worker/fix-308-b4f664-7
worker/fix-309-ec3939-8
worker/fix-310-7a3974-9
worker/fix-302-52ad0e-9
worker/fix-298-ce1acb-8
worker/fix-297-66bd11-7
worker/fix-296-104622-6
worker/fix-293-bare-closetab-eb22b5-3
worker/fix-280-gone-ask-lapse-bca98e-2
worker/fix-290-reapidle-guard-coverage-9b0dd1-1
worker/fix-285-trust-seed-8f3565-10
worker/fix-284-backend-error-seat-85912c-11
worker/fix-282-chained-ask-e6d0bb-8
worker/fix-283-teardown-leaks-f40dfa-9
worker/fix-281-pin-handler-actions-4921ac-7
worker/audit-rendezvous-lifecycle-d072ae-2
worker/audit-health-placement-1a2476-6
worker/audit-teardown-exits-e207a5-3
worker/audit-launcher-asymmetry-27e370-4
worker/audit-rest-authz-6ca53c-5
worker/investigate-275-abandon-asking-fdef52-8
worker/fix-274-worktree-leak-b0095d-7
worker/fix-273-exhausted-pattern-9665b5-6
worker/fleetd-267-model-check-bd8068-1
worker/fleetd-131-archunit-18b834-7
worker/fleetd-266-sshagent-rename-a014ff-6
worker/fleetd-184-uid-claim-8e1f31-4
worker/fleetd-184-warn-b381ee-10
worker/fleetd-184-docs-be1d12-9
worker/fleetd-257-9bf010-7
worker/fleetd-103-23a113-6
worker/fleetd-247-342356-5
worker/fleetd-116-04dea8-4
worker/fleetd-252-a830e0-3
worker/fleetd-111-7e8673-9
worker/fleetd-155c-f8ef4b-8
worker/fleetd-176-b928ca-3
worker/fleetd-249-7a7878-2
worker/cb248-composition-root-b-9acdf7-15
worker/cb148-envrc-default-fa6c82-12
worker/cb201-unit5-wiring-6c12e6-8
worker/cb241-fallback-echo-1175e9-11
worker/cb149-trust-dialog-2392a5-9
worker/cb134-148-overlay-visible-c9b986-10
worker/cb234-session-id-keyed-04e1fc-1
worker/cb201-unit3-nudge-abdf5c-6
worker/cb201-unit2-policy-c1102c-5
worker/cb201-unit4-outcome-a13bfa-7
worker/cb201-unit1-classifier-91b9b1-4
worker/cb201-227-refine-831980-3
worker/cb175-model-readback-0f085f-1
worker/cb222-charter-tmpdir-17f013-1
worker/cb226-architect-slot-race-cd3aa8-3
worker/cb224-worktree-root-group-024523-2
worker/cb-123-role-demotion-c600f7-2
worker/cb-219-opencode-roots-1f677e-1
worker/cb214-claude-session-id-b9eab4-4
worker/cb213-zdotdir-wrong-process-dd6de4-3
worker/cb211-exhaustion-classification-9546e0-2
worker/cb137-ambiguous-task-4df3d8-4
worker/cb209-agentsessionid-4dfdb6-2
worker/cb185-hostenvnames-2692b5-3
worker/cb206-opencode-sqlite-128718-2
worker/cb185-worktree-group-fc0c99-1
worker/cb-137-ask-ticket-e7760c-2
worker/cb-172-broker-uri-d36ae4-4
worker/cb-175-model-readback-76ead6-3
worker/cb-161-pane-ancestry-293510-1
worker/cb-164-rebase-885863-8
worker/cb-164-empty-scrape-false-success-1a80af-3
fix/cb-197-ticket-ttl-from-completion
worker/cb-189-remote-url-coverage-4692f3-1
worker/cb-185-blockers-027756-4
worker/cb-192-gap-log-11b631-2
worker/cb-633-fix-5f4396-3
worker/cb185-router-d6436d-3
worker/cb185-router-routing-gaps-9e9d33-3
worker/cb185-paneids-992586-2
worker/cb-633-allow-list-union-ed374b-1
worker/cb-157-credential-in-remote-url-496e44-2
worker/cb-641-health-herdr-evidence-8f1f54-6
worker/cb-640-health-msg-evidence-99c9cd-1
worker/cb-642-fleets-status-skill-bbbc40-5
cb-634-ide-mcp
worker/lead-comms-wiring-c014b9-7
worker/lead-mailbox-c19577-6
worker/autocompact-window-82bc2f-5
worker/cb-634-probe-18056f-4
worker/cb635-broker-urienv
worker/cb-632-config-retry-8e0efa-7
lead/cb-622e-claude-md
lead/cb-622-followup
worker/cb-622a-165dff-1
lead/cb-622d-opencode-mount
worker/cb-622b-717c67-2
worker/cb-622c-ab7759-3
worker/cb-617b2-20ca4b-3
worker/cb-617a-5c2f4a-1
worker/cb596-4e49ef-3
worker/cb586-10500c-1
worker/cb-606-b9343a-25
worker/cb604-1445f8-24
worker/cb582-477374-21
worker/cb584-8c2281-22
worker/cb600-e6b9a9-20
worker/cb602-ce257f-19
worker/cb601-b42837-18
worker/cb598-6c7ba7-17
worker/cb599-740fe4-16
worker/cb597-282224-15
worker/cb590fix-185e9a-10
worker/cb528-recovery-race
worker/cb594-96bead-8
worker/cb590-916766-2
worker/cb527-997d99-3
worker/cb592-env-leak-3cbf9c-1
worker/cb588-async-ticket-nudge-3218f7-5
worker/cb578b-9dcb13-6
worker/cb581-d24826-5
worker/m2-u5-ef8c42-15
worker/cb578a-516499-2
worker/cb576-01a04b-17
worker/cb579-lead-tab-acba06-20
worker/cb580-terminal-health-ed6058-21
worker/cb577-f36fdc-18
worker/cb573b-3db06f-16
worker/cb568c-f36fdc-18
worker/cb568-drop-cause-c3ac1c
worker/cb575-cancelled-notification-c3ac1c
worker/m4-sol-a2cbec-3
worker/cb574-async-ask-c3ac1c
worker/cb573-health-model-8ca857-14
worker/cb572-unknown-target-7f2e35-13
worker/u4-700706-9
worker/u3-b9fcb6-6
worker/u2-ef5b68-4
worker/u1-469dce-1-clean
worker/u1-469dce-1
worker/cb-564-health-events-70cf7e-2
worker/cb-565-recycle-drops-role-98e58f-3
worker/cb-563-missing-reply-df2866-1
worker/cb-562-readiness-gate-silent-6c23c9-3
worker/cb-560-architect-presence-da8155-1
worker/cb-561-architect-silent-off-a71cab-2
worker/cb-548-bind-architect-slot-fe1b8c-1
worker/parity-overlay-settings-5fb711-1
secrets-central-store
cb-559-hot-key-correction
cb-557-fleet-role-pools
worker/cb-553-maxload-explicit-spawn-305ee3-6
worker/cb-551-idle-lead-heartbeat-f1633c-1
worker/cb-544-drain-preserves-worktree-925fad-3
worker/cb-552-docs-sync-1cb9cf-4
worker/cb-548-rendezvous-guard-rebased
worker/cb-548-rendezvous-guard-116b53-10
worker/cb-548-authz-v2-586df6-8
worker/cb-548-authz-264363-5
salvage/cb-528b-codex-home
salvage/cb-528a-codex-launcher
CB-518-primary-flow
feature/peer-launcher-spi
cb-103-injector
v1.1.0
v1.0.0
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#677
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 "%!s()"
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?
Found by an architect working #669 and confirmed by me in the main clone at
6f27522. Pre-existing, and independent of #669 — this is about the lead namespace as it ships today.What the guard checks, and what identity uses
validateLeadTabPrefixes()exists to stop a member being labelled so that it reads back as a lead. Its own error message says so:But it compares against
leader.tabPrefix():Identity does not use
tabPrefix. It uses the exacttab.tabPrefixis vestigial —FleetConfig.java:1132-1136says so in its own words: "no longer used to find a lead's tab —tabis matched exactly. Its only remaining job is the startup collision guard." It defaults to"lead:".I checked every place
FleetConfig.javareads a leader's real tab:Line 2760 is the early return inside
validatePanePlacementAgainstLeadTabs()— it only asks whether any lead has a tab, never what it is. Line 2927 isvalidateMembers()checking a leader names a tab at all.Positive control, so the empty result above is not a broken pattern: the same file has 1 use of
tabPrefix(), andgrep -n 'public void validate' …lists all eight validators. I read each. None compares a lead's exacttabto a member label.The reachable configuration
tabPrefixdefaults to"lead:"."alpha"does not start with"lead:", sovalidateLeadTabPrefixes()passes. Nothing else looks. The daemon starts.A member labelled
alphathen occupies a tab whose exact label is a configured lead tab, andCallerResolver.resolve()checks the lead map before the worker fallback with noSessionManagerlookup:Principal.leader(...)isRole.PRIMARY, whichAuthz.java:72grantsSPAWN,STOP,DRAINandHANDOVER.Why #661 does not cover this
#661 added
validatePanePlacementAgainstLeadTabs(), which refusesplacement: panewhile any lead names a tab. That closes the route where a pane-placed member lands in a lead's tab. It does not look at labels at all, so aplacement: tabmember whose rendered label happens to equal a lead's exact tab goes straight through.I confirmed #661 changed no resolver code:
git diff --stat f288cee~1 b4b7cf5listsFleetConfig.java,LeadTabScanner.java,LeadContextGauge.java,LeadLauncher.javaand tests.CallerResolver.javais not among them.This is the one-way-gate shape again: a guard added after an incident closes the direction that incident came from.
Not exploitable on this fleet right now, and why that is not reassuring
I checked the live
fleetd/fleetd.yaml. All eight profiles useplacement: tab, and the currenttabLabeltemplate does not render to any configured lead tab, so no member is mislabelled today. The hole needs a specific operator配置 mistake to open — but it is a mistake nothing warns about, and the consequence is a worker holding primary rights.The fix
Add an exact-label collision check: refuse at startup when
fleet.tabLabel, or any profiletabLabeloverride, can render to a configured lead's exacttab. Placeholders in the template ({role},{profile},{n}) must be treated as wildcards, because the rendered value is what the scanner matches, not the template.Then decide what
tabPrefixis for. If the exact check supersedes it, remove it rather than leaving two guards where one is the real one — a reader who seesvalidateLeadTabPrefixes()reasonably assumes the collision case is handled.Acceptance criteria
Properties under a change, not names of constructs:
fleet.tabLabel(or any profile override) to a value that renders to a configured lead's exacttabmakesvalidateAll()refuse startup, and the message names both sides of the collision. Changing either side makes it start.validateAll()with no edit tovalidateAll()itself —invokeAllValidatorsis a reflective sweep. Add the case to the reachability enumeration inFleetConfigValidateAllTest, which #668 just made the single place for that.tabPrefixbehaviour either still holds or is deliberately removed. If removed, say in the commit message which line it used to pin and why that can no longer go wrong.Related, and the stronger fix
#669's Unit D proposes that
CallerResolverlook upSessionManagerbefore any tab map, and return a spawned member's own role without consulting lead or collaborator tabs. That would close this at the resolver, which is where it actually bites, and turn every config validator here into defence in depth. If that unit lands first, this ticket shrinks to the startup warning.Not verified by me
Whether the same gap exists for architect slot labels. I looked at the lead namespace because #669 took me there. I did not trace whether a member label can collide with an architect slot's tab.
Lead review of PR #686 — confirmed defect, high. Do not merge as is
PR #686 extends
validateLeadTabPrefixes()instead of adding a new validator. That choice is fine,and it means
validateAll()needs no change because its reflective sweep already reaches themethod. I checked the test deletion too and it is safe (see the note at the end).
But the validator misses the main case the ticket asked for.
What is wrong
I read
FleetConfig.java:2693-2721myself. The loop walksfleet.leaders(), and for each lead itcompares that lead's
tabandtabPrefixagainst two things only:fleet.tabLabel(), the member label templatetabLabel()overrideIt never compares one lead's
tabagainst another lead'stab. So two leads configured withthe same exact tab label pass validation.
That is the case this ticket exists for. The tab label is the only test that decides whether a
session in a pane is read back as a lead, so two leads sharing one tab is the collision that
matters most here. It is also the state that blocked #359.
Measured on the PR branch
I made a throwaway worktree on
refs/remotes/pr/686(cb4a686) and added my own test with acontrol:
Result of
mvn -o -Dtest=LeadAdjudicationProbeTest test:The control in the same run, two leads with distinct tabs, passed. So the validator does run
and does accept valid configs. The failure is the validator accepting the collision, not a broken
fixture.
I then removed my probe test, and
git statuswas clean.A reviewer I put on the validator found the same thing on its own, from the code, and rated it
high. We agree, and we reached it by different routes: it read the comparison set, I ran a config
through it.
What the fix needs
leader.tab()and refuses when two leads share one. Comparecase-insensitively, the same way
startsWithIgnoreCasealready does for prefixes. Keep thisindependent of the member-template checks, because it is a different relation.
tabthat collides with another lead'stabPrefix.My reading is that this should also be refused, but the ticket did not ask for it, so say which
way you went and why.
different tabs must load. Show both.
tabPrefixchecks and their tests green.On the deleted test lines — no problem found
I checked the 76 removed lines in
FleetConfigValidateAllTest.javamyself rather than taking thereport. 58 are comment lines and 3 are code. No
@Testand no assertion was removed, and the testmethod count is 7 before and 7 after. The 3 code lines are an assertion message reword and the
fixture changing from a template scenario (
tabLabel: "lead: {role} {profile}"/tab: "lead: opus")to a literal one (
alpha/alpha).The reachability property is still pinned. A reviewer mutated
invokeAllValidatorsto skipvalidateLeadTabPrefixesand the sweep test went RED withlead-tab-prefixes.yaml: validateAll() must refuse this config.One thing to keep in mind while fixing the above: the fixture swap moved this class's case from a
template collision to a literal one. That is acceptable for a reachability test, but please make
sure the template-vs-exact collision stays covered by a test in
FleetConfigTest.Fixed and merged. Closing.
PR #691 merged to
mainasbfee23a, pushed (7dec74f..bfee23a). It supersedes PR #686, which I closed unmerged — #691 carries #686's commit plus the exact-tab collision guard on top.Two things landed together:
templateCanRenderAs(...)— the replacement collision check this ticket asked for. A membertabLabeltemplate is refused when, treating{role},{profile},{model}and{n}as wildcards, it can render as a configured lead's exacttab. The oldtabPrefixcheck is kept as a second, weaker arm.validateLeadTabPrefixes()that refuses twofleet.leadersentries sharing one exacttab, case-insensitively, with its ownIllegalStateExceptionso each message stays true to its own relation.The delegated decision, and how it went
I told the worker to decide for itself whether a lead's
tabstarting with a different lead'stabPrefixshould also be refused, and to say which way it went. It chose not to refuse it, and I accept the reasoning:tabPrefixis never read byCallerResolver— only the exacttabdecides identity. The defaulttabPrefixis"lead:", and the codebase's own multi-lead fixtures use exactly that shared default with distinct tabs (FleetConfigTest.leadersBlockRegistersEveryPaneByNameuses"lead: opus-5.0"and"lead: gpt-sol-5.6"). Refusing that case would reject the normal, safe two-lead convention to guard against something that cannot be exploited, becausetabPrefixcarries no identity.My own reading before delegating was that it should also be refused. The worker's reason is better than mine and I changed my mind on the evidence, not on who said it.
Verified by me
Build, trial-merged in a throwaway worktree,
rm -rf target/surefire-reports,mvn -o clean install: 1945 tests, 0 failures, BUILD SUCCESS,exit=0, 173 report files as the control. 1942 + 1 + 2 = 1945. After merging,main's tree hash30c0fc51d8a3a6411c913acef97e681ff53c93bbequals the tree I built, so that green build covers exactly what landed.The guard, read not inferred. Sorted names, unordered pairs, null and blank tabs skipped, separate exception.
A mutation the worker did not run. It mutated the new condition to
false && …and saw RED — sound for that line. I mutatedequalsIgnoreCase→equalsand ranFleetConfigTest: 155 tests, 0 failures — the mutation survived. So the case-insensitivity is unpinned.I checked whether
equalsIgnoreCaseis even correct, and it is.LeadTabScanner.java:145keys the tab→name map ontab.strip().toLowerCase(Locale.ROOT)and:254looks up through the same lowercasing, so two tabs differing only in case collide at runtime and the mapputsilently overwrites one — exactly the harm this guard's message names. Correct, load-bearing, and untested. Filed separately as a coverage gap; it did not block the merge.What I did not do
I did not boot a daemon with two colliding lead tabs. "The daemon refuses to start" rests on a code reading plus a unit test on the validator, not on an observed boot failure.
#676 rode along in this PR but is not fully done — one acceptance item remains. It stays open, with my measurement on that ticket.