浏览代码

SPEC.md: note Sprint Delete action shipped

§9 picks up an inline entry for SHA 8e8b8fd (didn't match a planned
phase — operator-facing destructive action). §6 routes table gains
POST /sprints/{id}/delete with the confirm_name guard noted. §11
test-count line bumped from 143/392 → 149/424 (it had been stale
across Phases 21+22 — refreshing now).

§13 prepends 8e8b8fd to the history block.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chiappa 3 天之前
父节点
当前提交
c6128af8f4
共有 1 个文件被更改,包括 32 次插入1 次删除
  1. 32 1
      SPEC.md

+ 32 - 1
SPEC.md

@@ -209,6 +209,7 @@ Pages (HTML):
 | GET    | `/sprints/{id}`             | signed-in      |
 | GET    | `/sprints/{id}/present`     | signed-in      |
 | GET    | `/sprints/{id}/settings`    | admin          |
+| POST   | `/sprints/{id}/delete`      | admin (form `_csrf` + `confirm_name` must match sprint name verbatim) |
 | GET    | `/audit`                    | admin          |
 | GET    | `/settings`                 | admin          |
 | POST   | `/settings`                 | admin (form CSRF via `_csrf`) |
@@ -1008,6 +1009,34 @@ with a `BOOTSTRAP_ADMIN` audit row.
       status tests keep passing without modification; the migrator
       picks up 004 on next request.
 
+- [x] **Sprint settings: secured Delete sprint action** (`8e8b8fd`).
+      New "Danger zone" section at the bottom of `/sprints/{id}/settings`
+      with a `POST /sprints/{id}/delete` form gated three ways:
+      `SessionGuard::requireAdmin` + `verifyCsrf`, plus a `confirm_name`
+      field that must match `sprint.name` verbatim (server-side
+      authoritative check, with a `sprint-settings.js` UX guard that
+      keeps the submit button disabled until the typed name matches
+      and a final `window.confirm()` so a misclick on the now-enabled
+      button can't fire the destructive POST). Cascade audit (spec §7):
+      `SprintController::delete` snapshots every descendant before the
+      FK cascade fires — `task_assignments`, `sprint_worker_days`,
+      `tasks`, `sprint_workers`, `sprint_weeks` — and records one
+      DELETE per child plus one for the parent inside a single
+      transaction. Phase 22's `tasks.linked_task_id ON DELETE SET NULL`
+      on cross-sprint copies is also captured: rows in *other* sprints
+      whose `linked_task_id` pointed at one of this sprint's tasks get
+      an UPDATE audit (`linked_task_id` X → null) so the chain stays
+      reconstructable. On success the user lands on
+      `/?deleted=<sprint name>` with a green "Sprint X was deleted."
+      chip rendered by `views/home.twig`. New
+      `SprintRepository::delete(int $id): ?Sprint` returns the
+      pre-deletion snapshot for the parent audit row, mirroring
+      `TaskRepository::delete`. `tests/Cascade/CascadeAuditTest.php`
+      grows a fourth path covering the full sprint delete: sets up a
+      2-worker / 4-week / 1-task / 2-assignment fixture, audits each
+      leaf, drops the sprint, then asserts the per-entity-type audit
+      count matches the cascade size. Tests: 149 / 424 (was 148 / 406).
+
 ### Upcoming
 
 Nothing scheduled.
@@ -1059,7 +1088,7 @@ for f in $(git ls-files '*.php'); do php -l "$f" | tail -1 | sed "s|^|$f: |"; do
 Run the test suite:
 ```bash
 vendor/bin/phpunit
-# → OK (143 tests, 392 assertions)
+# → OK (149 tests, 424 assertions)
 ```
 
 The Phase 20 parser tests need `ext-dom`, `ext-zip`, `ext-xmlreader`,
@@ -1106,6 +1135,8 @@ before acting — nothing here is load-bearing once it grows stale.
 ## 13. Git history (as of this writing)
 
 ```
+8e8b8fd Sprint settings: secured Delete sprint action
+be91620 SPEC.md: mark Phase 22 shipped (per-task hamburger menu)
 c2dad80 Phase 22: per-task hamburger menu — move/copy/edit/reorder
 e2f19d6 Phase 21: derive sprint week count from start/end dates
 62bb8b2 SPEC.md: mark Phase 20 shipped (XLSX import wizard)