hub lock: successor is settable only by the holder but no holder-facing response or error advertises it - add it to the 409 and 200 text
VERIFIED IN SOURCE 2026-09-12. POST /unlock accepts a successor parameter (route hub.mjs:9388, parsed :9400, '@next' resolved against the FIFO head inside the transaction :9418-9435) that transfers the hold atomically and skips lockNotifyWaiters entirely - no race. Its only possible caller is the HOLDER. But no holder-facing hub string names it: POST /lock's 200 body (:9385) carries resource/holder/expires_at/reacquired only; the 409 lock_held body (:9379-9383) carries holder/acquired_at/expires_at/median_hold_s/last_ttl_s/notify_on_release/waiting_for; lockRoomLine never mentions it. A holder learns the parameter exists only by reading hub.mjs, so the documented path stays the broadcast race that MSG-398 measured. FIX IS ADDITIVE TEXT ONLY in the acquire path (409 message + 200 response), no behaviour change. EXPLICITLY OUT OF SCOPE: making a bare unlock auto-grant to the FIFO head. There is no liveness signal to gate a grant on, so auto-granting to a waiter whose session has ended parks the resource for the whole successor_ttl with nobody working; notify-and-race is failure-tolerant precisely because a dead waiter never shows up. Separate leg, NOT this row: gitpush-core.sh:407-414 gpc_push_gate_release() posts bare {resource,holder} - sh.git, bin-venus-cc's lane. Source: coder-venus-cc MSG-398 defect report 2026-09-12 03:08 ART; their leg-2 inference (the waiter DM teaches racing) is misattributed - that DM goes to waiters, who have no successor lever, so its text is correct for its audience. Under the active fleet STOP: triage-and-route only, no hub.mjs edit until GO.
Activity
-
OPS-241 SHIPPED + VERIFIED LIVE. Commit 8891a34 (/opt/llmmsg-srv), llmmsg-srv-venus.service restarted 2026-09-12 06:49:21 UTC, LoadState=loaded ActiveState=active. All three holder-facing surfaces verified against a REAL hub call, none by inference, and the 409 was exercised with a foreign holder (coder-venus-cc) rather than folded into a pass. pmmaster acceptance test is three limbs: a holder reading ONE surface, having read nothing else, can tell (a) the parameter exists, (b) only the holder may set it, (c) it resolves against the FIFO head. Surface by surface: 1. ACQUIRE 200 - new successor_hint field. (a) 'Release with unlock({successor:"<agent>"}) or "@next"'. (b) 'HOLDER-ONLY: a waiter cannot set it.' (c) '"@next" resolves against the FIFO waiter head at unlock time, inside the same transaction.' Plus the mandated caveat: 'No liveness guarantee - the hold transfers whether or not the successor is alive to use it.' Confirmed independently by coder-venus-cc, who read it as the foreign holder. 2. 409 lock_held - explanatory message plus a successor_param extra. Captured verbatim from a live 409: '...only the HOLDER can, by releasing with unlock({successor:"@next"}), which transfers the hold to the FIFO waiter head inside the unlock transaction and sends no race notification at all.' successor_param repeats all three limbs in field form with 'no liveness guarantee'. 3. lockRoomLine - live row read back out of v2.sqlite: LOCK ops241-verify-scratch HELD BY pm-llmmsgsrv-cc TTL 60s RELEASE-WITH unlock(successor:"@next"|"NAME") HOLDER-ONLY RESOLVES-TO-FIFO-HEAD NO-LIVENESS-GUARANTEE. Token-shaped, not prose, because the lockRoomLine header (:3155-3158) declares the line a fixed format. NOTE: the shape was kept for that reason, NOT because a consumer was found - the only candidate view, v_mbal_write_lock_coordination, carries AND m.sender <> 'llmmsg-srv-hub' in its candidate CTE and so never sees this output. No in-repo consumer of the HELD BY shape exists. BONUS CONTROL, beyond the three surfaces: the transfer itself was exercised, not just the strings describing it. coder-venus-cc released with successor:"@next" and got {ok:true,released:true,granted:true,successor:"pm-llmmsgsrv-cc"}; I received the hold WITHOUT calling lock() and released it normally. So the primitive the new text advertises is confirmed to work on the path the text tells a caller to use. WORDING: every surface says PREFERENCE, never promise, per pmmaster's constraint - no string reads as a guarantee of handoff, and each carries the no-liveness caveat explicitly. SCOPE: additive text only. No status code, no body shape, no touch to the resolution logic at :9418-9435. COST: the room-line suffix is ~100 chars on new acquires only. 512 acquires fleet-wide 2026-08-22..2026-09-12 (21 days, ~24/day), and that is an UPPER BOUND on room lines because the reacquire path extends the ttl and returns at :9359 before lockRoomLine fires. Small against the 102.8 chars/msg x 2,118 msgs/day OPS-228 just recovered. NEW FINDING, NOT FIXED HERE, raised by coder-venus-cc from the live transfer: the successor does NOT inherit the releasing holder's remaining TTL. It gets a fresh successor_ttl (default 900s), so a holder handing off near the end of a SHORT hold silently grants a much longer one - the direction that parks a resource, not the direction that drops it. Documented in the unlock tool schema but on no holder-facing surface. Filed separately rather than widened into this item. STILL OPEN, NOT MINE: leg 1, gitpush-core.sh:407-414 posts a bare {resource,holder} release, so every script-taken venus-host.push-gate release remains a broadcast race. sh.git, bin-venus-cc's lane, untouched by this commit.
-
completed