bus: list messages stranded by a holder change in agents, row 80 (#1574)

A holder that releases a role without reading what it was delivered leaves
those messages where no later holder receives or reads them. The agents
view now lists them for human and reader sessions under `stranded`, as
{id, from, run, at}, oldest first, at most 50. The bus doesn't redeliver,
route or mark them read; a person who still needs one sends it again.

Round 2 is a mechanical rebase of round 1 onto row 93. Reviewed by Filbert
(28206, approve); Sage gate record 28207. Gate on 7496251c: 14 node suites
and 9 scripts green.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-13 11:01:50 -05:00
co-authored by Claude Opus 5.5
parent 177b49ebf9
commit 8e0fbca420
3 changed files with 106 additions and 5 deletions
+12 -1
View File
@@ -43,6 +43,8 @@ const CLOSED = "('resolved','withdrawn','expired')";
export const RECENT = 50;
// How many replies `replies` returns, oldest first.
export const REPLIES = 50;
// Row 80 (#1574): how many stranded messages `agents` lists for each role, oldest first.
export const STRANDED = 50;
// Row 89 (#1583): the arguments `tasks` takes, and its page size by default and at most.
const TASK_ARGS = ['done', 'assignee', 'label', 'requirement', 'limit', 'after', 'task_ref'];
export const TASK_PAGE = 50;
@@ -1213,7 +1215,16 @@ export class Broker {
.map((d) => ({ id: d.id, question: d.question, class: d.class, blocking: !!d.blocking, task_ref: d.task_ref, at: d.at }));
const working = busy.messages ? { messages: busy.messages, since: busy.since } : null;
const state = working ? 'working' : inputNeeded.length ? 'waiting' : 'idle';
return { ...c, state, working, inputNeeded };
// Row 80 (#1574): messages to the role that an earlier holder was delivered and never read.
// The run they went to no longer holds the role. A delivered row already exists, so routing
// and receive skip them. Listing them writes nothing.
const stranded = this.#store.all(
`SELECT m.id, m.from_role AS "from", d.holder_run AS run, d.at FROM messages m JOIN deliveries d ON d.message=m.id AND d.op='delivered' WHERE m.business=? AND m.to_role=? AND d.holder_run IS NOT ? AND NOT EXISTS(SELECT 1 FROM deliveries r WHERE r.message=m.id AND r.op='read') ORDER BY m.seq LIMIT ${STRANDED}`,
c.business,
c.role,
c.holder_run,
).map((m) => ({ id: m.id, from: m.from, run: m.run, at: m.at }));
return { ...c, state, working, inputNeeded, stranded };
}
#claimEnd(s, h, op, decision) {
this.#store.run(