fix: support safe project deletion

This commit is contained in:
2026-08-13 18:45:20 +08:00
parent 1eb36645e4
commit cb88a69012
10 changed files with 458 additions and 3 deletions

View File

@@ -0,0 +1,70 @@
# Task: Diagnose and fix project deletion
## Identity
- Task ID: 20260813-project-delete-fix-d3633d68
- Mode: Feature
- Branch: main
- Worktree: D:\Datas\PythonProjects\XQKqueue
- Base commit: 1eb36645e41dfd479799470df65d5cb8a8ac077e
- Owner: codex
- Status: Complete; ready for integration
## Scope
- Diagnose the missing administrator project-deletion workflow across the React admin client and Go HTTP service.
- Add a safe deletion contract for projects that have no retained operational history.
- Preserve queue sessions and their ticket, call-batch, and simulation history by rejecting deletion when retained operational data exists; preserve audit rows by detaching the project foreign key.
- Cover the administrator UI/API request and the server deletion boundary with regression tests.
## Intent And Constraints
- The user's confirmed symptom is that a project cannot be deleted; the repository currently exposes no project-delete UI action, web API method, HTTP route, or handler.
- PostgreSQL remains the sole write authority. The client must not infer deletion eligibility.
- Existing `ON DELETE RESTRICT` history relationships must not be replaced with broad cascading deletion.
- The working product assumption is that deletion means removing an unused/accidentally-created project; projects with retained operational history must be rejected with an explicit conflict response, while audit history must remain available without retaining a project foreign key.
- Keep the change surgical and reuse the existing admin authentication, transaction, API-error, polling-refresh, and feedback patterns.
## Outcome
- Confirmed the reported behavior was a missing capability rather than a failing existing request: the admin UI had no delete action, the web API client had no delete method, and the Go server had no DELETE route or handler.
- Added an administrator-only `DELETE /api/admin/projects/{id}` endpoint and registered it as an important structured-log route.
- Implemented guarded deletion in one transaction: lock the project row, reject projects with any queue session using `409 PROJECT_HAS_HISTORY`, detach retained project-scoped audits, write an unscoped `PROJECT_DELETED` snapshot audit, and delete the otherwise-unused project.
- Preserved the existing database ownership of ephemeral cascades (`user_projects` and `idempotency_keys`) and did not add broad cascades or delete queue, ticket, call-batch, device, or audit history.
- Added a two-step destructive action to the project maintenance page, including in-flight duplicate prevention, server error feedback, refresh, and replace-navigation to the project list after success.
- Added a danger-color button variant so deletion is visually distinct from ordinary project maintenance.
- Added web API/component regression coverage and full-handler PostgreSQL integration coverage for authentication, successful unused-project deletion, retained audit history, and atomic rejection of a project with queue history.
## Verification
- Red signal: before implementation, the new web deletion tests failed because `api.deleteProject` and the delete controls did not exist; the new Go DELETE route test returned 405 instead of reaching admin authentication.
- Focused web: `pnpm exec vitest run src/api.test.ts src/pages/AdminPage.test.tsx` passed, 2 files / 22 tests.
- The web API regression uses the server's actual empty `204 No Content` response contract rather than a JSON success fixture.
- Full web: `pnpm test:run` passed, 18 files / 69 tests.
- Web type/build: `pnpm typecheck` and `pnpm build` passed.
- Focused real PostgreSQL: `go test ./internal/httpapi -run '^TestDeleteProject(PostgresIntegration|RouteRequiresAdminAuthentication)$' -count=1 -v` passed against a fresh isolated PostgreSQL 17 database.
- Full server: `go test ./... -count=1` passed against a second fresh isolated PostgreSQL 17 database.
- The integration test exercises the complete admin login-cookie and routed-handler chain. It verifies 204 with an empty body, project/grant removal, retained old audit rows with a null project foreign key, a deletion snapshot audit, and a 409 path with no partial mutation.
- `git diff --check` passed with only existing Windows line-ending notices.
- Server static checks: `go vet ./...` and `go build ./...` passed.
- Independent read-only Sol review: PASS; no P0/P1 or unresolved P2/P3 findings. It verified PostgreSQL parent/FK lock compatibility, transactional rollback, 204 handling, audit detachment/snapshot semantics, schema-owned cascades, UI confirmation/in-flight/error/navigation behavior, logging, and task-document ownership.
## Follow-ups
- Promote the guarded deletion rule and admin DELETE interface into canonical business-rule/data-flow documentation through a later Integration Gate.
- If product policy later requires archived projects, deleted-code reservation, or deleted-project filtering in audit/history screens, treat that as a separate schema and cross-query design task rather than broadening this hard-delete endpoint.
## Promotion Candidates
- Target: `.project-docs/40-domain/business-rules.md`.
Proposal: an administrator may permanently delete only a project with no queue session; any retained queue history blocks deletion with `PROJECT_HAS_HISTORY`. Deletion of an eligible unused project preserves audit entries and records a `PROJECT_DELETED` snapshot while removing the project and ephemeral grants/idempotency state.
Evidence: `server/internal/httpapi/admin.go`, `server/internal/httpapi/project_delete_integration_test.go`, and the PostgreSQL verification recorded above.
Future impact: future project relationships must either be classified as retained deletion blockers or explicitly ephemeral; clients must not decide eligibility locally or introduce broad history cascades.
Semantic conflicts: none with current retention rules; the prior documents did not define project deletion.
Human confirmation: the user requested that projects become deletable; the guarded empty-only interpretation was selected to preserve existing history and audit contracts.
- Target: `.project-docs/20-architecture/data-flow.md`.
Proposal: add `DELETE /api/admin/projects/{id}` to the admin interface list and record that the service transaction locks the project, enforces the queue-history guard, preserves detached audits, and owns deletion.
Evidence: the registered server route, transaction handler, web client call, and full-handler integration test.
Future impact: administrator clients should surface the service's 409 reason and must not emulate or bypass deletion rules.
Semantic conflicts: none known.
Human confirmation: no additional confirmation required for documenting the implemented interface.