Skip to content

kanban: verify openBoard() cannot race a stalled attach, and close morph#305 as invalid - #333

Merged
Yaraslaut merged 1 commit into
masterfrom
fix/305-kanban-openboard-bind-race
Aug 28, 2026
Merged

kanban: verify openBoard() cannot race a stalled attach, and close morph#305 as invalid#333
Yaraslaut merged 1 commit into
masterfrom
fix/305-kanban-openboard-bind-race

Conversation

@Yaraslaut

Copy link
Copy Markdown
Member

Summary

morph#305 asked to gate Main.qml's openBoard() dispatch on BoardBridge::bound (restoring the relay #313 removed), on the premise that a click-triggered openBoard() can race the handler's registration and fail fast with "handler not bound" before its attach settles — especially in Remote/Socket mode.

Re-triaged rather than implemented as filed: the premise is false against current master, verified with a real reproduction rather than by re-reading the same source the original triage did.

What the code actually guarantees

OpenBoard is payload-keyed (BRIDGE_MODEL_KEY(BoardModel, OpenBoard, &OpenBoard::projectId), board_model.hpp:436). BridgeHandler::execute() (morph/core/bridge.hpp:1967-2007) routes a payload-keyed action through Bridge::attachHandlerAsync, whose own doc comment on execute() (:1948-1952) is explicit: "A payload- or result-keyed action's attach/promote step never throws out of this call, even when the backend refuses it ... the failure is instead delivered through the returned Completion's .onError(...)." The dispatch itself runs from inside the attach's completion callback (:1996-2006) — there is no window where openBoard() can observe "handler not bound", in any mode, because it never takes the fail-fast executeVia path that produces that error at all. That path (:2043) is reserved for non-keyed actions on an already-attached handler.

attachHandlerAsync predates the issue by a wide margin (#42, merged well before master@9371c1a0) — this is not a fix that landed after the issue was filed, the guarantee already held when it was triaged.

The original triage (comment on #305) was read-only by its own admission ("I did not stand up a fresh Socket-mode repro myself") and reasoned from executeVia's fail-fast contract without noticing execute()'s keyed-action branch dispatches through a different path entirely.

Verification

New test in test_board_presenter.cpp drives a real Mode::Socket client through FaultProxy, with the attach reply itself delayed 300ms via delayReply() — the most adversarial timing this rung's transport can produce — then calls openBoard() the instant the presenter is constructed, exactly the ordering #305 describes. It reports the board correctly once the delayed reply lands; failed() never fires.

$ ctest kanban gui presenter tag, cl-qt-debug (MSVC)
All tests passed (103 assertions in 13 test cases)

$ full ladder_kanban_tests suite
All tests passed (1038 assertions in 136 test cases)

Change

No production code changes — the guarantee already held. Adds one regression test locking it in, so a future change to execute()'s keyed-action path that reintroduced the race would be caught here.

Closes #305 (as invalid — see the issue comment with full evidence).

🤖 Generated with Claude Code

…rd()

morph#305 claimed BoardBridge::openBoard() dispatched right after
construction races the handler's registration and can fail fast with
"handler not bound", inferred from reading rather than reproduced.

It cannot: OpenBoard is payload-keyed (BRIDGE_MODEL_KEY, board_model.hpp),
so BridgeHandler::execute() routes it through Bridge::attachHandlerAsync
(bridge.hpp, landed in #42, predating this issue), whose own contract is
that a keyed action's attach step never throws "handler not bound" out
of execute() -- it waits for the attach round trip and dispatches from
inside that completion, regardless of mode.

Verified rather than assumed: a new test drives a real Socket-mode
client through FaultProxy with the attach reply itself delayed 300ms,
then calls openBoard() the instant the presenter is constructed -- the
most adversarial ordering the issue describes. It reports the board
correctly once the delayed reply lands; no failed() ever fires.

No production change: the guarantee already held. This closes morph#305
with the counter-evidence.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Yaraslaut
Yaraslaut merged commit 820ca07 into master Aug 28, 2026
37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

kanban: openBoard() is dispatched without waiting for its handler registration to bind (Remote mode)

1 participant