fleetd #705: close the REST ticket-poll door #716
Closed
agent
wants to merge 0 commits from
worker/task-15-af0d10-12 into main
pull from: worker/task-15-af0d10-12
merge into: fleet:main
fleet:main
fleet:worker/702-4f5c7f-2
fleet:worker/715-5c43fc-1
fleet:worker/721-70f9ea-5
fleet:worker/718-99362b-2
fleet:worker/task-16-50a702-13
fleet:worker/task-12-4d0479-9
fleet:worker/task-13-823ce2-10
fleet:worker/705-ticket-owner-af9928-8
fleet:worker/703-list-collaborators-9c06c2-7
fleet:worker/669-example-truth-0b303d-6
fleet:worker/669-collab-deliverability-9ba859-3
fleet:worker/669-collab-reload-report-2a21bd-4
fleet:worker/669-7e80a6-1
fleet:worker/669-unit-d-efbbd7-1
fleet:worker/669-1b786a-1
fleet:worker/669-1d1d9f-1
fleet:worker/692-4afb9d-2
fleet:worker/689-02fced-13
fleet:worker/693-cf23fa-14
fleet:worker/677-fix-lead-collision-f69073-12
fleet:worker/638-fix-overmask-dbb1bf-11
fleet:worker/675-5b7478-4
fleet:worker/669-unit-a-70cc8f-3
fleet:worker/677-8cdaaf-5
fleet:worker/638-a7b391-1
fleet:worker/683-4536d6-2
fleet:worker/651-a75bbe-8
fleet:worker/680-20607d-7
fleet:worker/664-c12e95-3
fleet:worker/668-08534d-4
fleet:worker/672-0f2469-2
fleet:worker/670-7d1022-1
fleet:worker/661-ac7c28-2
fleet:worker/664-37fb9b-3
fleet:worker/663-remove-3arg-read-3f6783-1
fleet:worker/659-remove-dead-backcompat-ba5e6f-1
fleet:worker/637-revision-60a488-23
fleet:worker/656-redact-regression-tests-892903-19
fleet:worker/637-context-gauge-threshold-466eb5-16
fleet:worker/639-redact-line-numbers-de4ac4-17
fleet:worker/641-set-reformat-guard-6f96a4-18
fleet:worker/642-herdr-guard-scope-5de0e4-15
fleet:worker/650-javadoc-scope-95f3b3-14
fleet:worker/612-01e9f7-13
fleet:worker/612-a-r4-quarantine-outage-7ab0e8-5
fleet:worker/612-a-r9-r11-capacity-coverage-peers-cfcc79-7
fleet:worker/612-a-r10-loophealth-ccc872-8
fleet:worker/612-a-r12-turnregistrar-9e3bb7-9
fleet:worker/612-a-r5-leadconfigdir-9e70cf-6
fleet:lead/config-edit-redact-anchor-wording
fleet:worker/config-edit-seam-ca8dc1-1
fleet:worker/612-r67-630-lifecycle-290b8d-3
fleet:worker/629-625-ports-seams-da7d5d-4
fleet:worker/612-r12-exhaustion-f37cd7-1
fleet:worker/612-r38-amqp-24b083-2
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/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#716
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/task-15-af0d10-12"
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?
Follow-up to the MCP half merged in
c468953(PR #712).Part 1 -- close the REST door
GET /tasks/{ticket}(FleetApp.taskStatus) calledMessageService.poll(ticket), the no-check overload. It now resolves the caller the same wayallow(...)does (ctx.attribute(CALLER)) and threadscaller.terminal()intoMessageService.poll(ticket, callerTerminal), matching the shape of the MCPfleet_pollhandler. A caller with no terminal (the unnamed primary) is unaffected -- that behaviour lives inMessageService.ownsTicketand was not touched.Part 2 -- pin both handlers
Added a source-scrape guard test (with its own control assertion, following
FleetMcpAuthzTest#theFleetListHandlerActuallyConsultsCollaboratorsVisibleTo's idiom) for:fleet_pollMCP handler (FleetMcpAuthzTest#theFleetPollHandlerActuallyThreadsCallerTerminalIntoPoll)taskStatusroute (FleetAppAuthTest#theTaskStatusRouteActuallyThreadsTheCallersTerminalIntoPoll)Also added a behavioural REST test (
FleetAppAuthTest#restPollRefusesADifferentWorkerButAllowsTheCreatorAndTheUnnamedPrimary) that drivesGET /tasks/{ticket}with three differently-resolved callers (two different workers, plus the unnamed primary) sharing oneMessageService. The ticket is minted directly on the shared service (the wayMessageServiceTestdoes), since REST's ownwait:falsesend path (FleetApp.sendMessage->messages.sendAsync(id, content)) does not thread a creator terminal through yet -- out of this ticket's scope, noted below.Verification
mvn clean install: BUILD SUCCESS, Tests run: 2011, Failures: 0, Errors: 0, Skipped: 0 (main was 2008; +3 new tests).callerTerminal(exchange)withnullat thefleet_pollhandler'spoll(...)call (line-anchored sed).mvn compilestayed green (mutation live). Full suite: 1 failure --FleetMcpAuthzTest.theFleetPollHandlerActuallyThreadsCallerTerminalIntoPoll. Restored, full suite green again.taskStatusline. Compile green. Full suite: 2 failures --FleetAppAuthTest.theTaskStatusRouteActuallyThreadsTheCallersTerminalIntoPollandFleetAppAuthTest.restPollRefusesADifferentWorkerButAllowsTheCreatorAndTheUnnamedPrimary. Restored, full suite green again.Out of scope (not fixed, noted only)
FleetApp.sendMessage'swait:falsepath callsmessages.sendAsync(id, content)-- the overload that records no creator terminal -- while the MCPfleet_sendpath already threads one through. A named lead/architect creating an async ticket over REST therefore has no recorded owner on it, which would make that same lead's own later REST poll of it look identical to an unrelated caller's. Left alone since it's outside this ticket's stated scope (thetaskStatus/poll route only).Also spotted the same "an identifier used as an authorization token" shape is worth a broader sweep elsewhere in this codebase, per the brief's instruction -- not investigated further here.
GET /tasks/{ticket} now resolves the caller the same way allow(...) does and threads that terminal into MessageService.poll(ticket, callerTerminal) instead of the no-check overload, so a worker can no longer read a ticket a different session created over REST. Adds a source-scrape guard (with its own control assertion) for both the fleet_poll MCP handler and this REST route, plus a behavioural test driving GET /tasks/{ticket} with three differently-resolved callers against one shared MessageService.Lead review — one more thing must ship in this PR before I merge it
I checked your work in the main clone and on your pushed commit
0ba597e. Part 1 and Part 2 aregood, and I confirmed the two production call sites myself:
I also checked the
caller == nullbranch you added. It is sound, and here is why, so nobody hasto re-derive it:
CALLERis only set by theapp.beforefilter whenauth != null(
FleetApp.java:244), andallow(...)returnstruewith no check in that sameauth == nullcase. So the null branch is reachable only in legacy mode, where nothing is enforced at all. And
when
auth != null,Authz.permitsdenies a null or anonymous caller outright atauth/Authz.java:97, so no unauthenticated caller ever reaches line 898.The follow-up: your "out of scope" note is a real break, and it is this PR's to fix
You flagged that
FleetApp.sendMessage'swait:falsepath callsmessages.sendAsync(id, content),the overload that records no creator terminal. I verified it at
FleetApp.java:697on your commit.You were right to flag it, and I am ruling that it has to be fixed here rather than deferred.
The reason it cannot wait:
ownsTicketiscallerTerminal == null || callerTerminal.equals(task.creatorTerminal). A REST-created ticket hascreatorTerminal == null, so for a terminal-bearing caller the comparison is"term_x".equals(null), which isfalse. So after your fix, a lead that creates an async ticketover REST can no longer poll its own ticket. Only the unnamed primary can. Driving the fleet over
REST is a documented fallback for when the MCP mount drops, so this is reachable, not theoretical.
Merging Part 1 on its own would close a read hole and break a working path in the same commit. I am
not shipping that half.
Scope of the follow-up — this and nothing else
wait:falsesend. Use the 4-argsendAsync(target, content, onAccepted, creatorTerminal)overload, and take the terminal from thesame
ctx.attribute(CALLER)source you used fortaskStatus— not a second resolution path.Keep passing whatever
onAcceptedthat path passes today; do not change the delegation-ownershiphook.
over REST as that same caller, and assert it is not refused. Add the refusal half too — a
different terminal-bearing caller must still be refused — so the test cannot pass by simply
disabling the check.
sedon the newcreatorTerminalargument,mvn -o compileconfirmed green first so you know the mutation islive and not a compile error, then the suite, then restore and confirm the file is byte-identical.
other two.
What I do not want
ownsTicket,Authz,Principal, or the role table.That is #715 and it is already designed; it is not yours.
Report back
Give me the real
mvn clean installnumbers fromfleetd/, written to a file rather than piped —a pipe hides a failure behind a zero exit — and state the test-count arithmetic against the 2011
you reported. End your turn with exactly one
fleet_replycarrying the whole report.sendMessage's wait:false branch now records the resolved caller's own terminal as the ticket's creatorTerminal, the same way taskStatus already resolves its caller, so a REST-created ticket's own creator can still poll it under the ownership check that now gates GET /tasks/{ticket}.Pushed the third unit from comment 18645.
Fix:
FleetApp.sendMessage'swait:falsebranch now readsctx.attribute(CALLER)(the same waytaskStatusandallow(...)do) and calls the 4-argMessageService.sendAsync(id, content, null, creatorTerminal)overload, instead of the no-creator 2-arg overload.onAcceptedstaysnull, same as before.New behavioral test (
FleetAppAuthTest.restSendAsyncRecordsTheCreatingCallersTerminalSoItCanStillPollItsOwnTicket): creates a ticket over REST as a named-lead caller (terminal-bearing, has SEND), polls it over REST as that same caller (not refused), and polls it as a different terminal-bearing caller (refused, with the exactownsTicketdetail message as the control).Mutation test: line-anchored
sedreplaced the newcaller == null ? null : caller.terminal()argument atsendAsync(...)with a literalnull.mvn -o compilestayed green (mutation is live, not a compile error). Full suite run: exactly 1 failure, in exactly the new test, with messagethe session that created the ticket over REST must be able to poll it: ..."detail":"forbidden: this ticket was created by a different session". Restored from backup;diffconfirmed byte-identical to pre-mutation.Control break: temporarily changed the new test's refusal-detail literal to a non-matching string and ran only that test; it failed with its own message:
a different terminal must still be refused with the ownership detail, not some other rejection: .... Restored;diffconfirmed byte-identical.Full build:
mvn clean install-> BUILD SUCCESS,Tests run: 2012, Failures: 0, Errors: 0, Skipped: 0(2011 baseline + 1 new test = 2012, confirmed against the aggregated surefire reports).Did not touch
ownsTicket,Authz,Principal, the role table, or the MCP send path. Not merging.Merged to
mainas9a64d42. Closing by hand — a local merge does not close a PR here.All three parts are in. Good unit, and the thing that made it good was flagging the REST send gap
instead of filing it away as someone else's problem.
I resolved a conflict, and the test count is how I checked the resolution
This branch and PR #717 both appended tests to
FleetMcpAuthzTest.java, so the merge conflicted. Itwas addition against addition, so I kept both sides and closed the two tests properly.
The count is the real check on that: main was 2014 after #717, this branch adds 3 from the first
round and 1 from the second, and the merged build reports 2018. If my resolution had swallowed a
test it would read 2017. All five tests are present exactly once — I checked each by name.
Three mutations, all mine, each confirmed live before the suite ran
FleetMcp.java:527— poll'scallerTerminal(exchange)→nulltheFleetPollHandlerActuallyThreadsCallerTerminalIntoPollFleetApp.java:698— RESTsendAsynccreator →nullrestSendAsyncRecordsTheCreatingCallersTerminalSoItCanStillPollItsOwnTicketFleetApp.java:899— REST poll caller →nullrestPollRefusesADifferentWorkerButAllowsTheCreatorAndTheUnnamedPrimaryFleetMcp.java:527is the one that matters most. Before this unit, that exact mutation survivedthe full suite — the fix could be switched off and nothing noticed. It now fails. That is the
specific thing this unit was asked to close, and it is closed.
Every file was restored and confirmed byte-identical, and the merged tree is byte-identical to the
tree I built and mutated (
143e08f).On your javadoc caveat
You asked whether "a caller with SEND permission" on the new
startOnSharedServiceoverload reads asa current contract rather than history. It does. It says what the parameter is for, which is what a
maintainer of that line needs. It names no ticket, no past behaviour and no reason-for-a-reviewer, so
it is on the right side of the rule.
Documentation, which was mine
wiki/11-Features.mdnow carries an entry for this gate, including the two gotchas that are easy tolose: the unnamed primary is never refused, and that is safe only because an anonymous caller is
denied
TASK_READfirst — nothing in the code pins that coupling. The second gotcha is the splitthis PR's follow-up fixed, written down so nobody reintroduces half of it.
One thing I took from your work into a new ticket
Your fix leaves
MessageService.poll(String)with no production caller. It defaults the terminal tonull, which skips the ownership check — it fails open, silently, which is exactly the hole thisPR closed. It has 44 test callers, so deleting it is the wrong trade. #718 tracks pinning it with a
scrape test. I also found a second instance of the same shape while reviewing:
FleetMcp.java:975forwards
sendAsyncwithcreatorTerminaldefaulted tonull. Not yours to fix; recorded so it isnot rediscovered.
Pull request closed