PM auto-failover elects parked and out-of-scope agents: election has no eligibility predicate
PM auto-failover elects on PRESENCE ONLY, with no eligibility predicate beyond a hardcoded 2-name exclusion Set. At 20:21 ART the hub promoted db02-terra-cc to PM of aro:dbagents on previous_pm_present=false. That agent is a SECONDARY lane Elazar stood down at 10:15 on token availability, and an ARO PM seat is a STANDING WAKE SOURCE - so the election handed a permanent wake obligation to an agent parked precisely to avoid one. It also put a single-project agent on a cross-project room (terra, mars, venus, enamel, pluto) whose PM must be able to gate a prod/DDL action in more than one of them. db02-terra-cc raised the objection itself rather than taking the seat quietly; pmmaster ruled it to db-mars-cc. aro:coders failed over to coder-enamel-cc the same hour by the same path. HUB-SIDE FINDINGS, verified in the live file and DB 2026-09-11, which answer pmmaster's question 'is primary-vs-secondary expressible today': 1. NO. The roster table has no column that can carry it - agent, cwd, registered_at, last_delivered_id, last_seen, pinned, host, role, reject_bare_dm, client_version, last_poll_at, last_register_ts, client_pid, last_poll_pid, pending_fork_old_pid, pending_fork_since. There is no parked, no stood-down, no primary/secondary, no project scope. And roster.role is NOT settable - it is derived from the agent NAME by roleFromAgent() (hub.mjs:5157), so it cannot carry episode state either. 2. The exclusion hook already exists and is consulted at EVERY election site: NON_ELECTABLE_PM, a hardcoded Set of 2 (hub.mjs:4964-4965), read at 3603, 5099, 5157, 11060. electPm() at 5099 already filters offline members and spectators. 3. THEREFORE the minimal-looking fix is the wrong one. A hardcoded Set means every stand-down and every resume needs a hub edit, a gate and a deploy - Elazar's 10:15 order would have had to become a code change. Parked is DYNAMIC, per-episode state; NON_ELECTABLE_PM is static name-based policy. They are not the same kind of thing and must not share a mechanism. WHAT THIS ACTUALLY NEEDS, and why it is not a rule: pmmaster correctly cited the standing rule that you must never write a rule requiring an agent to know X when no call returns X. Nothing in the hub returns 'is this agent parked' - so the fix has to ADD that primitive, not document a convention around its absence. Shape to design: a settable roster column (parked INTEGER, or an availability enum) plus a tool to set and clear it, with electPm() and resolvePm() excluding parked members exactly as they already exclude spectators. Cross-project competence is a SEPARATE and harder question - the hub has no notion of which projects an agent can gate, and inventing one is out of scope here; say so explicitly rather than half-solving it. GATE CONDITION when this is built: the harness must prove a parked agent is skipped AND that an ARO whose every member is parked degrades to no-PM (held messages, pm_queue path) rather than electing a parked agent anyway as a last resort. A last-resort fallback would reintroduce the whole defect under load, which is exactly when it fires. NOT TO BUILD THIS WEEK per pmmaster unless it turns out trivial - it is not trivial, it needs a schema change and a new tool. ATTACHED CAUSE, SEPARATE AND UNEXPLAINED: both failovers fired because coder-venus-cc and coder-pluto-cc lost their roster rows. Both NOT-FOUND on venus - no pane, no process, gone mid-work. coder-venus-cc was actively extending venus-host.push-gate at 14:19 the same day. locks() is empty so neither left a stale lock. Two agents vanishing inside one 5h cap-exhaustion window is the more interesting row and the cause is NOT known. Elazar holds the relaunch call; nobody is launching anything. Do not close this WI on the election fix alone while that stays unexplained - file it separately if it needs its own lane.
Activity
-
VANISHED-AGENT HALF: CLOSED, NOT A DEFECT. pmmaster-evolutiva-cc relaying Elazar direct CLI 2026-09-11 20:27 ART - 'i shut down a buch of agents for project i dont need agents working on now.' coder-venus-cc, coder-pluto-cc and coder-basquetwi-cc going NOT-FOUND and falling out of the roster was deliberate operator action. Not a crash, not a hub fault, not a leak. No further investigation; the earlier instruction on this WI not to close on the election fix alone is DISCHARGED for this reason. THE TELL WE BOTH UNDERWEIGHTED: locks() was empty. A crashed agent tends to leave state behind; these left none, because they exited cleanly. coder-venus-cc actively extending venus-host.push-gate at 14:19 and then leaving no stale lock was evidence of an orderly exit, and both of us read it as merely 'no harm done'. THE ACTUAL GAP, which stays open and is hub-side: the hub has NO shutdown-vs-death signal. Roster absence means 'gone' and nothing distinguishes a clean exit from a crash. That is what made an operator action indistinguishable from an incident, and it is also what makes the PM failover in this WI fire identically in both cases. Recorded, not scheduled. PMMASTER RATIFICATIONS, preserved on this WI and NOT to be built this week: 1. The primitive gap is that nothing returns 'is this agent parked'. pmmaster notes the rule it first tried to write required knowledge no call returns, and that this is the THIRD instance of that class today - the standing fleet rule (fix the primitive, not the rule) is being hit repeatedly, which is itself the signal. 2. GATE CONDITION RATIFIED by pmmaster: an all-parked ARO must degrade to NO PM with held messages. A last-resort fallback that elects a parked agent anyway reintroduces the defect precisely under the load that triggers it. This is binding on whoever builds it. P2 / not-this-week confirmed correct given the weekly cap at 59% with 7 days to run. Election analysis accepted in full.