Skip to content

kanban: an unattached BoardModel replies "stoull", and two guards written to prevent that never fire #368

Description

@Yaraslaut

What happens

BoardModel is keyed by project id, so a freshly registered handler is attached to no board. Every action dispatched in that state answers with the bare string stoull — the what() of the std::invalid_argument that std::stoull("") throws — delivered as though it were a domain error.

send register typeId=BoardModel      -> ok
execute GetBoardState                -> err "stoull"
execute CreateColumn name="Todo"     -> err "stoull"
execute CreateTask ...               -> err "stoull"
execute MoveTaskPosition ...         -> err "stoull"
execute AddComment ...               -> err "stoull"
execute GetEventsSince lastEventId=0 -> err "stoull"
execute GetActivity                  -> err "stoull"
execute GetRules projectId=1         -> err "stoull"

The part that makes it worth an issue

Two of those actions already have a guard with exactly the right message. GetActivity:

// examples/kanban/src/models/board_model.cpp
GetActivityResult BoardModel::execute(const GetActivity& /*action*/) {
    if (!_projectIdStr.has_value()) {
        throw NotFound{"GetActivity: handler was never attached via OpenBoard"};
    }
    requireRole(Role::Viewer);
    ...

and GetRules has the same shape. Neither ever fires. On an unattached handler _projectIdStr is engaged with an empty string, so has_value() is true, control falls through to requireRole, and requireRole reaches std::stoull(*_projectIdStr).

That is worse than having no guard: reading the source suggests the good message exists, so the behaviour looks correct until someone drives it. polls::PollModel gets this right and answers "GetPollState: handler was never attached via OpenPoll", which is the message a client can act on and the one this rung appears to have been copying.

Why it matters to a client

stoull names nothing: not the action, not the model, not the mistake. A client cannot distinguish "I forgot to call OpenBoard" from an internal fault, and cannot present anything useful. It also leaks an implementation detail of the id representation into the wire contract.

Suggested fix

Make the sentinel unambiguous — either leave _projectIdStr disengaged until OpenBoard succeeds, or have requireRole (and the other stoull call sites, board_model.cpp:304, 333, 347, 390, 432, 490, …) check for an empty string and throw the same NotFound{"<Action>: handler was never attached via OpenBoard"} the two existing guards already spell. Whichever way, the two guards that exist today should start firing.

Where it is pinned

scripts/scenario/scenarios/kanban/a-board-must-be-opened-before-it-answers.scenario asserts the current replies per action, including that the two guarded ones do not mention OpenBoard, so a fix has to come through that file.

Found by the out-of-process scenario corpus. The in-process tests attach before acting, so no test had reached this state over a server.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: ladderSubsystem: ladderbugSomething isn't workingtriage: rescopeReal problem, wrong framing; rewrite before building

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions