CB-548: record delegator ownership only when a send is accepted #21

Closed
agent wants to merge 2 commits from worker/cb-548-rendezvous-guard-116b53-10 into main
Member

Send-ownership hardening for CB-548/CB-532.

The bug. BridgeMcp called primaryRegistry.recordDelegation(target, caller) at bridge_send request time, before MessageService had won the session lock / delivered. A second sender that timed out BUSY could therefore overwrite the live delegator and steal late-reply push routing for a turn it never owned.

The fix.

  • Moved delegator ownership into a MessageService.send(..., Runnable onAccepted) accepted-delivery hook, invoked only once the send wins the target lock and queues delivery. Threaded through sendAsync too (async flood records on acceptance). BUSY (lock never taken) never records. The answer (turnId) path is left untouched — ownership of an existing bridge_ask is preserved, not rewritten.
  • Rendezvous.open is now atomic fail-if-present: a double open on a session throws IllegalStateException rather than replacing the first waiter, so any future invariant violation fails loudly instead of silently swapping the waiter another send is blocked on. Rendezvous.close made public as the symmetric deregister.
  • Updated javadocs/comments that implied record-at-request.

Tests. Rendezvous double-open rejection proving the first waiter stays registered and resolvable; MessageService regressions: L holds W / A BUSY leaves L as delegator, an accepted A send after L completes becomes owner, async records ownership, and answering an ask does not rewrite ownership. Full mvn clean install: 526 tests, 0 failures, 0 errors, BUILD SUCCESS. Not merged.

Send-ownership hardening for CB-548/CB-532. **The bug.** `BridgeMcp` called `primaryRegistry.recordDelegation(target, caller)` at `bridge_send` **request** time, before `MessageService` had won the session lock / delivered. A second sender that timed out BUSY could therefore overwrite the live delegator and steal late-reply push routing for a turn it never owned. **The fix.** - Moved delegator ownership into a `MessageService.send(..., Runnable onAccepted)` accepted-delivery hook, invoked only once the send wins the target lock and queues delivery. Threaded through `sendAsync` too (async flood records on acceptance). BUSY (lock never taken) never records. The answer (`turnId`) path is left untouched — ownership of an existing bridge_ask is preserved, not rewritten. - `Rendezvous.open` is now atomic fail-if-present: a double open on a session throws `IllegalStateException` rather than replacing the first waiter, so any future invariant violation fails loudly instead of silently swapping the waiter another send is blocked on. `Rendezvous.close` made public as the symmetric deregister. - Updated javadocs/comments that implied record-at-request. **Tests.** Rendezvous double-open rejection proving the first waiter stays registered and resolvable; MessageService regressions: L holds W / A BUSY leaves L as delegator, an accepted A send after L completes becomes owner, async records ownership, and answering an ask does not rewrite ownership. Full `mvn clean install`: 526 tests, 0 failures, 0 errors, BUILD SUCCESS. Not merged.
agent added 1 commit 2026-08-13 18:56:25 +02:00
CB-548: record delegator ownership only when a send is accepted
CI / build (pull_request) Successful in 53s
CI / contract (pull_request) Successful in 1m18s
b39f700765
Record PrimaryRegistry delegator ownership via a MessageService accepted-delivery
hook (won the session lock + queued delivery), never at bridge_send request time, so
a concurrent sender that times out BUSY cannot steal a live turn's reply routing.
Make Rendezvous.open atomic fail-if-present so a double open trips loudly instead of
replacing the waiter another send is blocked on. Answering a bridge_ask keeps the same
ownership (no rewrite). Adds ownership/rendezvous regression tests.
ltms added 1 commit 2026-08-13 19:19:27 +02:00
CB-548: guard primary singleton to PRIMARY callers; open waiter before enqueue
CI / build (pull_request) Successful in 55s
CI / contract (pull_request) Successful in 1m9s
cedab54ae8
Fix two handler-level bugs found in PR #21:
- Only PRIMARY callers may update PrimaryRegistry.record (the legacy singleton 'primary'
  fallback for no-delegation inbox nudges). An architect SEND previously recorded its terminal
  as the fallback; the per-target delegation map does not cure the singleton. New
  BridgeMcp.recordPrimarySingleton uses the resolved role (caller.isPrimary()) — named leads
  (PRIMARY) still record, architects never do.
- MessageService.send now opens the rendezvous waiter BEFORE queueing delivery, fixing both the
  enqueue-before-open fast-reply race (a fast reply no longer orphans into the inbox) and
  callback-failure ordering: a throwing onAccepted (public callback) fails the send cleanly with
  no stale waiter and no queued, orphanable message.

Tests: architect SEND vs lead SEND primary-singleton regression; throwing onAccepted leaves no
stale waiter or queued orphan.
ltms closed this pull request 2026-08-13 19:43:55 +02:00
Some checks are pending
CI / build (pull_request) Successful in 55s
CI / contract (pull_request) Successful in 1m9s

Pull request closed

Sign in to join this conversation.