kanban: honour the projectId CreateRule and GetRules already require - #381
Merged
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Both DTOs carry a projectId their own validate() insists be present, and
BoardModel::execute() consulted neither: the board came from the handler's
attach state alone. A handler attached to project A, asked for project B's
rules, answered with A's and an ok; asked to create a rule on B, it created
one on A and reported its id. Neither answer could be told apart from having
been served, so a client that passed the id it meant was reading -- or
writing -- the wrong board with no way to notice.
The argument is now checked against the attached board and a mismatch refused
with "projectId does not match the attached board", in the same shape, error
type and message style as this file's other cross-project refusals ("rule
does not belong to this project", "column does not belong to this project").
The field is honoured rather than dropped because GetRules' sibling reads on
ProjectAdminModel (GetProjectRoles) take a projectId and mean it.
OpenBoard is the third action in this model taking a projectId and always
meant it -- it is the keyed attach action, and it is what the check compares
against. No other action here carries one.
Nothing was disclosed that a caller could not already read: the handler is
keyed by project, and requireRole runs against the attached board before the
new check, so the refusal cannot be used to probe projects the caller has no
standing on.
scripts/scenario/scenarios/kanban/rules-created-listed-and-deleted.scenario
pinned the old behaviour on purpose and cited this defect; it now pins the
refusal, on the writing half as well as the reading one.
Fixes #369
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GnSnsebJ92Kti1D7m3RwYJ
Yaraslaut
force-pushed
the
fix/369-kanban-project-id
branch
from
September 1, 2026 15:43
2fc000a to
47f2160
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #369.
GetRulescarried aprojectId,validate()insisted it be present, and the implementation never consulted it — the query was built from the handler's attached board. A handler attached to project A, asked for project B's rules, answered with A's, successfully and silently. The argument was load-bearing in the type and inert in the code.CreateRulehad it too, and it is a writeThe issue asked for
CreateRuleto be checked for the same thing. It has it, and it is worse: attached to project A withprojectId=Band A's own trigger column,CreateRulecreated the rule on A and returned its id. Measured, not inferred — pre-fix,CHECK(GetRules{A}.rules.empty())came backfalseafter a create that should have been refused.board_model.cppcarries exactly three actions with aprojectId. The third,OpenBoard, is correct as it stands: it is the keyed attach action (BRIDGE_MODEL_KEY), and its id is what the other two are now compared against.ProjectAdminModel's three (SetMemberRole,RemoveMember,GetProjectRoles) are a different file and already mean it.The fix
A new
requireProjectMatchesAttachedBoard, sibling of the existingrequire*BelongsToProjecthelpers in the same file, throwingNotFound{"projectId does not match the attached board"}. Both call sites sit after the attach andrequireRolegates, so the new refusal cannot be used to probe projects the caller has no standing on.CreateRule's sits inside its existingtry, so the refusal is journalledFailedlike every other refusal there.The doc comments that asserted the old behaviour are corrected rather than left to contradict the code:
board_model.hpp's@param action Unused — not consulted, both DTOs inrule_dto.hpp, andboard_presenter.hpp's_projectIdcomment. The GUI is unaffected —BoardPresenter::openBoard()stashes exactly the id it opens with.Verification
Reproduced in-process, pre-fix:
test cases: 7 | 5 passed | 2 failed, bothbecause no exception was thrown where one was expected, plus theCreateRulewrite landing on the wrong project.Reproduced on the wire, pre-fix server,
GetRules projectId=20while attached to project 19:Post-fix:
[cross-tenant]All tests passed (11 assertions in 7 test cases); full rungAll tests passed (1025 assertions in 139 test cases).run_scenarios.py --rung kanban --twicegreen on both passes;--mutatereports no surviving mutants, so the new assertions are load-bearing.test_morph_scenario.pyandscenario_coverage.pyboth exit 0, kanban still 22/22 actions and 20/20 workflows.scripts/scenario/scenarios/kanban/rules-created-listed-and-deleted.scenariopinned the old behaviour on purpose and cited this issue. It now asserts the refusal — forGetRulesand for theCreateRulewrite — and its header comment describes the fixed behaviour.Not verified: the kanban test binary could not be built under
-Werrorin the agent's worktree (#372/#376 — AUTOMOC's relative include escaping a nested worktree, unrelated to this change), so the tests were run from a non-strict build dir.board_model.cppitself was rebuilt under the full strict config and links clean. No GUI run beyond the presenter suite.🤖 Generated with Claude Code
https://claude.ai/code/session_01GnSnsebJ92Kti1D7m3RwYJ