Skip to content

[Backend][Product-safety] Archived boards accept card writes - no service-layer IsArchived guard #2080

Description

@Chris0Jeky

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:

File IsArchived occurrences
backend/src/Taskdeck.Application/Services/CardService.cs 0
backend/src/Taskdeck.Application/Services/ColumnService.cs 0
backend/src/Taskdeck.Application/Services/LabelService.cs 0

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:

  1. The UI asserts a safety property the backend does not hold. [Backend][UX] Archiving a board makes its captures and applied-proposal ledger vanish from every list with no disclosure #1973's disclosure work is built on the premise that an archived board is inert — that is what makes "your captures and decision records are retained, restore the board to act on them" a true statement. Today a user can act on them without restoring, and nothing says so.
  2. 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:

  1. 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.
  2. 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.
  3. Explicit unarchive-and-edit affordance. Treat archival as a filing state rather than a lock, and make editing an archived board a deliberate, disclosed act. Legitimate — but it contradicts the copy [Backend][UX] Archiving a board makes its captures and applied-proposal ledger vanish from every list with no disclosure #1973 just shipped, so choosing it means revisiting that wording too.

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

No milestone set — this needs the v0.1.2-vs-later call.

Metadata

Metadata

Assignees

No one assigned

    Labels

    backendPrimary implementation impact in .NET API/domain/application/infrastructure.dogfoodingFrom real personal/beta use; evidence-grade product feedback (exempt from intake severity bar).hardeningReliability, safety, operability, and production-readiness improvements.product-truthProduct or docs claim something unsupported, misleading, or silently untrue; truth-repair work.

    Projects

    Status
    Pending

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions