Skip to content

Asset archive/soft-delete (Phase 3) - #71

Merged
SylonZero merged 1 commit into
masterfrom
feat/phase3-asset-archive
Sep 12, 2026
Merged

SylonZero merged 1 commit into
masterfrom
feat/phase3-asset-archive

Conversation

@SylonZero

Copy link
Copy Markdown
Owner

Summary

  • Adds soft-delete/archive tombstone support for Assets (archivedAt/archivedById/archivedByKind), per the "Deletion & Cascade Design for Core Entities" spec — the same treatment agreed for Product, applied first here since Product deletion remains paused (Phase 4, most-guarded/last).
  • Archiving hides an asset from listing/picker views but does not cascade or null out references — work items, tasks, dependency edges, and plan/release links all keep working. Reversible via restore_asset / the UI's Restore button.
  • New shared authorization rule (canDeleteAsset in lib/db/authz.ts, used by both the UI and MCP): org owner/admin, the asset's creator, or a declared asset owner (assetOwners).
  • New MCP tools archive_asset / restore_asset, both disclosing accurate reference counts (work items, tasks, dependency edges, plan targets, release stamps) in the response.
  • UI: the product page's asset editor now shows an "Archive" confirmation (accurate, non-destructive copy — no "cannot be undone" language) instead of permanent delete, plus a collapsible "archived assets" section with a Restore action. getProduct now returns archived assets too so that section has something to show.
  • Folded in a small fix: tests/lib/ai.test.ts's Release fixture was missing the createdById/updatedById/etc. fields that PR Expose createdById/updatedById in the query layer #70 made required — a real (if CI-inert, since vitest doesn't type-check) regression from that PR.

Base branch

Stacked on feat/expose-attribution-query-layer (#70), which is still open — this PR should be retargeted to master automatically once #70 merges.

Test plan

  • 13 new tests (6 canDeleteAsset cases in tests/lib/db/authz.test.ts, 7 archiveAsset/restoreAsset cases in tests/lib/db/mutations.test.ts) — 287/287 passing
  • pnpm exec tsc --noEmit — clean of anything this PR touches (two pre-existing, unrelated errors remain: passwordHash, CodePlan.productName)
  • pnpm build — succeeds
  • New migration (hand-written for Postgres since drizzle-kit generate needs an interactive TTY not available here; drizzle-kit generate worked directly for SQLite) verified against a real, previously-populated local dev DB — columns added cleanly, existing rows unaffected

🤖 Generated with Claude Code

https://claude.ai/code/session_016ZM1Fxzj2sqge7EyGQ8LXb

@SylonZero
SylonZero added this pull request to stack #72 September 12, 2026 23:20
Base automatically changed from feat/expose-attribution-query-layer to master September 12, 2026 23:21
… PR #70

Assets get the same soft-delete tombstone treatment agreed for Product:
archivedAt/archivedById/archivedByKind, kept deliberately separate from
the existing operational health/status enum. Archiving hides an asset
from listing/picker views but never nulls out or unlinks anything that
references it — work items, tasks, dependency edges, and plan/release
links keep working, matching the "no surprise cascades" rule from the
Deletion & Cascade Design spec.

- lib/db/authz.ts: canDeleteAsset — org owner/admin, the creator, or a
  declared asset owner (assetOwners), same shared-policy pattern as the
  other canDelete* checks.
- lib/db/mutations.ts: archiveAsset/restoreAsset, both audit-logged.
  deleteAsset kept for a possible future admin-only purge, no longer on
  the normal UI/MCP path.
- MCP: archive_asset/restore_asset tools disclose reference counts
  (work items, tasks, dependency edges, plan targets, release stamps)
  without claiming anything was touched.
- UI: product page's asset editor swaps the delete confirmation for an
  archive one with accurate, non-destructive copy, plus a collapsible
  "archived assets" section with a restore action. getProduct now
  returns archived assets too (previously filtered) so that section has
  something to show.
- Query layer: ~10 listing functions now exclude archived assets
  (isNull(assets.archivedAt)); detail/history views deliberately don't,
  so an archived asset is still viewable directly.
- Also fixes a real (if inert) type-check regression from PR #70: Release
  gained required Attribution fields but tests/lib/ai.test.ts's fixture
  literal wasn't updated. tsc --noEmit is clean of anything this session
  touched; the two remaining errors (passwordHash, CodePlan.productName)
  predate this work.

Tests: 13 new (6 authz, 7 mutations) — 287/287 passing. tsc --noEmit and
pnpm build verified. Migration hand-written for Postgres (drizzle-kit
generate needs an interactive TTY not available here) and drizzle-kit
generate for SQLite; both verified against a real populated local dev DB.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZM1Fxzj2sqge7EyGQ8LXb
@SylonZero
SylonZero force-pushed the feat/phase3-asset-archive branch from b7396cc to 850fe28 Compare September 12, 2026 23:21
@SylonZero
SylonZero merged commit 750a648 into master Sep 12, 2026
1 check passed
@SylonZero
SylonZero deleted the feat/phase3-asset-archive branch September 12, 2026 23:22
@SylonZero SylonZero mentioned this pull request Sep 12, 2026
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant