WS report handlers silently drop a pool member's reports: the repository query joins on agent.daemonId.
What is wrong
Three handlers admit a frame only if the reporting daemon is the agent's recorded daemon. For a pool agent that column is NULL, so the frame is discarded — without an error, because each of these paths treats "not mine" as "ignore".
Cron reports. persistence/repositories/cron.repo.ts:225:
const cron = await this.db.cronDef.findFirst({
where: { id: cronId, agent: { daemonId: reportingDaemonId } }, …
})
if (!cron) return false
and ws/handlers/cron-report.ts:24 ignores the false. Every fire / progress / completion report from a pool member is dropped, so lastRunAt never advances and the console's run history for a pool agent's crons stays empty forever. Note the sibling read was already compensated: listForDaemon (cron.repo.ts:203, same join) is unioned with listForAgents(heldAgentIds) in orchestrator/placement.ts:401. recordReport was not.
Integration channel snapshots. ws/handlers/integration-channels.ts:28 and :46:
const owned = await deps.integration.activeForDaemon(DaemonId(conn.daemonId))
const integration = owned.find((i) => i.id === p.integrationId)
if (!integration) return // drops silently
integration.repo.ts:533 joins agent: { daemonId }. integration.repo.ts:542 (activeForAgents) exists precisely as the duty half and is used in the reconcile path (orchestrator/placement.ts:404), but this handler was never converted. Slack channel-membership snapshots from a pool member are discarded, so the console's conversation lists — and the gating defaults and collab-route refresh computed from them — never update.
Cross-daemon child session status. ws/handlers/child-session-status.ts fails on both legs:
:46 if (!parent || parent.daemonId !== conn.daemonId || …) { NOT_FOUND }
:54 if (!childAgent || childAgent.orgId !== parent.orgId || !childAgent.daemonId) { NOT_FOUND }
:62 if (childAgent.daemonId === conn.daemonId) { NOT_FOUND }
:67 const owner = deps.connReg.get(childAgent.daemonId)
The parent proof compares the session's recorded daemon against the asker, so after a rollout the member now serving the parent is not the one that first reported it (and the column is NULL once the retired row is reaped, SessionMeta.daemonId is onDelete: SetNull). The child leg refuses every pool agent outright, because a pool agent names no machine. viewSessionStatus on a cross-daemon child therefore returns "not found" for all pool agents.
Smallest correct fix
Each of these is one predicate:
- cron: fence on "the reporting daemon serves this cron's agent" (placement ∪ live duty holders) instead of the join;
- integration channels:
activeForDaemon(d) ∪ activeForAgents(servedAgents(d)), the union the reconcile path already builds;
- child status: authorize the parent with the resolver's may-act predicate, and target the child with
servingDaemon(childAgent).
Part of the "authorize on agent.daemonId" recurrence #955 records under M4; siblings #996, #999.
WS report handlers silently drop a pool member's reports: the repository query joins on
agent.daemonId.What is wrong
Three handlers admit a frame only if the reporting daemon is the agent's recorded daemon. For a pool agent that column is NULL, so the frame is discarded — without an error, because each of these paths treats "not mine" as "ignore".
Cron reports.
persistence/repositories/cron.repo.ts:225:and
ws/handlers/cron-report.ts:24ignores thefalse. Every fire / progress / completion report from a pool member is dropped, solastRunAtnever advances and the console's run history for a pool agent's crons stays empty forever. Note the sibling read was already compensated:listForDaemon(cron.repo.ts:203, same join) is unioned withlistForAgents(heldAgentIds)inorchestrator/placement.ts:401.recordReportwas not.Integration channel snapshots.
ws/handlers/integration-channels.ts:28and:46:integration.repo.ts:533joinsagent: { daemonId }.integration.repo.ts:542(activeForAgents) exists precisely as the duty half and is used in the reconcile path (orchestrator/placement.ts:404), but this handler was never converted. Slack channel-membership snapshots from a pool member are discarded, so the console's conversation lists — and the gating defaults and collab-route refresh computed from them — never update.Cross-daemon child session status.
ws/handlers/child-session-status.tsfails on both legs:The parent proof compares the session's recorded daemon against the asker, so after a rollout the member now serving the parent is not the one that first reported it (and the column is NULL once the retired row is reaped,
SessionMeta.daemonIdisonDelete: SetNull). The child leg refuses every pool agent outright, because a pool agent names no machine.viewSessionStatuson a cross-daemon child therefore returns "not found" for all pool agents.Smallest correct fix
Each of these is one predicate:
activeForDaemon(d) ∪ activeForAgents(servedAgents(d)), the union the reconcile path already builds;servingDaemon(childAgent).Part of the "authorize on
agent.daemonId" recurrence #955 records under M4; siblings #996, #999.