Vikunja #137 — Four-guard state model spec (terminal/done, blocked/unblock, running/watcher-owner, done/no-rewatch) #5
Labels
No labels
blocked:verify-failed
bug
in-progress
triage-approved
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Mark/kanban#5
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Tracking issue for the four-guard state model spec being implemented on Mark/kanban (plan §1 / Vikunja #137). Closes when all four rules are enforced in code with explicit guards and the four named integration tests pass.
Four rules
donestate (validated, archived, failed) setsterminal=trueon the card; transitions out ofdoneare rejected at the guard layer regardless of source.blockedcards only return to active via an explicitkanban_unblockevent; nothing else flips them back.runningis owned exclusively by the watcher from the moment it's set until it reaches a terminal state; no other actor may transition a running card.donecards on entry and never re-subscribes them.Implementation status (on Mark/kanban)
All four rules are enforced in code with explicit guards on branch
137-kanban-guards-and-watcher-branches@a0bc081d9a53d77d9a1e294ab275ef64864c377f(also merged tomainas commit4b983dcvia PR #2 on 2026-09-12). 157/157 vitest tests pass locally on the branch HEAD.Rule-to-guard mapping
server/transition_guards.ts::computeFlagUpdatesForTransition(line 91:terminal: 1on done entry; line 132: reject done→X withkind: done_is_terminal) +server/storage.ts::transitionTask(line 290: atomic flag update)four_guards_integration.test.ts:142— 2 testsserver/watcher.ts::branch_blocked(line 150: unsubscribe, only re-add onkanban_unblockevent at line 287) +server/storage.ts::transitionTask(line 236: rejects blocked→active with typed error referencingkanbanUnblock) +server/storage.ts::kanbanUnblock(line 55: sole handler for blocked→assigned)four_guards_integration.test.ts:251— 3 testsserver/transition_guards.ts::assertCallerAllowedForRunning(rejects non-watcher withkind: foreign_running_write) +server/watcher.ts::branch_running(line 170: tick-entry assertioncard.owner !== WATCHER_PRINCIPAL) +server/principal.ts::deriveCallerFromRequest(HTTP trust boundary, body-supplied caller ignored)four_guards_integration.test.ts:382— 5 testsserver/watcher.ts::branch_done(line 131:drop_doneaction;_watchSetmutation only at line 181 [running dispatch] and line 287 [kanban_unblockarm]) +KANBAN_EVENT_CATALOGexposes no event that re-adds a done cardfour_guards_integration.test.ts:552— 3 testsDiff stats vs. origin/main
13 files changed, 3612 insertions(+), 22 deletions(-).
shared/schema.ts—terminal+ownercolumns ontasksmigrations/0001_add_terminal_owner.sql— schema migration with safe defaultsserver/transition_guards.ts(new) — pure-logic guard module with typedTransitionGuardError{kind:…}server/principal.ts(new) — server-derived caller trust boundaryserver/storage.ts—transitionTaskruns guard before ALLOWED_TRANSITIONS;kanbanUnblock()storage method;setTaskFlags()server/routes.ts—X-Internal-Actorheader → caller derivation; PATCH/flagsroute; 409 surfaceserver/watcher.ts(new) — in-processWatcherclass withSweep()three-branch logic +deliverKanbanEvent()catalogserver/__tests__/four_guards_integration.test.ts(new) — 17 tests across 5 describe blocks covering (a)(b)(c)(d) plus (e) owner-assertionPRs
4b983dc)Acceptance
kanban_unblockis the only path out ofblockeddoneverified by test4b983dc) — run #6623 success, 93sRefs: kanban t_d3f147f9 (root), t_e49638bb (implementer), t_002084ad/t_c657a9cc/t_383734ad/t_fc944a5a (children), t_7fd87e14 (verifier).
PR #4 opened with the proper title + rule-to-guard mapping:
PR: #4
Note: PR #2 was the merge PR (auto-generated title "Merge 137-kanban-guards-and-watcher-branches -> main"), already merged to main on 2026-09-12 16:51:50 UTC as commit
4b983dc. PR #4 captures the rule-to-guard mapping table that PR #2's auto-description lacked.Rule-to-guard summary (full table in PR #4 description)
doneis terminal; no transition out ofdoneserver/transition_guards.ts::computeFlagUpdatesForTransitionsetsterminal: 1on done entry; line 132 rejects done→X withkind: done_is_terminal. Wired intoserver/storage.ts::transitionTask(line 290, atomic flag update). 409 surface atserver/routes.ts.four_guards_integration.test.ts:142— 2 testsblockedonly returns to active viakanban_unblockserver/watcher.ts::branch_blocked(line 150, unsubscribe; re-add only onkanban_unblockevent indeliverKanbanEventline 287) +server/storage.ts::transitionTask(line 236, rejects blocked→active with typed error referencingkanbanUnblock) +server/storage.ts::kanbanUnblock(line 55, sole handler)four_guards_integration.test.ts:251— 3 testsrunningis owned exclusively by the watcherserver/transition_guards.ts::assertCallerAllowedForRunning(rejects non-watcher withkind: foreign_running_write, missing/null caller withkind: missing_caller_for_running) +server/watcher.ts::branch_running(line 170, tick-entry assertioncard.owner !== WATCHER_PRINCIPALthrowsWatcherError{kind: foreign_running_write}) +server/principal.ts::deriveCallerFromRequest(HTTP trust boundary, body-supplied caller ignored, server-derived fromX-Internal-Actorheader)four_guards_integration.test.ts:382— 5 testsserver/watcher.ts::branch_done(line 131,drop_doneaction;_watchSet.addonly at line 181 [legit running dispatch] and line 287 [kanban_unblockarm]) +KANBAN_EVENT_CATALOGexposes no event that re-adds a done cardfour_guards_integration.test.ts:552— 3 testsCI status
CI on the merge commit (
4b983dc) is GREEN — run #6623, status=success, 93 seconds duration, triggered by push to main on 2026-09-12 17:55:51Z → 2026-09-12 17:57:24Z. URL: https://forgejo.tail018ac4.ts.net/Mark/kanban/actions/runs/2.The PR #4 head (
a0bc081) is an ancestor of the merge commit (4b983dc) and contains the same code that CI verified — 157/157 vitest pass locally on the branch HEAD.Acceptance
kanban_unblockis the only path out ofblocked✓doneverified by test (5 sweeps, no re-subscribe) ✓Refs: kanban t_d3f147f9 (root), t_e49638bb (implementer), t_002084ad/t_c657a9cc/t_383734ad/t_fc944a5a (children), t_7fd87e14 (verifier).
Closing — tracking issue for the four-guard state model spec (Vikunja #137). Implementation tracked in Vikunja / kanban card body.