fleetd #778: scope TASK_READ to a caller's own ticket; gate push-loop nudges on Authz #785
Closed
agent
wants to merge 2 commits from
worker/778-12988a-4 into main
pull from: worker/778-12988a-4
merge into: fleet:main
fleet:main
fleet:worker/829-shell-tests-linux-green-f5376c-2
fleet:worker/808-0c71eb-2
fleet:worker/797-disable-box-gate-5b5478-12
fleet:worker/799-a76336-9
fleet:worker/796-a7911a-5
fleet:worker/778-e301b0-1
fleet:worker/791-fleet-plugin-0-3-0-9e25b0-2
fleet:worker/790-observer-to-lead-send-523d45-1
fleet:worker/788-eb7dd0-1
fleet:worker/782-styling-probe-4ceb10-9
fleet:worker/782-18f8bc-5
fleet:worker/780-faf58b-3
fleet:worker/759-authz-comment-and-role-list-e3f0e5-5
fleet:worker/756-758-observer-pane-discovery-7e6ffd-1
fleet:worker/759-role-model-comments-5d8409-3
fleet:worker/743-pane-discovery-ad5b75-5
fleet:worker/743-observer-send-4706db-6
fleet:worker/749-edge-baseline-28d1a0-3
fleet:worker/748-dead-comment-refs-f42ac5-4
fleet:worker/737-9c61d3-4
fleet:worker/737-a263f3-3
fleet:worker/737-20d1d9-1
fleet:worker/737-b038f7-2
fleet:worker/726-unit2-75cb13-4
fleet:worker/737-owner-key-ff061f-10
fleet:worker/736-presence-forget-f35144-9
fleet:worker/705-observer-14c258-6
fleet:worker/722-024c34-5
fleet:worker/726-ea34a0-2
fleet:worker/726-10cbf0-1
fleet:worker/729-5961c6-3
fleet:worker/727-ee14ed-3
fleet:worker/719-bdd95e-4
fleet:worker/702-4f5c7f-2
fleet:worker/715-5c43fc-1
fleet:worker/721-70f9ea-5
fleet:worker/718-99362b-2
fleet:worker/task-15-af0d10-12
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#785
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/778-12988a-4"
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?
Two small, independent changes, per the ticket's own scoping correction (narrower than the title).
Change 1 -
Authz.permits'sTASK_READcase was unconditionally closed to anyone but a primary, worker, or architect. A collaborator or an observer can create an async ticket viafleet_send(wait:false)but could never read it back. Added acallerOwnsTicketclassifier (same pattern as the existingknownLeadOrCollaborator/knownObserverTargetclassifiers), backed by a newMessageService.isTicketOwnedBy(ticket, callerOwner). Wired into bothFleetMcp'sfleet_pollhandler andFleetApp'sGET /tasks/{ticket}. Both call sites had a second bug that made this moot even with a correct policy: they were passingtarget/nullinto the authorization check instead of the actual ticket id, so ownership could never have been evaluated. Fixed. No change toDRAIN(fleet_poll{target}) or theREPLY/ASKown-pane rule, as instructed.Change 2 -
ReplyPushLoopnudged a pane to run a callAuthzwould refuse it (observed: an observer nudged 5x tofleet_poll(ticket=...)). Fixed generally, not as an observer special case:ReplyPushLoopnow takes a(lead, ticket) -> booleanauthorization predicate, consulted once atpendingTicketsFor(the one place every other lookup in the class reads from), so an unauthorized ticket is invisible todecide/injectNudge/bumpNudgeCountsalike. The real predicate (wired inFleetdAssembly) reconstructs the lead'sPrincipalfrom its bare terminal via a newCallerResolver.resolveTerminal(extracted from the existing connection-basedresolve()) and runs the sameAuthz.permits/isTicketOwnedBycheck the real call would face. Reply and question nudges need no equivalent gate -SPAWN/SEND-to-a-worker are already primary/architect-only, so the lead resolved for those two sources always already has unconditionalDRAIN/ANSWER. Also fixed the nudge-sent/failed log lines, which called any ticket creator a "lead" regardless of role.Tests (both required pairs are positive+negative control):
AuthzTest: new TASK_READ ownership matrix for an observer and a collaborator, each with a negative control (ticket created by someone else); existing observer/collaborator denial tests' javadoc updated for accuracy (they use the default fail-closed classifier, not an absolute denial).MessageServiceTest:isTicketOwnedBydirect test, same positive/negative pairing, plus an unknown-ticket case.ReplyPushLoopTest: a forbidden ticket produces no nudge, paired with a positive control (same setup, authorizing predicate) proving the nudge still fires normally.Build:
mvn clean installfrom the worktree root - BUILD SUCCESS, Tests run: 2182, Failures: 0, Errors: 0, Skipped: 0 (includes PackageCyclesTest green; no new package-pair edge was needed since ReplyPushLoop's new dependency is a plainBiPredicate<String,String>built in FleetdAssembly, not a new import from msg into auth).Out of scope, not touched, per the ticket: the already-correct
REPLY/ASKownsSession rule, and the separate "reply returned as unroutable" defect (ticket already resolved by the turn-completion scrape before the reply queue existed).TASK_READ was unconditionally closed to anyone but a primary, worker, or architect, so a non-worker peer that used fleet_send(wait:false) could never collect its own async reply. Add a ticket-ownership classifier to Authz.permits, following the same pattern as the existing SEND classifiers, and expose MessageService.isTicketOwnedBy so FleetMcp's fleet_poll handler and FleetApp's GET /tasks/{ticket} can build it. Both call sites also fix a second bug: they were passing a blank/null authorization target instead of the actual ticket id, so even a correct policy could never have been evaluated against it. Tests: AuthzTest gets a TASK_READ ownership matrix for an observer and a collaborator, each paired with a negative control (a ticket created by someone else). MessageServiceTest covers isTicketOwnedBy directly, same pairing.Adjudicated against current
origin/main=376b583. Closing this as superseded — but one half of it is still wanted, and I have filed it separately.The authorization half already shipped, by another route
Everything this PR does in
Authz,MessageService,FleetMcpandFleetAppis on main already, under a different name:origin/mainAuthz.NO_CALLER_OWNS_TICKET+callerOwnsTicketparamticketOwnedByCallerparam —Authz.java:166, used at:225MessageService.isTicketOwnedByMessageService.ownsTicket—MessageService.java:1572FleetMcp.java:593-596FleetApp.java:305,:328-334So both gates I was most worried about — the MCP tool and the REST route — are covered on main. There is no half-applied grant here.
And main's version of the rule is the safer one. Compare:
Same outcome for every role that exists today. But this PR's form hands the ticket grant to any future role that is not primary, worker or architect, the moment someone adds one. Main names the two roles it means. Rebasing this PR onto main would mean discarding its own version of this line anyway.
The one part that did NOT ship: the push-loop nudge gate
ReplyPushLoop.pendingTicketsForon main is still the plain, unfiltered stream (ReplyPushLoop.java:213). So the grant exists at the two gates a caller reaches, and the loop that tells a pane to poll consults nothing.I checked whether that is reachable rather than assuming it, and it is, though narrowly.
onTicketTerminalsets a ticket's nudge target fromresolveLiveLead(target)→primaryRegistry.nudgeTargetFor(target)(ReplyPushLoop.java:566,:484), and that lookup is keyed on the worker target, not on the ticket.fleet_send{wait:false}is open to an observer and a collaborator. So if two different callers delegate to the same worker target, a ticket created by one can be recorded against the other as its nudge target — and the nudge then names a ticket that pane does not own, whose pollTASK_READwill refuse. Main already solved exactly this shape for questions:PendingQuestioncarries "whether that nudge target's own role may actually run thefleet_send(turnId=…)a question nudge would tell it to run" (ReplyPushLoop.java:222-227). Tickets never got the same treatment.Filed as its own ticket so it can land small and against current main, rather than through a rebase of this one.
Two things to carry into that ticket, not to repeat
Both are in this PR's push-loop code and both point the wrong way:
(lead, ticket) -> true, and the field assignment turns anullpredicate into(lead, ticket) -> trueas well. The production wiring does supply the real check, and I could not reach either default — but a default on an authorization parameter has to fail closed. This repo wrote that rule down for this very PR series: see the Features entry on #778's near-miss, "A default on a parameter like this has to fail closed: naming no route is harmless, naming a refused one is the defect." The same applies toFleetdAssembly'sif (messages == null || cr == null) return true;construction-window escape.debug. Nothing abovedebugrecords that a nudge was withheld, so if the filter is ever wrong the symptom is a lead that is silently never told about a ready ticket. That is the failure mode this project has already paid for twice: the one instrument that located #802 was removed when the symptom it logged was switched off. Log the suppression at a level an operator sees.Why this is not a rebase
Measured with
git merge-tree --write-tree origin/main refs/pull/785/head: exit 1, conflicts in six files —Authz.java,FleetdAssembly.java,FleetMcp.java,ReplyPushLoop.java,FleetApp.javaandAuthzTest.java. The branch is 47 commits behind. Five of those six conflicts are in the half that is already on main, so resolving them would mean deleting this PR's work to keep main's. The remaining ~50 lines ofReplyPushLoopare worth more as a fresh change.On the one finding from the delegated review
A reviewer flagged
MessageService.pollreachingtasks.get(ticket)on aConcurrentHashMap, which throws on a null key. The map type and the missing guard are both real. It is not a defect in this PR, for two independent reasons, and I checked each:FleetMcp.java:1346returnserror("ticket (or target) is required")for a blank ticket beforemessages.pollat:1349, and the REST path takesctx.pathParam("ticket")onGET /tasks/{ticket}, which cannot match an empty segment.origin/main:1500, so it predates this branch.Recording it here so nobody re-raises it as a blocker.
Pull request closed