Skip to content

feat(deleter): project delete refuses while resources block it - #2008

Open
AmanGIT07 wants to merge 1 commit into
mainfrom
feat/project-delete-blockers
Open

AmanGIT07 wants to merge 1 commit into
mainfrom
feat/project-delete-blockers

Conversation

@AmanGIT07

Copy link
Copy Markdown
Contributor

Summary

DeleteProject now refuses while the project's resources block the delete, using the RESOURCES_EXIST check from #2005. The refusal is failed_precondition with one google.rpc.PreconditionFailure violation per blocking namespace. The organization delete removes its projects without the project check, so a resource that blocks a project delete does not stop an organization delete. No delete is refused while app.deletion is disabled.

Changes

  • core/deleter/service.go:
    • DeleteProject looks the project up by id with IncludeDisabled. A missing project returns project.ErrNotExist; an id that is not a uuid returns project.ErrInvalidUUID.
    • It then runs ProjectResourceBlockers, and returns a BlockedError naming the project when anything blocks.
    • deleteProject holds the removal steps, unchanged. DeleteOrganization calls it for each project.
  • core/deleter/deleter.go: BlockedError gains ProjectID. Error() names the project or the organization and leaves out empty messages, so a project refusal reads project cannot be deleted yet.
  • internal/api/v1beta1connect/deleter.go: DeleteProject answers failed_precondition with the blockers, and not_found for a missing project or an id that is not a uuid. The blocked-delete log lines carry project_id and no longer say organization.
  • test/e2e/regression/api_test.go: the API suite runs with app.deletion enabled and a rule for compute/order (project: block, organization: allow).

Technical Details

  • On the Connect protocol, failed_precondition is HTTP 400. A violation carries the blocker type and the namespace; PreconditionFailure has no field for the count.
  • DeleteProject with a project name answered 500 before; it now answers not_found.

Test Plan

  • core/deleter/service_test.go TestDeleteProject: a blocked project returns the BlockedError and removes nothing; a missing project returns project.ErrNotExist; a name returns project.ErrInvalidUUID; the existing removal cases expect the lookup.
  • TestDeleteOrganization: with the deletion config on, an org whose project holds a resource that blocks a project delete is deleted, and the resource is removed.
  • internal/api/v1beta1connect/deleter_test.go TestHandler_DeleteProject: a blocked delete answers failed_precondition with one RESOURCES_EXIST violation; a missing project and a name answer not_found.
  • e2e TestProjectDeleteRefusedWhileResourcesBlock: a project holding a compute/order resource is refused with that violation and stays readable; after the resource is deleted, the project delete succeeds; an org whose project still holds the resource is deleted.
  • go test ./core/... ./internal/api/... ./pkg/server/... passes.
  • e2e TestEndToEndAPIRegressionTestSuite, TestEndToEndServiceUsersRegressionTestSuite and TestEndToEndPATRegressionTestSuite pass.
  • golangci-lint run ./core/deleter/... ./internal/api/v1beta1connect/... ./test/e2e/regression/... reports no issues.

@vercel

vercel Bot commented Oct 20, 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 4:35pm UTC

@coderabbitai

coderabbitai Bot commented Oct 20, 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: 5de874da-fe22-45a5-8f6d-3edfd2117bc9
📥 Commits

Reviewing files that changed from the base of the PR and between 26f3314 and da8dac4.

📒 Files selected for processing: 3
  • core/deleter/service.go
  • core/deleter/service_test.go
  • test/e2e/regression/api_test.go

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

📝 Summary

Summary by CodeRabbit

  • New Features

    • Project deletion now validates project identifiers and reports missing or invalid projects as not found.
    • Deletion is blocked when resources prevent project removal, with details identifying the blocking resources.
    • Organizations can still be deleted along with projects, even when project-level blockers are configured.
  • Bug Fixes

    • Improved blocked-deletion messages for projects and organizations.
    • Empty blocker messages are excluded from user-facing errors.

Changes

Project deletion now validates project IDs, loads projects including disabled projects, checks resource blockers, and reports blocked, missing, and invalid projects through the Connect API. Organization deletion uses an internal project teardown path that bypasses project-level blockers.

Project deletion behavior

Layer / File(s) Summary
Project deletion checks and teardown
core/deleter/deleter.go, core/deleter/service.go, core/deleter/service_test.go
BlockedError supports project targets and omits empty blocker messages. Project deletion validates and loads the project, checks resource blockers, and deletes it when unblocked. Organization deletion uses the internal teardown path. Unit tests cover these cases.
Connect error mapping and logging
internal/api/v1beta1connect/deleter.go, internal/api/v1beta1connect/deleter_test.go
The Connect API maps blocked project deletions to failed-precondition errors and missing or invalid projects to not-found errors. Blocked-delete logs include the project ID.
End-to-end deletion scenarios
test/e2e/regression/api_test.go
Regression tests verify project deletion behavior for blocking compute/order resources and organization deletion with another blocked project.

Priority: ⬇️ Low

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

Change: Feature

Suggested reviewers: whoabhisheksah

Merge Risk: ⚪ Minimal · up to da8da

Project deletion now refuses while resources block it, and organization deletion still removes its projects. No merge-blocking risk was identified.

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 20, 2026 •

Copy link
Copy Markdown

Coverage Report for CI Build 39458684805

Coverage increased (+0.03%) to 55.62%

Details

  • Coverage increased (+0.03%) from the base build.
  • Patch coverage: 8 uncovered changes across 3 files (35 of 43 lines covered, 81.4%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
core/deleter/service.go 23 19 82.61%
core/deleter/deleter.go 11 8 72.73%
internal/api/v1beta1connect/deleter.go 9 8 88.89%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 41888
Covered Lines: 23298
Line Coverage: 55.62%
Coverage Strength: 19.29 hits per line

💛 - Coveralls

@AmanGIT07 AmanGIT07 mentioned this pull request Oct 20, 2026
6 tasks done
DeleteProject runs the RESOURCES_EXIST check and returns a BlockedError
naming the project, which the API answers with failed_precondition and one
violation per blocking namespace. The org delete removes its projects
without that check. An unknown project or an id that is not a uuid answers
not found. The API e2e suite runs with the deletion config on.
@AmanGIT07

Copy link
Copy Markdown
Contributor Author

Sandbox run: a local Frontier server with Postgres and SpiceDB in Docker, called over the Connect protocol. The binary was built from #2014 with #2011 applied, so it has main plus #2008, #2011, #2013 and #2014.

Deletion config used:

app:
  deletion:
    enabled: true
    resources:
      resource/aoi: { project: block, organization: allow }
      resource/workflow: { project: block, organization: allow }
      resource/orderitem: { project: block_unless_deletable, organization: block_unless_deletable }
      resource/insight: { project: allow, organization: allow }

resource/report has no rule.

DeleteProject responses when resources block

Project with 2 AOIs, 1 workflow, 1 order item still processing, and 1 finished order item ("deletable": true):
DeleteProject → HTTP 400

{
  "code": "failed_precondition",
  "message": "project cannot be deleted yet",
  "details": [
    {
      "type": "google.rpc.PreconditionFailure",
      "value": "Ch8KD1JFU09VUkNFU19FWElTVBIMcmVzb3VyY2UvYW9pCiUKD1JFU09VUkNFU19FWElTVBIScmVzb3VyY2Uvb3JkZXJpdGVtCiQKD1JFU09VUkNFU19FWElTVBIRcmVzb3VyY2Uvd29ya2Zsb3c",
      "debug": {
        "violations": [
          {
            "type": "RESOURCES_EXIST",
            "subject": "resource/aoi"
          },
          {
            "type": "RESOURCES_EXIST",
            "subject": "resource/orderitem"
          },
          {
            "type": "RESOURCES_EXIST",
            "subject": "resource/workflow"
          }
        ]
      }
    }
  ]
}

Project with 1 resource/report resource (no rule in the config):
DeleteProject → HTTP 400

{
  "code": "failed_precondition",
  "message": "project cannot be deleted yet",
  "details": [
    {
      "type": "google.rpc.PreconditionFailure",
      "value": "CiIKD1JFU09VUkNFU19FWElTVBIPcmVzb3VyY2UvcmVwb3J0",
      "debug": {
        "violations": [
          {
            "type": "RESOURCES_EXIST",
            "subject": "resource/report"
          }
        ]
      }
    }
  ]
}

Disabled project with 1 AOI:
DeleteProject → HTTP 400

{
  "code": "failed_precondition",
  "message": "project cannot be deleted yet",
  "details": [
    {
      "type": "google.rpc.PreconditionFailure",
      "value": "Ch8KD1JFU09VUkNFU19FWElTVBIMcmVzb3VyY2UvYW9p",
      "debug": {
        "violations": [
          {
            "type": "RESOURCES_EXIST",
            "subject": "resource/aoi"
          }
        ]
      }
    }
  ]
}

Each refusal is logged as a warning, for example:

{"level": "WARN", "msg": "delete blocked", "org_id": "", "project_id": "e5969d9b-a95d-4fce-85ac-62f230ff5563", "blockers": 3}

Other checks

Check Expected Observed
The refused project is untouched row live, 5 live row live, 5 live
Delete by an org viewer (no delete permission) permission_denied permission_denied
Delete an empty project (owner) ok ok
Delete the first project above after deleting its 4 blocking resources ok ok
CheckProjectDelete on an org's project holding an AOI can_delete=false, blockers=[('resource/aoi', 1)] can_delete=false, blockers=[('resource/aoi', 1)]
DeleteOrganization on that org (project rules do not apply) ok ok
With enabled: false, delete that project ok ok

This branch was successfully deployed

1 active deployment
Preview — da8dac46 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.

2 participants