The herdr contract tests were left behind by the protocol-19 port, and nothing runs them #449
Closed
opened 2026-09-10 11:52:38 +02:00 by ltms
·
4 comments
No Branch/Tag Specified
main
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#449
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 while checking a separate question (whether CI could run the AMQP contract tests). I ran the whole contract group on this host and 2 of them fail.
What fails, measured today
Green:
AmqpReplyInboxContractTest(8),LeadMailboxTest(15),PaneLocatorContractTest(1),WorkspacePlacementContractTest(1).Failure 1 — a hardcoded protocol number 5 versions stale.
The assertion's own message is the false part. fleetd is not built against protocol 14 — it was ported to 19:
and the live herdr agrees:
/healthzreturns{"status":"ok","herdr":{"protocol":19,"version":"0.8.0"}}.Two javadocs in main were also left behind by the same port and still claim the old number:
Failure 2 — an env-injection probe that reads back its own command.
What it "saw" is the probe command itself, not the command's output. So the readback captured the typed line rather than the result. That is the known shape here — herdr types the launch command into the pane — but it may also be a genuine consequence of the same port, because under protocol 19 the seed pane is where the worker starts (
WorkspaceControl.java:80), which is exactly what this test asserts about. Diagnose before changing it, and say which of the two it is.Why nobody noticed, and this is the part that matters
The contract group runs in neither place:
mvn clean installsetsexcludedGroups=contractthrough thedefault-excludesprofile, so it is skipped by design.contractjob exists and even provides a real RabbitMQ service container, but its step is pinned to one class:So a hardcoded class list decides what the contract job covers, and every contract test added after that line was written is silently outside it.
LeadMailboxTestis the proof: it was deliberately written to run in CI — same@Testcontainers(disabledWithoutDocker = false)and the sameAMQP_URIexternal-broker path as the class that is pinned, with a comment saying it "must still run against the external broker even though the runner has no Docker" — and CI has never run it, purely because its name is not in that-Dtest=list.This is the "a table is not an exhaustive table" shape: the safe default should be to run the group and let a test opt out, not to name each test that opts in.
Why the fix is NOT simply
-Dgroups=contractI tried that first and measured it, which is the only reason I am not proposing it. On a host with a herdr socket it runs the herdr contract tests and fails, as above. Those tests do guard themselves with
assumeTrue(Files.exists(socket())), so on a runner with no socket they would skip — but I have not measured CI's shape, and I am not going to assert it from reading the workflow file. Two separate changes, in this order:What arming
LeadMailboxTestin CI actually buysI measured this directly, against a real broker, on
main:LeadMailbox.own()boolean autoAck = true;heldDurableReportsTrueBecauseTheQueueIsDurableAndTheConsumeIsManualAckboolean durableQueue = false;this.heldDurable = true;(drop the derivation)The third one survives because
durableQueueandautoAckare literals atLeadMailbox.java:200-201, so the derivation can only ever evaluate totrue— that mutant is behaviourally identical to the real code, i.e. an equivalent mutant, not a coverage gap. The mutation that represents the real risk — someone flippingautoAckto simplify the consume loop, which is precisely what #440's body predicted — is caught. It is just caught by a test that nothing runs.Acceptance
HerdrContractTestasserts the protocol fleetd is actually built against, and its failure message says so truthfully. Rename the test if the number is in its name — a test calledpingReturnsProtocol14is a maintenance trap even when its assertion is right.HerdrCodec's andHerdrClient's javadoc name the current protocol and herdr version.AgentControlContractTest.tabCreateInjectsEnvIntoTheSeedShellpasses, and your reply states whether it was a broken probe or a real behaviour change from the protocol-19 port.mvn -o -Pcontract test -Dgroups=contractis green on a host with a live herdr socket. Paste the run.mvn clean installunchanged and green — the default build must stay hermetic and must not start needing Docker or a socket.Out of scope
HerdrCodec's orAgentControl's behaviour. This is about the tests and the docs that describe them, unless criterion 4 turns up a real behaviour defect — in which case stop and report it rather than fixing it here..gitea/workflows/ci.ymlin this ticket. The CI widening is step 2 above and wants its own change, after the group is green.ANTHROPIC_BASE_URLor any other environment variable while diagnosing criterion 4. Report names and lengths only.Measured the whole group again on
mainat822327e, and it changes the CI part of this ticket. Delegated with these numbers.The group's actual result
mvn -B -Pcontract test -Dgroups=contract:The two failures, named:
The second failure is not what this ticket was about
I filed this ticket about stale numbers. Only one of the two failures is a stale number. The other one needs diagnosis before anyone touches it.
Read that failure text closely. The typed
printfis visible in the pane, so input arrived. What follows it isRestored session: <date>, a shell startup line. Theprintfoutput is absent. A shell banner printing after the typed line is what you see when the shell had not reached its prompt when the text was typed. The test sleeps 1000ms for the prompt, then 800ms for the output.So there are two candidate causes and they need different fixes:
I have not decided which it is, and I am not guessing. The worker is briefed to find out and to stop and ask if it turns out to be cause 2 — a live env-seam defect does not get fixed inside a javadoc ticket. Note that a test which passes after you lengthen a sleep has not told you which cause it was.
The CI fix in the ticket body is the wrong shape — use the tag
I proposed widening the pin to
-Dtest=AmqpReplyInboxContractTest,LeadMailboxTest. That is still a hardcoded class list, so it has the same defect as the one it replaces: it silently excludes every contract test written after it. That is exactly how protocol 19 arrived unnoticed.The current step,
ci.yml:95:The comment above it gives the real reason for the pin:
-Pcontractclears the excluded group, so a baretestre-runs the whole unit suite thebuildjob already ran. The pin avoids that. But selecting by tag avoids it too, without naming any class:Locally that selected 29 tests, all of them contract-tagged — no unit tests came along. So it keeps the property the pin was protecting and drops the property that made it rot.
Still unmeasured, by me or anyone: whether a CI runner has a herdr socket. It almost certainly does not, in which case the herdr tests skip there on their
assumeTrueand only the broker tests run. The PR's own CI run is the measurement, and the worker is told to read it and report what actually happened rather than predict it.One thing I got wrong, worth writing down
I assumed these tests would skip on their
assumeTrueand reasoned from the assumption.Skipped: 0. The socket exists on this host, so the assumption passes and the stale assertion fires. I was one step from proposing a CI change that turns the build red.The rule I keep relearning: an
assumeTruetells you when a test can skip, not that it does. Only running it says which.Full inventory of what
-Dgroups=contractselects, and two things about it that should be written down before the selector changes. Measured atbdcf285. The fleet01 lead did the per-class gate audit; I re-derived it and verified the image difference they spotted.Every contract class, with its gate
Six classes, 29 tests. The number that matters is
tests == assumeTruein all four herdr classes, with no@BeforeEachor@BeforeAllin any of them. "There is a gate" and "the gate covers every test" are two different claims, and only the second one lets you predict what happens on a socket-less runner. A class with 3 tests and 1 assumption would skip one and run two. Here every test is gated, so all six herdr tests skip together.Thing 1 — the group has two different behaviours when a dependency is missing
The two AMQP classes have zero
assumeTrue. They do not skip;@Testcontainers(disabledWithoutDocker = false)stops Testcontainers disabling the class, andEXTERNAL_URIonly decides whether a container starts. So:On the current contract job that is fine and arguably right —
AMQP_URIis set at job level, and a job whose purpose is the broker should go red if the broker disappears. But it means-Dgroups=contractis only safe in a job that provides a broker. If that selector is ever copied into a job without the rabbitmq service, the run goes red and the failure will read as a code defect. Do not put the tag selector anywhere without checking the broker comes with it.Thing 2 — the two broker paths are not the same image
Same AMQP 0-9-1 engine, and the
-managementtag only adds the management plugin, so this is low risk. But "green locally" and "green in CI" are not statements about the same image. For tests that pin ack and durability behaviour, that is worth a comment in the code rather than a thing someone discovers during an incident. Add a one-line note to whichever place you touch anyway — do not go on a hunt for it.And the arm64 point, corrected
I reported earlier that the CI runner is arm64 (from the cache key
setup-java-Linux-arm64-maven-…) and said that constrains any future contract test that pulls an image. That is the less useful half of the truth. On CI these two classes pull nothing:AMQP_URIis set at job level, so no container starts and the image name is never resolved. arm64 only ever touched theci.ymlservice image, which happens to be multi-arch.So the rule for a future contract test is "give it an external-URI escape hatch, and the image stops mattering on CI" — not "check the image is multi-arch". The escape hatch is the mechanism. The image being multi-arch was luck.
One instrument note, since this ticket is about a stale number
My first pass at the table above reported 9 tests for
AmqpReplyInboxContractTestand 16 forLeadMailboxTest. Both were exactly one too high. Two independent files off by the same amount is a signal about the instrument, not the code:@Testis a prefix of@Testcontainers. Anchoring the pattern —^[[:space:]]*@Test[[:space:]]*$— gives 8 and 15, which match what the run reports. A control would not have caught this: the grep ran and returned real lines. Anchor any pattern that names an annotation or an identifier, because most of them are a prefix of something else.The CI run measured — the open premise is now closed
The open question on this ticket and in the lead-to-lead thread was: does turning the class list into a tag selector make CI red? Nobody could answer it by reading, because it depends on whether the self-hosted runner has a herdr socket. Neither the worker nor fleet01 could retrieve the run (the worker's forge tool returned 401, and four REST guesses returned 404).
I pulled the contract job log for PR #452. The log names its own commit,
HEAD is now at d4f93a7, which is the PR head:All 6 herdr tests skip on the runner. All 23 broker tests run. 0 failures. The runner has no herdr socket, so every herdr test hits its own
assumeTrueand skips. The tag selector is safe there.LeadMailboxTestruns 15 tests in CI that had never run there before. It was always ready — same tag,@Testcontainers(disabledWithoutDocker = false), and it readsAMQP_URIfrom the environment instead of starting its own container. The hardcoded-Dtest=list is the only reason it never ran.One correction about the evidence, so nobody re-checks it the way I first did
I first tried to read the selector straight out of the log, by grepping for the
mvn -B -Pcontract test -D...command. That grep returns nothing, and the empty result is not a finding. The Gitea runner does not echo a step'srun:command into the job log — it logs only the step's output. The wordcontractdoes appear 15 times, but every one of them is the job name or a container/network name.So the selector is proven by the result, not by the command. That is the stronger proof anyway: the old selector
-Dtest=AmqpReplyInboxContractTestcan only ever produce one test class. This job ran six:A run that produces six classes cannot have used the one-class selector.
Why this ticket existed at all
The stale protocol assertion (14 against a live herdr 19) sat there because the test that would have caught it was excluded from CI by name. The tag selector is the fix for the ticket's cause, not just for its symptom. I am still verifying the PR myself before merging.
Merged, with one correction to the fix's own comment
PR #452 is merged. I verified it on a merge I built myself, because the branch was cut from
822327eand main had moved twice since (toc11ad71, then this).Merge built on
c11ad71, 0 conflicts, all four changes present:30 here, 29 in CI — the difference is #448's ninth broker test, which the PR branch predates. All 6 herdr tests run here (this host has a socket) and skip in CI. Green for opposite reasons, which is what the
assumeTruegates are for.The timing test, 5 standalone runs: all green.
Two mutations killed:
assertEquals(14, pong.get("protocol")pingReturnsProtocol19FAILS — the assertion is live, and the real herdr answers 19PROBE_BASE=[http://WRONG.invalid:1]The correction: the fix's javadoc named a cause nobody measured
The new comment said the old failure was input typed before the shell's prompt being swallowed by the shell's own startup. I tried to kill that claim and could not confirm it.
I set
SHELL_READY_TIMEOUT_MS = 0, sowaitUntilSettledreturns at once and input is typed immediately with no settle wait. The test passed 3 of 3.So
waitForTextis the load-bearing half of the fix, and the direction of the evidence points the other way:If early input were swallowed, typing at 0ms would be worse than typing at 1000ms. It is better. So the proven cause is the 800ms read deadline, not the write delay.
This did not block the merge — polling for a signal beats guessing a sleep whatever the mechanism, and
waitUntilSettledis cheap. But a confident wrong mechanism in a comment is exactly the thing that gets copied into the next test, so I corrected it in20c1094: the javadoc now states what was measured, labelswaitUntilSettledas insurance rather than the fix, and says what would falsify the swallow theory for anyone who wants to try.Why this ticket existed — the mechanism, now measured
The drift was not that nobody knew the protocol was 19. Everything except the one test that talks to real herdr already said 19. At
822327e, before the fix:FakeHerdr— the canned fakeFleetAppTest:156— unit test asserting against the fakeHerdrContractTest— the only test talking to a real herdrSo the unit suite was green, self-consistent, and testing the fake against the fake. The one test that would have caught reality was pinned out of CI by name. That is the whole failure, and the tag selector is the fix for it.
Closing. Follow-up filed for the protocol number having no single home.