Phase E: deletion succession — ownership transfer + sole-member org dissolution - #53
Conversation
🤖 CodeAnt AI — Review Status
|
✅ Deploy Preview for moderaty ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Warning Review limit reached
Next review available in: 19 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?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 reviews. How do review 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 refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughSummary by CodeRabbit
WalkthroughAccount deletion now evaluates every organization membership. Sole-member organizations are dissolved. Shared organizations remain, the deleting user is removed, and ownership transfers to the oldest eligible member when required. Tests cover cleanup, succession, preservation, and tombstoning. ChangesOrganization deletion lifecycle
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant Deletion as src/lib/server/deletion.ts
participant Database as Organization database
participant Logger as Succession logger
User->>Deletion: Request account deletion
Deletion->>Database: Inspect organization memberships
Database-->>Deletion: Return surviving members and roles
Deletion->>Database: Remove memberships or dissolve selected organizations
Deletion->>Logger: Log ownership succession after commit
Deletion-->>User: Tombstone account
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Sequence DiagramThis PR extends account deletion to evaluate every organization membership. Sole-member organizations are fully dissolved, while surviving shared organizations retain their data and promote a successor when the deleting user was the last owner. sequenceDiagram
participant User
participant DeletionService
participant Database
participant SharedOrg
User->>DeletionService: Request account deletion
DeletionService->>Database: Find memberships and organization members
alt Sole-member organization
DeletionService->>Database: Delete organization data and organization
else Shared organization survives
alt Deleting user was the last owner
DeletionService->>Database: Promote oldest admin or member
Database-->>SharedOrg: Organization remains owned
else Another owner survives
Database-->>SharedOrg: Keep roles unchanged
end
DeletionService->>Database: Remove membership and detach connected channels
end
DeletionService->>Database: Delete sessions and tombstone user
Database-->>User: Account deletion complete
Generated by CodeAnt AI |
🏁 CodeAnt Quality Gate ResultsCommit: ✅ Overall Status: PASSEDQuality Gate Details
|
There was a problem hiding this comment.
The implementation successfully extends account deletion to handle shared organization ownership succession and sole-member organization dissolution. The logic correctly promotes the oldest admin (or oldest member as fallback) when the last owner leaves a shared organization, and properly dissolves sole-member shared organizations. The comprehensive test suite validates all scenarios including edge cases. All checks pass (0 errors, 368 tests passed).
You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.
PR Summary by QodoAccount deletion: shared-org ownership succession + dissolve sole-member shared orgs
AI Description
Diagram
High-Level Assessment
Files changed (2)
|
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | ✅ 45 (≤ 100 complexity) |
| Duplication | ✅ 0 (≤ 1 duplication) |
AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
Pull Request Overview
The PR successfully implements organization ownership succession and dissolution logic, addressing several critical account deletion workflows. However, the current implementation is not up to standards according to Codacy, primarily due to code duplication in the test suite and an N+1 query pattern in the succession logic.
A significant gap was identified in the test coverage: there is no test scenario verifying the requirement that userId must be used as a tie-breaker for seniority when createdAt timestamps are identical. Additionally, the use of localeCompare on a field that might be returned as a Date object by the database driver poses a runtime stability risk.
About this PR
- The test suite for deletion succession contains several patterns of duplicated logic, specifically regarding console mock capturing and organization dissolution assertions. Centralizing these into helpers would improve the resilience of the test suite as the deletion logic evolves.
Test suggestions
- Plain member leaves a shared organization: verify only the user's membership is removed while the org and team data remain.
- Last owner of a shared organization leaves with admins surviving: verify the oldest admin is promoted to owner and the event is logged.
- Last owner of a shared organization leaves with no admins surviving: verify the oldest member is promoted to owner.
- Multiple owners exist: verify no succession or promotion occurs when one owner deletes their account.
- Sole member of a shared organization deletes: verify the organization and all associated data (channels, comments, moderation history) are dissolved.
- Succession tie-breaking: verify 'userId' is used to determine seniority when 'createdAt' timestamps are identical.
Prompt proposal for missing tests
Consider implementing these tests if applicable:
1. Succession tie-breaking: verify 'userId' is used to determine seniority when 'createdAt' timestamps are identical.
TIP Improve review quality by adding custom instructions
TIP How was this review? Give us feedback
| ]); | ||
| const info = vi.spyOn(console, 'info').mockImplementation(() => {}); | ||
| await deleteUserRecords(userId); | ||
| const successionLogged = info.mock.calls.length > 0; | ||
| info.mockRestore(); | ||
|
|
||
| const remaining = await teamMemberships('org-team'); |
There was a problem hiding this comment.
🟡 MEDIUM RISK
Suggestion: This test logic for capturing succession logs is duplicated. Consider extracting it into a helper to keep the test suite DRY.
Try running the following prompt in your IDE agent:
In src/lib/server/deletion.test.ts, create a helper function called
runDeletionWithLogCapture(userId: string)that mocksconsole.info, executesdeleteUserRecords, and returns the mock's call state. Use this helper in the tests for admin promotion and survivor role checks.
| const others = await tx | ||
| .select({ userId: memberships.userId, role: memberships.role, createdAt: memberships.createdAt }) | ||
| .from(memberships) | ||
| .where(and(eq(memberships.orgId, membership.orgId), ne(memberships.userId, userId))) | ||
| .all(); |
There was a problem hiding this comment.
🟡 MEDIUM RISK
Suggestion: The query for 'others' inside the loop over userMemberships creates an N+1 query pattern, which increases database roundtrips and holds transaction locks longer than necessary.
Refactor the succession logic to fetch all membership rows for all organizations in userMemberships using a single inArray query before the loop, then process the ranking and succession logic in-memory.
There was a problem hiding this comment.
Warning
CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.
Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/server/deletion.test.ts`:
- Around line 317-322: Replace the shared info.mock.calls.length checks in
src/lib/server/deletion.test.ts at lines 317-322 and 366-381 with one reusable
helper that extracts logged succession messages from the console.info spy. In
the positive test, assert exactly one message reports promotion of admin-old in
org-team; in the negative test, assert no message reports a promotion in
org-team.
- Around line 281-297: Extend the surviving shared-org test around
deleteUserRecords by seeding a second org-team channel owned by the deleting
user, and import the exported WIPED_REFRESH_TOKEN from deletion.ts. After
deletion, assert this connector channel remains with userId null and
refreshTokenEnc set to WIPED_REFRESH_TOKEN, while preserving the existing
assertions for UC-team and team memberships.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 82edc68a-d47e-42c5-a173-ae357b4c5b61
📒 Files selected for processing (2)
src/lib/server/deletion.test.tssrc/lib/server/deletion.ts
…erationData helpers)
Code Review by Qodo
Context used✅ Compliance rules (platform):
74 rules 1.
|
… logs, content-asserted tests
Review triage (commit
|
There was a problem hiding this comment.
Warning
CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.
Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/server/deletion.test.ts`:
- Around line 258-267: Extend the deletion tests around deleteWithSuccessionLogs
and the related assertions to cover a failed final tombstone update: force that
update to reject, assert deleteUserRecords rejects, and verify console.info
receives no calls. Ensure the setup reaches the rollback boundary and restores
mocks so the test specifically fails if succession logs are emitted before the
transaction commits.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 673d45f2-7016-4728-896f-39bfb1d2aab3
📒 Files selected for processing (2)
src/lib/server/deletion.test.tssrc/lib/server/deletion.ts
PR Code Suggestions ✨Previous suggestions up to commit
|
| Category | Suggestion | Severity | Generated at (UTC) |
| Incomplete implementation |
Channels owned by the deleting user without an organization are detached instead of erasedThis cleanup query finds channels to delete only through src/lib/server/deletion.ts [178-180] Why it matters? 🤔
(Use Cmd/Ctrl + Click for best experience) Prompt for AI Agent 🤖This is a comment left during a code review.
**Path:** src/lib/server/deletion.ts
**Line:** 178:180
**Comment:**
*Incomplete Implementation: This cleanup query finds channels to delete only through `channels.orgId`. During the documented expand window, a channel can have `userId = userId` while `orgId` is still `NULL`; such a channel is excluded from the personal-org deletion, then the later broad update detaches it and wipes its token instead of deleting the channel and its moderation history. Include the user's owned channels with a null organization in the dissolution set, or otherwise fail loudly on this inconsistent ownership state.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix | Major | 2026-08-03 13:38
|
Latest suggestions up to commit 658d06e
| Category | Suggestion | Severity | Generated at (UTC) |
| Race condition |
Non-conditional succession updates can overwrite concurrent membership changesThe promotion decision is based on an earlier snapshot, but the UPDATE only matches src/lib/server/deletion.ts [163-167] Why it matters? 🤔
(Use Cmd/Ctrl + Click for best experience) Prompt for AI Agent 🤖This is a comment left during a code review.
**Path:** src/lib/server/deletion.ts
**Line:** 163:167
**Comment:**
*Race Condition: The promotion decision is based on an earlier snapshot, but the UPDATE only matches `orgId` and `successor.userId`. A concurrent role change or membership change can therefore overwrite a newer role or promote a member whose selected role is no longer valid. Include the last-owner and successor-role predicates in the write-time condition, and abort if the conditional update affects no rows.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix | Major | 2026-08-03 13:51
|
| Stale reference |
Detached channels remain active, allowing in-flight processing to act after account deletionDetaching a surviving channel only wipes its token and clears src/lib/server/deletion.ts [191-194] Why it matters? 🤔
(Use Cmd/Ctrl + Click for best experience) Prompt for AI Agent 🤖This is a comment left during a code review.
**Path:** src/lib/server/deletion.ts
**Line:** 191:194
**Comment:**
*Stale Reference: Detaching a surviving channel only wipes its token and clears `userId`; it leaves `active` unchanged. `runChannel` rechecks only `active` before enforcement, so an in-flight run that loaded the old access token before deletion can pass the post-deletion check and still execute YouTube moderation actions after the connector has been deleted. Mark the channel inactive as part of deletion, and ensure the processing checks observe that state before enforcement.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix | Major | 2026-08-03 13:51
|
Review triage round 2 (commit
|
…acy duplication gate)
|
Review triage round 3 (commit
|




User description
Behavior
Phase E of the multi-tenancy plan: makes account deletion (
deleteUserRecords) tenancy-complete for shared organizations.createdAt,userIdbreaks ties). The promotion is logged loudly (console.info). A surviving shared org is never left ownerless.Reconciliation with main
The plan's E1 "find this anchor" is stale: main's
deleteUserRecordsis already the Phase-C tenancy-aware version, so this is an extension, not a replacement. Plan E2 test 5's "connected channel userId untouched" loses to main's detach semantics (reconcile-with-main rule): connector channels in surviving orgs keepuserId = NULL+WIPED_REFRESH_TOKENsentinel.Tests (red-first, then green)
Verification
npm run check— 0 errors, 0 warningsnpm run test— 45 files, 368 tests passednpm run build— cleancodacy-analysison both changed files — 0 issuesCodeAnt-AI Description
Preserve shared organizations during account deletion and transfer ownership when needed
What Changed
Impact
✅ Shared organizations survive member deletion✅ No ownerless organizations after account deletion✅ Fewer abandoned organizations and channels💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.