An observer cannot read the ticket it created, and the daemon nudges it five times to run exactly that forbidden call #778

Open
opened 2026-10-05 19:07:16 +02:00 by ltms · 0 comments
Owner

Reported by the anki lead (term_65d106559db144, an observer pane) on 2026-10-05, confirmed from this host's daemon log. Two defects, one of which is unconditional.

1. The reply loop between two observers is open at the reader's end

fleetd #748 gave an observer SEND to another observer. Observer→observer send works — measured both ways today, including across accounts (anki on the ltms config dir, trinotes on work). But the sender cannot read the answer:

fleet_poll(ticket=task-2c8e2b-2)        -> forbidden: observer:term_65d106559db144 may not TASK_READ
fleet_poll(target=term_65d106559d7e43)  -> forbidden: ... may not DRAIN

So a send is accepted, the receiver can fleet_reply, and the reply lands where its creator may not look. That is the same shape as [a grant at one gate of two is a dead feature] (#669): the capability was granted at the send gate and dropped at the read gate.

The daemon logged the receiver's own words from its pane:

11:51:05.418 WARN [turn-failed-term_65d106559db144] CompletionResolver - failing send ... via
turn-stall fallback: ⏺ I can't collect that ticket. fleet_poll(ticket=task-2c8e2b-2) returned
forbidden: observer:term_65d106559db144 may not TASK_READ. This session is an observer pane.

Preferred fix: scope TASK_READ to tickets whose creating terminal is the caller's own. A ticket already records its creator — #757 relies on that ("a ticket also records the terminal that created it, so even a leaked id reads nothing"), so the gate has the data it needs. The alternative the reporter offered — deliver to the sender's inbox and grant DRAIN — is a wider change for the same result, because an inbox read is destructive on first read.

Working rule agreed with both observer panes until this ships: answer with a new fleet_send to the sender's terminal id, never fleet_reply. Note fleet_reply is not forbidden to an observer; the break is at the reader, not the replier.

2. The daemon tells a caller to run a call it refuses — fix this regardless

11:24:52.512 DEBUG ReplyPushLoop - push: starting reminder loop for lead term_65d106559db144
11:25:07.620 DEBUG ReplyPushLoop - push: nudge sent to lead term_65d106559db144 (reply 1/5, ticket 1/5 ...)
... four more ...
11:26:23.048 DEBUG ReplyPushLoop - push: reminder cap (5) reached ... stopping

Five pane injections telling an observer to run fleet_poll, which Authz then refuses. The pane is the right receiver — it created the ticket — so this is not misdelivery; the instruction is wrong. Note the log calls an observer pane a "lead", which made this read as a routing fault when it is not.

An instruction a caller is forbidden to follow should never be injected. This holds whether or not fix 1 lands, and it is the cheaper half.

Not measured

I did not re-run the forbidden fleet_poll calls myself; those two lines are the reporter's output, and the CompletionResolver line above is the daemon's own record of the same refusal. I have not checked whether any other push path has the same mismatch between what it asks for and what the target may do.

Reported by the anki lead (`term_65d106559db144`, an observer pane) on 2026-10-05, confirmed from this host's daemon log. Two defects, one of which is unconditional. ## 1. The reply loop between two observers is open at the reader's end fleetd #748 gave an observer `SEND` to another observer. Observer→observer send works — measured both ways today, including across accounts (`anki` on the `ltms` config dir, `trinotes` on `work`). But the **sender cannot read the answer**: ``` fleet_poll(ticket=task-2c8e2b-2) -> forbidden: observer:term_65d106559db144 may not TASK_READ fleet_poll(target=term_65d106559d7e43) -> forbidden: ... may not DRAIN ``` So a send is accepted, the receiver can `fleet_reply`, and the reply lands where its creator may not look. That is the same shape as [a grant at one gate of two is a dead feature] (#669): the capability was granted at the send gate and dropped at the read gate. The daemon logged the receiver's own words from its pane: ``` 11:51:05.418 WARN [turn-failed-term_65d106559db144] CompletionResolver - failing send ... via turn-stall fallback: ⏺ I can't collect that ticket. fleet_poll(ticket=task-2c8e2b-2) returned forbidden: observer:term_65d106559db144 may not TASK_READ. This session is an observer pane. ``` **Preferred fix: scope `TASK_READ` to tickets whose creating terminal is the caller's own.** A ticket already records its creator — #757 relies on that ("a ticket also records the terminal that created it, so even a leaked id reads nothing"), so the gate has the data it needs. The alternative the reporter offered — deliver to the sender's inbox and grant `DRAIN` — is a wider change for the same result, because an inbox read is destructive on first read. Working rule agreed with both observer panes until this ships: answer with a **new `fleet_send` to the sender's terminal id**, never `fleet_reply`. Note `fleet_reply` is not forbidden to an observer; the break is at the reader, not the replier. ## 2. The daemon tells a caller to run a call it refuses — fix this regardless ``` 11:24:52.512 DEBUG ReplyPushLoop - push: starting reminder loop for lead term_65d106559db144 11:25:07.620 DEBUG ReplyPushLoop - push: nudge sent to lead term_65d106559db144 (reply 1/5, ticket 1/5 ...) ... four more ... 11:26:23.048 DEBUG ReplyPushLoop - push: reminder cap (5) reached ... stopping ``` Five pane injections telling an observer to run `fleet_poll`, which `Authz` then refuses. The pane is the **right** receiver — it created the ticket — so this is not misdelivery; the instruction is wrong. Note the log calls an observer pane a "lead", which made this read as a routing fault when it is not. An instruction a caller is forbidden to follow should never be injected. This holds whether or not fix 1 lands, and it is the cheaper half. ## Not measured I did not re-run the forbidden `fleet_poll` calls myself; those two lines are the reporter's output, and the `CompletionResolver` line above is the daemon's own record of the same refusal. I have not checked whether any other push path has the same mismatch between what it asks for and what the target may do.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: fleet/fleetd#778