Skip to content

feat(store): project delete keeps the row - #2007

Merged
AmanGIT07 merged 2 commits into
mainfrom
feat/project-soft-delete
Oct 20, 2026
Merged

AmanGIT07 merged 2 commits into
mainfrom
feat/project-soft-delete

Conversation

@AmanGIT07

Copy link
Copy Markdown
Contributor

Summary

DeleteProject now keeps the project row and sets deleted_at on it, instead of removing it. Project reads already skip deleted rows, and the name is free again because uq_projects_name_live covers live rows only. The project.deleted audit record is written in the same transaction, with the row's delete time.

The rest of the delete is unchanged: the project's policies are marked deleted, its SpiceDB tuples are removed, and its resources are still purged.

Changes

  • internal/store/postgres/project_repository.go: Delete uses softDelete with RETURNING. The audit record takes the returned deleted_at. When no live row matches, it returns project.ErrNotExist; it returned nil before.
  • internal/store/postgres/project_repository_test.go: TestDelete covers the kept row, the audit record, a second delete, an unknown id, and reusing the name.
  • test/e2e/regression/api_test.go: new TestProjectDeleteKeepsTheRowAndRemovesAccess.

Technical Details

  • A second delete of the same project gets project.ErrNotExist from the repository. Through the API it is refused earlier, at the permission check, because the project's tuples are already gone.
  • Project create rolls back through the same delete when its setup fails, so a failed create now leaves a row marked deleted. The name stays free.

Test Plan

  • TestDelete: the row stays with deleted_at set and reads no longer find it; one audit record is written; a second delete and an unknown id return project.ErrNotExist without an audit record; a new project can take the name.
  • e2e TestProjectDeleteKeepsTheRowAndRemovesAccess: an org owner, a user with a project policy, a group member, and a service user can all get the project before the delete, and none of them can after. Every policy that was on the project answers not found and has deleted_at set, and no live policy is left on the project. The project row has deleted_at set, a second delete is refused, and a new project can take the name.
  • go test ./internal/store/postgres/... passes.
  • e2e TestEndToEndAPIRegressionTestSuite, TestEndToEndServiceUsersRegressionTestSuite and TestEndToEndPATRegressionTestSuite pass.
  • golangci-lint run ./internal/store/postgres/... ./test/e2e/regression/... reports no issues.

SQL Safety

  • The id flows through goqu.Ex{}. The only goqu.L is now() inside softDelete, with no values.
  • ToSQL() callers capture and forward params (query, params, err := stmt.ToSQL(); db.…Context(ctx, …, query, params...)). Never query, _, err := ….
  • No ? placeholders inside single-quoted SQL literals in goqu.L.
  • No new //nolint:forbidigo or // #nosec G20x annotations.

The project row now gets deleted_at instead of being removed. The
project.deleted audit record is written in the same transaction, with the
row's delete time. Deleting a project that is already deleted, or does not
exist, returns not found. An e2e test checks that everyone who had access
loses it and that every policy on the project is marked deleted.
@vercel

vercel Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
frontier Ready Ready Preview Oct 20, 2026 10:53am UTC

@coderabbitai

coderabbitai Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: raystack/frontier/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 435aa154-8e5f-4da7-bdc7-3228998cdbe4
📥 Commits

Reviewing files that changed from the base of the PR and between eeec4a8 and 5f4dcb0.

📒 Files selected for processing: 4
  • core/deleter/service.go
  • core/deleter/service_test.go
  • core/resource/service.go
  • internal/store/postgres/resource_repository.go

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 0 remain after this review.

📝 Summary

Summary by CodeRabbit

  • Bug Fixes
    • Deleted projects are now hidden and inaccessible to owners and users who previously had access.
    • Deleting an already deleted or unknown project now returns a not-found error instead of appearing successful.
    • Project names can be reused after the original project is deleted.
    • Deletion handling now consistently removes associated access policies and records the deletion event.

Project deletion now soft-deletes the project row and returns project.ErrNotExist for repeated or unknown deletions. Cleanup order, access removal, audit records, retained rows, and project-name reuse are covered by tests.

Changes: Project Deletion

Layer / File(s) Summary
Repository deletion semantics
internal/store/postgres/project_repository.go, internal/store/postgres/project_repository_test.go
The repository soft-deletes projects and uses the row’s DeletedAt timestamp in the audit record. Tests check missing and repeated deletion errors, audit records, and project-name reuse.
Deletion orchestration and cleanup order
core/deleter/service.go, core/deleter/service_test.go, core/resource/service.go, internal/store/postgres/resource_repository.go
DeleteProject deletes policies and purges resources before deleting the project. An already-missing project returns success. Tests verify the cleanup order and missing-project path.
API deletion and access checks
test/e2e/regression/api_test.go
The regression test checks that deletion removes project access, soft-deletes project policies, retains the deleted project row, rejects a second deletion, and allows project-name reuse.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature

Suggested reviewers: whoabhisheksah

Merge Risk: ⚪ Minimal · up to 5f4dc

The soft-delete, cleanup, audit, access-removal, and repeat-delete behavior is covered by the inspected implementation and tests.

Pre-merge checks: Passed 2
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coveralls

coveralls commented Oct 19, 2026 •

Copy link
Copy Markdown

Coverage Report for CI Build 39411055393

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Coverage increased (+0.3%) to 55.587%

Details

  • Coverage increased (+0.3%) from the base build.
  • Patch coverage: 1 uncovered change across 1 file (6 of 7 lines covered, 85.71%).
  • 245 coverage regressions across 6 files.

Uncovered Changes

File Changed Covered %
core/deleter/service.go 3 2 66.67%
Total (2 files) 7 6 85.71%

Coverage Regressions

245 previously-covered lines in 6 files lost coverage.

File Lines Losing Coverage Coverage
core/deleter/service.go 72 81.66%
internal/store/postgres/billing_transactions_repository.go 58 71.9%
core/resource/service.go 51 82.16%
internal/store/postgres/billing_customer_repository.go 36 62.63%
cmd/serve.go 22 0.0%
core/deleter/deleter.go 6 0.0%

Coverage Stats

Coverage Status
Relevant Lines: 41855
Covered Lines: 23266
Line Coverage: 55.59%
Coverage Strength: 19.29 hits per line

💛 - Coveralls

@rohilsurana rohilsurana left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a few comments. The main one is that a second delete now errors where it used to succeed. Most of these touch files outside this diff, so they are listed here.

  1. core/deleter/service.go:226: the comment saying the project row is removed for good is now wrong. The Purge comments in internal/store/postgres/resource_repository.go:289 and core/resource/service.go:505 say a resource row cannot outlive its project row, which is also no longer true. Can you update them?

  2. core/deleter/service.go:242: see the inline comment on the repository change, which explains the second delete and the org cascade. Treating project.ErrNotExist from DeleteModel as success in DeleteProject fixes both.

  3. core/deleter/service_test.go:113: can we add a test that pins the order, policies, then resources, then the project row last? The delete order must not change, and nothing checks it now. The steps recorder used in the org tests below would work.

  4. core/project/service.go:83: a failed create used to leave no project row, because the rollback hard deleted it. Now the rollback soft deletes, so a deleted row stays behind. The project.created and project.deleted records were already written before this PR. It only happens when the SpiceDB link or the owner policy fails, so it is rare. Could you open a ticket to track it? Purging the row on rollback would fix the leftover row. Removing the audit pair would need one transaction around the create and the membership step.

Comment thread internal/store/postgres/project_repository.go
DeleteProject ignores project.ErrNotExist from DeleteModel, so a second
delete, and a project deleted during an org delete, succeed as before. A
test now pins the order: policies, then resources, then the project row.
The comments that said the project row is removed for good are updated.
@AmanGIT07

Copy link
Copy Markdown
Contributor Author

In 5f4dcb0:

  1. Updated the three comments. The deleter's resource step is now TODO(fix): soft-delete the live resources instead of purging every row, and both Purge comments say only the project delete cascade uses it, to be removed once the cascade soft-deletes resources.
  2. Done, see the reply on the inline thread.
  3. The first TestDeleteProject case now records each step and checks: policies, then resources, then the project row.
  4. Opened A failed project create leaves a deleted project row behind #2009.

@rohilsurana rohilsurana left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. The second delete and the org cascade now treat a project that is already gone as deleted, the order is pinned by the test, the comments are updated, and the create rollback is tracked in #2009.

@AmanGIT07
AmanGIT07 merged commit 358f8dd into main Oct 20, 2026
8 checks passed
@AmanGIT07
AmanGIT07 deleted the feat/project-soft-delete branch October 20, 2026 16:29

This branch was successfully deployed

1 active deployment
Preview — 5f4dcb00 Deployed Oct 20, 2026 by vercel[bot]
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.

3 participants