You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Surfaced by the Codex review of PR #2076 (P1 thread) while closing #1973. PR #2076 fixed the reachable route; this issue is the underlying capability, which it deliberately did not touch.
The gap
Archiving a board does not stop anything from writing to it. Card, column, and label mutations against an archived board are accepted and persisted, with no restoration required and no error.
Direct evidence (measured on 14f6b01dc)
No service-layer guard on the interactive CRUD path.IsArchived appears zero times in all three:
No client-side gate either.frontend/taskdeck-web/src/views/BoardView.vue and src/views/paper/PaperBoardView.vue contain no isArchived reference, so add-card, edit, move, column, drag/drop, and keyboard mutation controls all render normally on an archived board.
The inconsistency is the sharpest part. Three other write paths already refuse archived boards, using the same ErrorCodes.InvalidOperation shape:
ExternalImportService.cs:100 — "Cannot import into an archived board."
StarterPackApplyService.cs:62 — "Cannot apply a starter pack to an archived board."
RestorePlanner.cs:72 — "Cannot restore to an archived board"
So the codebase has already decided that archived boards are not valid write targets — for bulk and import paths. The interactive path that a user actually reaches simply never had the check applied. This reads as an oversight rather than a deliberate asymmetry, which is why it is worth a tracked decision rather than a silent patch.
Boundary distinction — what PR #2076 did and did not do
PR #2076 (#1973) makes an archived board's captures and decision ledger reachable as read-only history, and its copy tells the user to restore the board before creating, editing, or triaging work.
Closed: the discoverable route. readOnly now propagates to ReviewProposalDetails, which withholds the "Open Board" control in history mode, so the read-only surface no longer hands the user an editable board two clicks from an Archive row.
Not closed: the write capability itself. Anyone who remembers or bookmarks /boards/{id}, follows a stale link, or calls the API directly still gets a fully editable archived board. ArchiveView exposes no direct board link, so before [Backend][UX] Disclose and expose archived board capture and decision history (#1973) #2076 this was reachable but undiscoverable; it remains reachable now.
Fixing the route was in scope for a disclosure PR. Fixing the capability is a product-safety decision with its own blast radius, so it is filed here instead.
Why it matters to the trust model
Taskdeck's shipped posture is review-first with no silent or destructive mutations (ADR-0003, GP-06, ADR-0056). Two things sit awkwardly against that:
A decision ledger whose board is still mutable is a weaker record. The applied-proposal history is meant to be the account of what was approved and when. If the board it describes can drift after archival without any restore step or audit prompt, the ledger and the board can disagree with no trace of why.
Neither is a live data-loss report — there is no evidence of anyone hitting this — but it is a stated invariant that is not enforced.
Open design question (likely wants a small ADR)
Three plausible answers, and they differ in more than implementation cost:
Hard reject. Return 409 / ErrorCodes.InvalidOperation for writes to an archived board, matching the three existing precedents exactly. Cheapest and most consistent — but it will surface as errors on any path that currently writes to archived boards without knowing it, so it wants a survey of callers (MCP tools and the CLI included, not just the web client) before it lands.
Restore-first flow. Keep the board reachable read-only and make the UI offer "Restore to edit" wherever a mutation is attempted. Best product story, most work, and it needs the client gating that neither board view has today.
Option 1 is the most defensible default given the existing precedents, but the caller survey is the part that decides whether it is safe, and that is exactly the sort of call worth recording rather than assuming.
Worth noting an ADR here would also settle what archiving means in this product, which is currently implied by three scattered guards rather than stated anywhere.
The gap
Archiving a board does not stop anything from writing to it. Card, column, and label mutations against an archived board are accepted and persisted, with no restoration required and no error.
Direct evidence (measured on
14f6b01dc)No service-layer guard on the interactive CRUD path.
IsArchivedappears zero times in all three:IsArchivedoccurrencesbackend/src/Taskdeck.Application/Services/CardService.csbackend/src/Taskdeck.Application/Services/ColumnService.csbackend/src/Taskdeck.Application/Services/LabelService.csNo client-side gate either.
frontend/taskdeck-web/src/views/BoardView.vueandsrc/views/paper/PaperBoardView.vuecontain noisArchivedreference, so add-card, edit, move, column, drag/drop, and keyboard mutation controls all render normally on an archived board.The inconsistency is the sharpest part. Three other write paths already refuse archived boards, using the same
ErrorCodes.InvalidOperationshape:ExternalImportService.cs:100—"Cannot import into an archived board."StarterPackApplyService.cs:62—"Cannot apply a starter pack to an archived board."RestorePlanner.cs:72—"Cannot restore to an archived board"So the codebase has already decided that archived boards are not valid write targets — for bulk and import paths. The interactive path that a user actually reaches simply never had the check applied. This reads as an oversight rather than a deliberate asymmetry, which is why it is worth a tracked decision rather than a silent patch.
Boundary distinction — what PR #2076 did and did not do
PR #2076 (#1973) makes an archived board's captures and decision ledger reachable as read-only history, and its copy tells the user to restore the board before creating, editing, or triaging work.
readOnlynow propagates toReviewProposalDetails, which withholds the "Open Board" control in history mode, so the read-only surface no longer hands the user an editable board two clicks from an Archive row./boards/{id}, follows a stale link, or calls the API directly still gets a fully editable archived board.ArchiveViewexposes no direct board link, so before [Backend][UX] Disclose and expose archived board capture and decision history (#1973) #2076 this was reachable but undiscoverable; it remains reachable now.Fixing the route was in scope for a disclosure PR. Fixing the capability is a product-safety decision with its own blast radius, so it is filed here instead.
Why it matters to the trust model
Taskdeck's shipped posture is review-first with no silent or destructive mutations (ADR-0003, GP-06, ADR-0056). Two things sit awkwardly against that:
Neither is a live data-loss report — there is no evidence of anyone hitting this — but it is a stated invariant that is not enforced.
Open design question (likely wants a small ADR)
Three plausible answers, and they differ in more than implementation cost:
409/ErrorCodes.InvalidOperationfor writes to an archived board, matching the three existing precedents exactly. Cheapest and most consistent — but it will surface as errors on any path that currently writes to archived boards without knowing it, so it wants a survey of callers (MCP tools and the CLI included, not just the web client) before it lands.Option 1 is the most defensible default given the existing precedents, but the caller survey is the part that decides whether it is safe, and that is exactly the sort of call worth recording rather than assuming.
Worth noting an ADR here would also settle what archiving means in this product, which is currently implied by three scattered guards rather than stated anywhere.
Refs
14f6b01dcis the commit that withholds "Open Board"No milestone set — this needs the v0.1.2-vs-later call.