feat(api): soft-delete projects — recoverable + audited (DELETE /projects/:id, restore) - #257
Conversation
Projects are soft-deleted (tombstone deleted_at, hidden from the listing, reversible via restore), mirroring ADR-030's spec-withdraw custody model and adding an audit actor. deleted_by is caller-supplied free text (no FK) — there is no user/auth model yet (#43 deferred); when auth lands it populates from the session. Audit = who (deleted_by) + when (deleted_at). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Migration 034 adds reversible projects.deleted_at/deleted_by (nullable, no FK). softDeleteProject tombstones idempotently (COALESCE keep-first, so a re-delete returns the original who/when), restoreProject clears the tombstone (idempotent), listProjects filters deleted_at IS NULL, and findProjectById surfaces the tombstone. All parameterized; pg errors wrapped in DatabaseError. Exported from the db barrel. ADR-031. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…i + tests
Adds the soft-delete (ADR-031) HTTP surface:
- DELETE /projects/:id — body { deletedBy } required (400 if missing/empty),
malformed id 400, unknown 404; 200 { projectId, deletedAt, deletedBy };
idempotent re-delete returns the existing tombstone.
- POST /projects/:id/restore — clears the tombstone; idempotent; 400/404 guards.
- openapi.yaml: both operations documented (3.1 nullable type [string, 'null'])
and deletedAt/deletedBy added to the project response schema.
- Integration tests (TDD, native fetch harness) cover soft-delete + list
hiding + GET-by-id surfacing, restore, idempotent re-delete, and the
400/404 guards; contract allowlist + ProjectWithToc mock fixtures updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A second DELETE preserves the original deleted_at/deleted_by via COALESCE but should not bump updated_at — the project's state didn't change. Only set updated_at when transitioning a live project to deleted. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Warning Review limit reached
More reviews will be available in 26 minutes and 10 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more credits in the billing tab to continue. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (12)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Symmetric to the soft-delete fix: restoring an already-active project (no tombstone to clear) must not bump updated_at. Only set it when deleted_at was actually non-null. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Review note: CodeRabbit was a rate-limited no-op, so Codex (GPT-5.5 xhigh) ran as the gate. 1
|
#325) * docs(readme): sync capabilities to last month of merged PRs Reflect shipped work in the README's "Included Today", "API Surface", and MCP tool table, validated against the merged diffs and current main: - PDF ingest (text-layer + OCR + font-encoding recovery) accepted by POST /parse (#287, #290, #311) - coordination / E&O report + submittal register (#241, #269, #277, #282, #283, #284) and article-role tagging (#273) - onboarding pipeline: library import, editability review/override, reclassify, finalize/reopen, open-comments (#243, #247, #248, #249, #272) - spec/project soft-delete + restore (#257, #313), document concurrency (#197), revision/addendum manual rendering (#221), numbering profiles (#317, #322) - add missing MCP tools get_numbering_profile, submittal_register, open_comments_report; document GET /docs (Scalar) (#213, #285) - add Example Client pointer to examples/web_ui_demo (#225) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(roadmap): move shipped work to done; re-date to 2026-07-01 Reconcile the roadmap with merged reality (was stamped 2026-06-17). Moved from planned/in-progress to Included, each validated against the diff: - PDF ingest (#287, #290, #311) — remove from "Later" - deep paragraph nesting pr6/pr7 (#215) - revision nomenclature (#216) + revision/addendum manual rendering (#221) — the two "Near Term" Phase 2e items are done - coordination / E&O report, required-sections, article-role, submittal register (#239, #241, #269, #273, #277, #282, #283, #284) — new "Coordination and Semantics" section; removed "coordination report" from planned Phase 4 - onboarding APIs (#243, #247, #248, #249, #272) — API done; UI remains planned - soft-delete/withdraw (#257, #313), section-number format (#266, #271), external-content associations (#242), structural numbering profiles (#317) Kept as planned (foundation only): header/footer composition (#222, #314) and keynote surfacing (#315) — DB/AST exist, no resolution/render/export yet. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(architecture): reflect merged structural changes Update the architecture spec for shipped work, validated against the diffs and current schema/routes: - Tech Stack + Data Flow: Parse — PDF text-layer (unpdf/pdfjs-dist) + OCR (tesseract.js/@napi-rs/canvas) path and numberingProfileId override (#287, #290, #311, #317; ADR-034, ADR-039) - DB schema — specs.onboarding_status/withdrawn_at, projects.section_number_format /deleted_at/deleted_by, paragraphs.source_facts/classification/ editability_override; "Additional tables" summary for editing_conventions, paragraph_associations, required_sections, keynotes, header_footer_configs, numbering_profiles, revision_nomenclature_profiles (foundation-only tables flagged) (ADR-021/022/023/028/031/032; #187, #242) - new Coordination Report / E&O section (finding vocabulary) and Document Concurrency section (locks/optimistic/lifecycle) (#197, #241, #269, #277, #282, #283, #284; ADR-018, ADR-033/035/036/037) - AST meta.articleRole (#273, ADR-033); API-surface note pointing at the CI-enforced openapi.yaml + GET /docs; refreshed MCP tool list Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Why
Projects had no delete at all. You asked for a recoverable delete with an audit (who + when) — not a destructive one. This delivers that, mirroring the spec-withdraw custody pattern (ADR-030).
What
DELETE /projects/:id— soft-deletes (tombstone): setsdeleted_at+deleted_by, hides the project fromGET /projects, but it stays readable by id and recoverable. Body carries the actor:{ deletedBy }(required). Idempotent: re-deleting preserves the original who/when.POST /projects/:id/restore— clears the tombstone; the project reappears.deleted_byis caller-supplied free text (no FK) — SpecR has no auth/user model yet (feat(api): Phase 5f — authentication + multi-tenant (JWT, org isolation) #43). When auth lands it populates this from the session. Rationale in ADR-031.deleted_at/deleted_by;GET /projects/:idsurfaces them.Testing
pnpm lintclean; OpenAPI contract gate green🤖 Co-authored by Claude Opus 4.8.