Space deletion: Deactivate → Delete, contents to trash, name tombstone (#268 decisions) #286

Closed
opened 2026-08-19 21:02:51 +00:00 by Cordy · 4 comments
Owner

Dogfood finding (2026-08-19, v0.6.51): the owner of a space (e.g. Fall 3) tries to delete it from the Spaces file listing and gets "Spaces are managed containers and cannot be deleted from the file listing." That guard is correct — but there is currently no way to delete a space at all: spacestore.Delete exists but is metadata-only and nothing exposes it. This issue implements the lifecycle decided in #268.

Decided model (#268, 2026-08-18):

  • Deactivate → Delete, two deliberate steps. Deactivate hides the space from members (owner + admins still see it, read-only) and is reversible. Delete is only offered on a deactivated space.
  • Delete moves the space's contents to trash under the normal trash-retention window — recoverable via Manual purge / restore until the window runs out or an admin purges. Legal holds and retention windows on paths inside the space block final deletion exactly as everywhere else.
  • Name tombstone: the space name stays reserved for the trash-retention window (= the tombstone window, one knob). Prevents the name-reuse resurrection pitfall (per-name group keys: a recreated space with the same name must not inherit the old key or old trashed content).
  • Group key retires at final purge, not at delete.

Implementation sketch:

  • spacestore: Deactivate(name) / Reactivate(name) flags + Delete(name) grows the content move (space tree → trash entries) and writes the tombstone {name, deletedAt, keepUntil}; Create refuses tombstoned names with a clear error.
  • API: POST /api/v1/spaces/{name}/deactivate, POST /api/v1/spaces/{name}/reactivate, DELETE /api/v1/spaces/{name} — all ownerOrAdmin, all audited (space-deactivate, space-delete).
  • Scope/route A: if IdP group bindings exist, deactivation must not delete IdP groups (route A demoted to opt-in per #268); stale peering recvSpaces must tolerate a deactivated/deleted space (listed pitfall).
  • UI (mockup-first, per house process): Spaces management page gets per-space Deactivate / Delete with a confirmation dialog in the v0.6.52 style (contents summary, retention note, red confirm); the file-listing guard message gains "manage spaces on the Spaces page" pointing at the real flow.
  • Docs: sharp-edge note — backup story vs. tombstones (from #268 corollaries).

Out of scope: key rotation on reactivation; bulk space archival.

**Dogfood finding (2026-08-19, v0.6.51):** the owner of a space (e.g. `Fall 3`) tries to delete it from the Spaces file listing and gets "Spaces are managed containers and cannot be deleted from the file listing." That guard is correct — but there is currently **no way to delete a space at all**: `spacestore.Delete` exists but is metadata-only and nothing exposes it. This issue implements the lifecycle decided in #268. **Decided model (#268, 2026-08-18):** - **Deactivate → Delete**, two deliberate steps. Deactivate hides the space from members (owner + admins still see it, read-only) and is reversible. Delete is only offered on a deactivated space. - **Delete moves the space's contents to trash** under the normal trash-retention window — recoverable via Manual purge / restore until the window runs out or an admin purges. Legal holds and retention windows on paths inside the space block final deletion exactly as everywhere else. - **Name tombstone:** the space name stays reserved for the trash-retention window (= the tombstone window, one knob). Prevents the name-reuse resurrection pitfall (per-name group keys: a recreated space with the same name must not inherit the old key or old trashed content). - **Group key retires at final purge**, not at delete. **Implementation sketch:** - `spacestore`: `Deactivate(name)` / `Reactivate(name)` flags + `Delete(name)` grows the content move (space tree → trash entries) and writes the tombstone `{name, deletedAt, keepUntil}`; `Create` refuses tombstoned names with a clear error. - API: `POST /api/v1/spaces/{name}/deactivate`, `POST /api/v1/spaces/{name}/reactivate`, `DELETE /api/v1/spaces/{name}` — all `ownerOrAdmin`, all audited (`space-deactivate`, `space-delete`). - Scope/route A: if IdP group bindings exist, deactivation must not delete IdP groups (route A demoted to opt-in per #268); stale peering `recvSpaces` must tolerate a deactivated/deleted space (listed pitfall). - UI (mockup-first, per house process): Spaces management page gets per-space Deactivate / Delete with a confirmation dialog in the v0.6.52 style (contents summary, retention note, red confirm); the file-listing guard message gains "manage spaces on the Spaces page" pointing at the real flow. - Docs: sharp-edge note — backup story vs. tombstones (from #268 corollaries). **Out of scope:** key rotation on reactivation; bulk space archival.
Author
Owner

Shipped as v0.6.54 (PR #289), live on both dogfoods. Built exactly on the #268 decisions; lifecycle tests (deactivate/reactivate roles + visibility, tombstone blocking + expiry) green in CI.

How it works:

  • ⋯ menu on a space row (/spaces listing, owner or admin): Details, then Deactivate space — reversible; the space vanishes for members while the owner keeps read-only sight (admins see everything via the unscoped view). A deactivated space's menu offers Reactivate and the red Delete space….
  • Delete opens the confirmation dialog (contents → Deleted files for the retention window; name reserved; holds protection), then moves /spaces/<name> into the deleting user's trash as one recoverable entry (visible in Admin → Deleted files → Manual purge) and tombstones the record.
  • Recreating the name before the reservation expires → 409 "reserved by a recently deleted space".
  • Audit: space-deactivate, space-reactivate, space-delete.

Known edges (accepted v1): restoring the trashed space folder restores files only, not membership — the tombstone still blocks recreating the space until expiry; an early manual purge doesn't shorten the name reservation; IdP-managed (route A) spaces answer 501 on lifecycle routes. Deviation from the mockup: no "deactivated" pill on the listing row yet — the state shows in the menu; pill is a candidate for a follow-up polish once row decoration is scouted.

Dogfood checklist (fresh login):

  1. ⋯ on a test space → Deactivate → space disappears for a member account (e.g. bao-side user), still listed read-only for you
  2. Reactivate → member access restored (member may need re-login for session-cached groups)
  3. Deactivate → Delete → dialog shows retention days → space gone, one big entry in Manual purge owned by you
  4. Try creating a space with the same name → refused with the reservation message
  5. Deleting from the file listing still guards, now pointing at the ⋯ menu
Shipped as **v0.6.54** (PR #289), live on both dogfoods. Built exactly on the #268 decisions; lifecycle tests (deactivate/reactivate roles + visibility, tombstone blocking + expiry) green in CI. **How it works:** - ⋯ menu on a space row (`/spaces` listing, owner or admin): Details, then **Deactivate space** — reversible; the space vanishes for members while the owner keeps read-only sight (admins see everything via the unscoped view). A deactivated space's menu offers **Reactivate** and the red **Delete space…**. - Delete opens the confirmation dialog (contents → Deleted files for the retention window; name reserved; holds protection), then moves `/spaces/<name>` into the deleting user's trash as **one recoverable entry** (visible in Admin → Deleted files → Manual purge) and tombstones the record. - Recreating the name before the reservation expires → 409 "reserved by a recently deleted space". - Audit: `space-deactivate`, `space-reactivate`, `space-delete`. **Known edges (accepted v1):** restoring the trashed space folder restores files only, not membership — the tombstone still blocks recreating the space until expiry; an early manual purge doesn't shorten the name reservation; IdP-managed (route A) spaces answer 501 on lifecycle routes. Deviation from the mockup: no "deactivated" pill on the listing row yet — the state shows in the menu; pill is a candidate for a follow-up polish once row decoration is scouted. **Dogfood checklist (fresh login):** 1. ⋯ on a test space → Deactivate → space disappears for a member account (e.g. bao-side user), still listed read-only for you 2. Reactivate → member access restored (member may need re-login for session-cached groups) 3. Deactivate → Delete → dialog shows retention days → space gone, one big entry in Manual purge owned by you 4. Try creating a space with the same name → refused with the reservation message 5. Deleting from the file listing still guards, now pointing at the ⋯ menu
Author
Owner

UI polish round decided (2026-09-03, operator review of the shipped lifecycle; mockups in space-lifecycle-ui-mockup.html):

  1. Delete dialog → variant A2 (consequence list + type-to-confirm). Space name in the title in normal type (no mono/colour); the three consequences become icon rows — contents to Deleted files, name tombstone, holds/retention protection; the danger button stays disabled until the space name is typed back (matches the admin inventory's name confirmation). Retention copy is dynamic, from the live trash settings: auto-purge on → "Recoverable for {n} days, then removed by the normal sweep"; auto-purge off (the default) → "Recoverable until an administrator purges them — nothing is removed automatically". The tombstone row states the same window. A user must never read "90 days" when the admin set 30 — or set never.
  2. Spaces listing → variant B2 (deactivated group). Active spaces list first; deactivated ones sink under an amber "Deactivated" divider, rows dimmed with an amber chip and the sub-line "Hidden from members · you keep view-only access" replacing the item count. The row visibly moving down on deactivation is the immediate feedback. Footer gains "· N space(s) deactivated". Amber = reversible; red stays destruction-only.
  3. Inside a deactivated space → variant C. The breadcrumb's bare "View only" gains a sibling amber "Deactivated" chip (cause + effect), and owners/admins see a one-line banner — "This space is deactivated. Members cannot see it; nothing was deleted." — with an inline Reactivate button, so undoing doesn't require re-finding the ⋯ menu. Members see nothing (the space is hidden for them anyway).

Backend note: the /spaces listing rows and the in-space listing response need a deactivated flag stamped from the spacestore (fillLocks-style enrichment, no N+1); everything else is frontend + i18n ×4.

**UI polish round decided (2026-09-03, operator review of the shipped lifecycle; mockups in `space-lifecycle-ui-mockup.html`):** 1. **Delete dialog → variant A2 (consequence list + type-to-confirm).** Space name in the title in normal type (no mono/colour); the three consequences become icon rows — contents to Deleted files, name tombstone, holds/retention protection; the danger button stays disabled until the space name is typed back (matches the admin inventory's name confirmation). **Retention copy is dynamic**, from the live trash settings: auto-purge on → "Recoverable for {n} days, then removed by the normal sweep"; auto-purge off (the default) → "Recoverable until an administrator purges them — nothing is removed automatically". The tombstone row states the same window. A user must never read "90 days" when the admin set 30 — or set never. 2. **Spaces listing → variant B2 (deactivated group).** Active spaces list first; deactivated ones sink under an amber "Deactivated" divider, rows dimmed with an amber chip and the sub-line "Hidden from members · you keep view-only access" replacing the item count. The row visibly moving down on deactivation is the immediate feedback. Footer gains "· N space(s) deactivated". Amber = reversible; red stays destruction-only. 3. **Inside a deactivated space → variant C.** The breadcrumb's bare "View only" gains a sibling amber "Deactivated" chip (cause + effect), and owners/admins see a one-line banner — "This space is deactivated. Members cannot see it; nothing was deleted." — with an inline **Reactivate** button, so undoing doesn't require re-finding the ⋯ menu. Members see nothing (the space is hidden for them anyway). Backend note: the `/spaces` listing rows and the in-space listing response need a `deactivated` flag stamped from the spacestore (fillLocks-style enrichment, no N+1); everything else is frontend + i18n ×4.
Cordy referenced this issue from a commit 2026-09-03 02:09:16 +00:00
Author
Owner

UI polish round shipped — v0.6.114 (PR #407), live on both dogfoods.

  • A2 delete dialog: consequence rows with icons, name in the title in normal type, type-the-name-to-confirm gating the danger button. The recoverability line is dynamic and honest: configured retention window when the sweep is on, "recoverable until an administrator purges them — nothing is removed automatically" when it's off (the default). New neverPurge field on GET /api/v1/spaces/{name}/state.
  • B2 listing: deactivated stamped on /spaces rows (fillSpaceStates, in-memory, owner/admin-only by construction); deactivated spaces sink below an amber "Deactivated" divider, dimmed, amber chip, "Hidden from members · you keep view-only access" sub-line; footer counts them.
  • C in-space: listing response gains spaceDeactivated; amber "Deactivated" chip beside "View only" in the crumb, plus a banner with inline Reactivate.

Dogfood checklist (fresh login — in-memory sessions):

  1. ⋯ on a deactivated-able space → Deactivate → row moves under the amber divider, dimmed, chip + sub-line; footer shows "· 1 space deactivated"
  2. Open the deactivated space → amber chip next to "View only" + banner; press Reactivate inline → space returns to the active group
  3. Deactivate again → ⋯ → Delete space → new dialog: three icon rows, danger button disabled until you type the exact name; check the first row's wording matches your Deleted-files setting (with "Never delete automatically" on it must say "until an administrator purges them")
  4. Grid view: deactivated tiles render dimmed
  5. All four languages on the dialog + banner (Appearance → language)
**UI polish round shipped — v0.6.114 (PR #407), live on both dogfoods.** - **A2 delete dialog:** consequence rows with icons, name in the title in normal type, type-the-name-to-confirm gating the danger button. The recoverability line is dynamic and honest: configured retention window when the sweep is on, "recoverable until an administrator purges them — nothing is removed automatically" when it's off (the default). New `neverPurge` field on `GET /api/v1/spaces/{name}/state`. - **B2 listing:** `deactivated` stamped on `/spaces` rows (`fillSpaceStates`, in-memory, owner/admin-only by construction); deactivated spaces sink below an amber "Deactivated" divider, dimmed, amber chip, "Hidden from members · you keep view-only access" sub-line; footer counts them. - **C in-space:** listing response gains `spaceDeactivated`; amber "Deactivated" chip beside "View only" in the crumb, plus a banner with inline **Reactivate**. **Dogfood checklist (fresh login — in-memory sessions):** 1. ⋯ on a deactivated-able space → Deactivate → row moves under the amber divider, dimmed, chip + sub-line; footer shows "· 1 space deactivated" 2. Open the deactivated space → amber chip next to "View only" + banner; press **Reactivate** inline → space returns to the active group 3. Deactivate again → ⋯ → Delete space → new dialog: three icon rows, danger button disabled until you type the exact name; check the first row's wording matches your Deleted-files setting (with "Never delete automatically" on it must say "until an administrator purges them") 4. Grid view: deactivated tiles render dimmed 5. All four languages on the dialog + banner (Appearance → language)
Author
Owner

Operator visual pass complete on the live dogfood (2026-09-03, v0.6.114) — all checklist items passed. With that, the full #286 arc is done: lifecycle shipped v0.6.54, admin inventory wired in the #311 sweep, and the UI polish round (A2 dialog with honest retention copy + name confirm, B2 deactivated group, C in-space banner) verified in use. Closing.

Operator visual pass complete on the live dogfood (2026-09-03, v0.6.114) — all checklist items passed. With that, the full #286 arc is done: lifecycle shipped v0.6.54, admin inventory wired in the #311 sweep, and the UI polish round (A2 dialog with honest retention copy + name confirm, B2 deactivated group, C in-space banner) verified in use. Closing.
Cordy closed this issue 2026-09-03 02:39:47 +00:00
Sign in to join this conversation.
No labels
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#286
No description provided.