Round out CRUD across MuseHub — consistent soft-delete + hard-delete idiom
Background
Discovered while cleaning up local test data created for issue #139's layout verification: there is no way to actually delete an issue, and no way to delete a proposal comment, via the CLI or API. Auditing the actual model layer (not assumptions) shows the real, inconsistent state:
| Entity | Delete concept in the model | CLI/API wired |
|---|---|---|
| Issue | None — only state (open/closed) |
N/A — nothing to wire |
| Issue comment | is_deleted (soft) |
Yes — muse hub issue comment-delete |
| Proposal | None — only state (open/merged/closed) |
N/A — nothing to wire |
| Proposal comment | is_deleted (soft) |
No — field exists, unused |
This isn't a deliberate "we soft-delete everywhere" decision that's just
missing hard-delete — issues and proposals were never given delete
semantics at all. Closing an issue changes state, not is_deleted; there
is no column to hard- or soft-delete against. Issue comments got it right
(soft-delete field + wired CLI). Proposal comments got half of it (field
exists, nothing wired). This ticket is the parent for bringing every
entity up to the same standard, deliberately, instead of continuing to
discover gaps by accident during cleanup.
Goal
One consistent, framework-wide idiom:
- Soft-delete (
is_deletedflag, or equivalent) on every entity that can meaningfully be removed by a normal user/agent action — matching the issue-comment pattern already proven to work. - Hard-delete as a deliberate, explicit escape hatch on top of that —
gabriel's ask: "an idiomatic framework-wide way of hard-deleting, just
a flag, something that ports across the entire framework." Concretely:
a
--hardflag (or?hard=trueat the API layer) on the delete operation, gated to owner/admin, that actually removes the row instead of flippingis_deleted. Same flag name, same semantics, on every entity's delete command — not a bespoke mechanism per entity. - Applies to issues and proposals too, not just their comments — right now there is no delete path for either at all, soft or hard.
Scope for this ticket
This parent ticket tracks the decision + the framework-wide mechanism
(the --hard idiom itself, where it lives in the codebase, how it's
enforced/authorized). Implementation for specific entities happens in
child tickets so the work can be split (gabriel + aaronrene) without
everyone touching the same shared mechanism at once.
Child tickets
- musehub#142 — Issues: add delete capability (soft + hard)
- musehub#143 — Proposal comments: wire the existing
is_deletedfield end-to-end (API + CLI), matching issue comments' pattern
Related
- musehub#140 (duplicate issue numbers → 500 on read/close) is related — discovered in the same cleanup session — but already filed and scoped separately. Not a child of this ticket; it's a data-integrity bug, not a missing CRUD operation.
Non-goals
- Retroactively hard-deleting anything that already exists via a one-off script or direct DB access — the point of this ticket is to make the proper path available going forward (e.g. for the local test issues from #139, gabriel is explicitly waiting for this to ship rather than doing a direct Postgres delete).