Phase B: session resolves and carries the active organization - #49
Conversation
…o-membership throw)
✅ Deploy Preview for moderaty ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
🤖 CodeAnt AI — Review Status
|
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe PR adds organization tenancy utilities, resolves active memberships in sessions, creates personal organizations during signup, and exposes memberships through the app layout. It also adds tests for fallback, repair, deterministic ordering, missing memberships, and transactional signup behavior. ChangesOrganization tenancy
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant ConsentFlow
participant ensurePersonalOrg
participant SignupDatabase
participant createSession
ConsentFlow->>ensurePersonalOrg: user identity
ensurePersonalOrg->>SignupDatabase: reuse or create organization and owner membership
SignupDatabase-->>ensurePersonalOrg: personal organization ID
ConsentFlow->>createSession: user ID and organization ID
createSession->>SignupDatabase: persist organization-linked session
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 |
🏁 CodeAnt Quality Gate ResultsCommit: ✅ Overall Status: PASSEDQuality Gate Details
|
Sequence DiagramThis PR resolves the session user against the active organization, falls back to the oldest membership when needed, and repairs the session. The application layout also loads the user's organization memberships for future switching. sequenceDiagram
participant Browser
participant App
participant Session
participant Organization
participant Database
Browser->>App: Request application page
App->>Session: Resolve session user
Session->>Organization: Resolve active organization
Organization->>Database: Load organization memberships
Database-->>Organization: Membership and organization data
alt Active membership exists
Organization-->>Session: Active organization context
else Active membership is missing
Organization-->>Session: Oldest organization context
Session->>Database: Repair session organization
end
Session-->>App: User with organization and plan
App->>Organization: Load organization list
Organization-->>App: User organization memberships
App-->>Browser: Render application page
Generated by CodeAnt AI |
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | ✅ 28 (≤ 100 complexity) |
| Duplication | ✅ 1 (≤ 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.
This PR successfully implements Phase B of the multi-tenancy plan, introducing organization-level session context. The implementation is thorough and well-tested with 308 passing tests including comprehensive coverage of edge cases (zero memberships, vanished org memberships, fallback logic, and session repair).
The code quality is solid:
- Proper error handling for data bugs (zero memberships throws rather than improvising access)
- Deterministic fallback logic using oldest membership
- Session repair mechanism for vanished org memberships
- Clean separation of concerns with the new
org.tsmodule - No security vulnerabilities identified
All changes align with the stated goals and maintain backward compatibility while preparing for future phases. The test coverage validates both happy paths and edge cases including mutation testing verification.
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 QodoResolve active organization during session hydration (multi-tenancy Phase B)
AI Description
Diagram
High-Level Assessment
Files changed (4)
|
PR Code Suggestions ✨Latest suggestions up to commit
|
| Category | Suggestion | Severity | Generated at (UTC) |
| Api mismatch |
New accounts without a created organization receive unusable sessions and fail every authenticated requestThe new invariant makes every session resolution throw when a user has no src/lib/server/session.ts [110-113] 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/session.ts
**Line:** 110:113
**Comment:**
*Api Mismatch: The new invariant makes every session resolution throw when a user has no membership, but the new-account consent transaction still creates only the user, consent, and session. A newly registered user therefore receives a valid cookie and is immediately rejected with a 500 on the next request. Create the personal organization and owner membership in the same signup transaction before creating the session, or do not enforce this invariant until that migration is complete.
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 | Critical | 2026-08-02 22:44
|
There was a problem hiding this comment.
Pull Request Overview
The PR successfully implements the functional requirements for Phase B, making sessions organization-aware and handling fallback logic. However, the implementation is currently not up to standards due to a critical runtime risk in the sorting logic and performance inefficiencies.
A primary concern is the use of localeCompare on what are likely Date objects in the organization sorting logic; this will cause a runtime crash. Additionally, the application currently performs redundant database queries by fetching user memberships twice on every page load—once during session resolution and again in the layout server load. These findings, combined with inconsistent field naming and logic duplication in org.ts, should be addressed before merging to ensure stability and maintainability.
About this PR
- While the session tests exercise the organization logic indirectly, the new 'asOrgRole' helper and the 'listOrgMemberships' sorting logic lack direct unit tests. Given the risk of runtime errors in the current sorting implementation, explicit test coverage is required.
- There is a systemic inefficiency where organization membership data is fetched multiple times during a single request lifecycle. Since 'resolveActiveOrg' already retrieves this data to determine the fallback organization, the full list should be persisted to 'event.locals' to avoid subsequent queries in '+layout.server.ts'.
Test suggestions
- Resolve session with a valid active organization ID
- Fall back to oldest membership when active_org_id is null
- Fall back to oldest membership and repair DB when active_org_id membership no longer exists
- Fail with an error when a user has no organization memberships
- List all organization memberships for a user sorted by creation date
- Verify role narrowing (asOrgRole) throws on invalid input
Prompt proposal for missing tests
Consider implementing these tests if applicable:
1. List all organization memberships for a user sorted by creation date
2. Verify role narrowing (asOrgRole) throws on invalid input
TIP Improve review quality by adding custom instructions
TIP How was this review? Give us feedback
| .where(eq(memberships.userId, userId)) | ||
| .all(); | ||
| if (rows.length === 0) return null; | ||
| const sorted = [...rows].sort((a, b) => a.membershipCreatedAt.localeCompare(b.membershipCreatedAt)); |
There was a problem hiding this comment.
🔴 HIGH RISK
JavaScript sorting with localeCompare is less efficient than SQL ORDER BY and will crash at runtime if 'createdAt' is a Date object (standard for Drizzle) rather than a string. Update 'src/lib/server/org.ts' to use 'orderBy' in the Drizzle queries and remove unnecessary array spreading.
| export const load: LayoutServerLoad = async ({ locals }) => { | ||
| if (!locals.user) throw redirect(302, '/login'); | ||
| return { user: locals.user }; | ||
| return { user: locals.user, orgs: await listOrgMemberships(locals.user.id) }; |
There was a problem hiding this comment.
🟡 MEDIUM RISK
Suggestion: This layout load triggers a redundant database query. Since 'resolveActiveOrg' already fetches the full membership list during session resolution, refactor the resolution logic to carry the membership list in 'locals.user' so it can be reused here without a second network roundtrip.
| const rows = await db | ||
| .select({ orgId: organizations.id, name: organizations.name, role: memberships.role, createdAt: memberships.createdAt }) | ||
| .from(memberships) | ||
| .innerJoin(organizations, eq(memberships.orgId, organizations.id)) | ||
| .where(eq(memberships.userId, userId)) | ||
| .all(); |
There was a problem hiding this comment.
🟡 MEDIUM RISK
Suggestion: The logic for querying user memberships is duplicated between 'listOrgMemberships' and 'resolveActiveOrg', and uses inconsistent aliases (e.g., 'name' vs 'orgName'). Extract the common database query into a private helper function to ensure consistent field naming and reduce maintenance overhead.
Code Review by Qodo
Context used✅ Compliance rules (platform):
73 rules 1.
|
| export const load: LayoutServerLoad = async ({ locals }) => { | ||
| if (!locals.user) throw redirect(302, '/login'); | ||
| return { user: locals.user }; | ||
| return { user: locals.user, orgs: await listOrgMemberships(locals.user.id) }; |
There was a problem hiding this comment.
3. Unconditional org list load 🐞 Bug ➹ Performance
The (app) layout now always calls listOrgMemberships() when its server load runs, adding an extra memberships+organizations join query even before the org switcher UI is used. This increases DB work/latency on authenticated layout loads and could be deferred or made conditional if it’s not yet needed.
Agent Prompt
### Issue description
`+layout.server.ts` now eagerly fetches `orgs` via `listOrgMemberships()` for every authenticated layout load execution, even though the data is described as “unused until Phase D”.
### Issue Context
`listOrgMemberships()` performs a join + in-memory sort and returns the full org list to the client.
### Fix Focus Areas
- src/routes/(app)/+layout.server.ts[19-29]
- src/lib/server/org.ts[73-84]
### Suggested fix
- If the switcher isn’t rendered yet, consider gating the fetch behind a feature flag or only fetching when the component is enabled.
- Alternative: expose a dedicated endpoint for org list and fetch lazily when the user opens the switcher.
- If keeping eager load, consider caching/memoizing org list per-request or selecting with explicit ORDER BY in SQL to avoid extra JS work.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
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: 3
🤖 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/org.ts`:
- Around line 48-84: Extract the shared memberships/organizations select, join,
and user filter into a reusable helper, then have both resolveActiveOrg and
listOrgMemberships call it. Replace localeCompare-based createdAt sorting with
deterministic ISO-string ordering using the query’s
orderBy(memberships.createdAt), or an explicit < and > comparator if sorting in
memory; preserve oldest-membership ordering and existing mapping and fallback
behavior.
In `@src/lib/server/session.ts`:
- Around line 114-124: In getSessionUser, merge the activeOrgId repair and
expiry renewal updates into one sessions UPDATE by building a single update
payload from the resolved fallback and renewal conditions, then executing one
db.update(sessions).set(...).where(eq(sessions.id, token)) call when either
change is needed. Preserve the existing logging and returned expiry/renewed
values.
In `@src/routes/`(app)/+layout.server.ts:
- Around line 21-29: Update session resolution so
getSessionUser/resolveActiveOrg returns the fetched membership list and
hooks.server.ts exposes it on locals alongside locals.user. Then change the
layout load to return that locals membership data instead of calling
listOrgMemberships, removing the duplicate query while preserving the existing
unauthenticated redirect and response shape.
🪄 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: 2c3d22e9-45bd-4057-96b3-ad57d6a872b7
📒 Files selected for processing (4)
src/lib/server/org.tssrc/lib/server/session.test.tssrc/lib/server/session.tssrc/routes/(app)/+layout.server.ts
| if (resolved.fellBack && row.session.activeOrgId !== null) { | ||
| console.info(`session for user ${row.user.id}: active org ${row.session.activeOrgId} no longer valid, falling back to ${resolved.org.orgId}`); | ||
| await db.update(sessions).set({ activeOrgId: resolved.org.orgId }).where(eq(sessions.id, token)); | ||
| } | ||
| const user = { ...row.user, plan: resolved.org.plan, orgId: resolved.org.orgId, orgName: resolved.org.orgName, orgRole: resolved.org.orgRole }; | ||
| if (expiresMs - Date.now() < RENEW_BELOW_MS) { | ||
| const expiresAt = new Date(Date.now() + SESSION_TTL_MS).toISOString(); | ||
| await db.update(sessions).set({ expiresAt }).where(eq(sessions.id, token)); | ||
| return { user: row.user, expiresAt, renewed: true }; | ||
| return { user, expiresAt, renewed: true }; | ||
| } | ||
| return { user: row.user, expiresAt: row.session.expiresAt, renewed: false }; | ||
| return { user, expiresAt: row.session.expiresAt, renewed: false }; |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win
Merge the repair and renewal writes into one UPDATE.
Lines 116 and 121 can both execute against the same sessions row in a single getSessionUser call (repaired active org and renewed expiry). Combine them into one db.update(sessions).set({...}) call to save a round trip on this hot path.
⚡ Proposed merge
- if (resolved.fellBack && row.session.activeOrgId !== null) {
- console.info(`session for user ${row.user.id}: active org ${row.session.activeOrgId} no longer valid, falling back to ${resolved.org.orgId}`);
- await db.update(sessions).set({ activeOrgId: resolved.org.orgId }).where(eq(sessions.id, token));
- }
+ const updates: Partial<typeof sessions.$inferInsert> = {};
+ if (resolved.fellBack && row.session.activeOrgId !== null) {
+ console.info(`session for user ${row.user.id}: active org ${row.session.activeOrgId} no longer valid, falling back to ${resolved.org.orgId}`);
+ updates.activeOrgId = resolved.org.orgId;
+ }
const user = { ...row.user, plan: resolved.org.plan, orgId: resolved.org.orgId, orgName: resolved.org.orgName, orgRole: resolved.org.orgRole };
- if (expiresMs - Date.now() < RENEW_BELOW_MS) {
- const expiresAt = new Date(Date.now() + SESSION_TTL_MS).toISOString();
- await db.update(sessions).set({ expiresAt }).where(eq(sessions.id, token));
- return { user, expiresAt, renewed: true };
- }
+ if (expiresMs - Date.now() < RENEW_BELOW_MS) {
+ const expiresAt = new Date(Date.now() + SESSION_TTL_MS).toISOString();
+ updates.expiresAt = expiresAt;
+ if (Object.keys(updates).length > 0) await db.update(sessions).set(updates).where(eq(sessions.id, token));
+ return { user, expiresAt, renewed: true };
+ }
+ if (Object.keys(updates).length > 0) await db.update(sessions).set(updates).where(eq(sessions.id, token));
return { user, expiresAt: row.session.expiresAt, renewed: false };📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (resolved.fellBack && row.session.activeOrgId !== null) { | |
| console.info(`session for user ${row.user.id}: active org ${row.session.activeOrgId} no longer valid, falling back to ${resolved.org.orgId}`); | |
| await db.update(sessions).set({ activeOrgId: resolved.org.orgId }).where(eq(sessions.id, token)); | |
| } | |
| const user = { ...row.user, plan: resolved.org.plan, orgId: resolved.org.orgId, orgName: resolved.org.orgName, orgRole: resolved.org.orgRole }; | |
| if (expiresMs - Date.now() < RENEW_BELOW_MS) { | |
| const expiresAt = new Date(Date.now() + SESSION_TTL_MS).toISOString(); | |
| await db.update(sessions).set({ expiresAt }).where(eq(sessions.id, token)); | |
| return { user: row.user, expiresAt, renewed: true }; | |
| return { user, expiresAt, renewed: true }; | |
| } | |
| return { user: row.user, expiresAt: row.session.expiresAt, renewed: false }; | |
| return { user, expiresAt: row.session.expiresAt, renewed: false }; | |
| if (resolved.fellBack && row.session.activeOrgId !== null) { | |
| console.info(`session for user ${row.user.id}: active org ${row.session.activeOrgId} no longer valid, falling back to ${resolved.org.orgId}`); | |
| updates.activeOrgId = resolved.org.orgId; | |
| } | |
| const user = { ...row.user, plan: resolved.org.plan, orgId: resolved.org.orgId, orgName: resolved.org.orgName, orgRole: resolved.org.orgRole }; | |
| if (expiresMs - Date.now() < RENEW_BELOW_MS) { | |
| const expiresAt = new Date(Date.now() + SESSION_TTL_MS).toISOString(); | |
| updates.expiresAt = expiresAt; | |
| if (Object.keys(updates).length > 0) await db.update(sessions).set(updates).where(eq(sessions.id, token)); | |
| return { user, expiresAt, renewed: true }; | |
| } | |
| if (Object.keys(updates).length > 0) await db.update(sessions).set(updates).where(eq(sessions.id, token)); | |
| return { user, expiresAt: row.session.expiresAt, renewed: false }; |
🤖 Prompt for 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.
In `@src/lib/server/session.ts` around lines 114 - 124, In getSessionUser, merge
the activeOrgId repair and expiry renewal updates into one sessions UPDATE by
building a single update payload from the resolved fallback and renewal
conditions, then executing one
db.update(sessions).set(...).where(eq(sessions.id, token)) call when either
change is needed. Preserve the existing logging and returned expiry/renewed
values.
| import { listOrgMemberships } from '$lib/server/org'; | ||
|
|
||
| import type { LayoutServerLoad } from './$types'; | ||
|
|
||
| // Everything under (app) requires a signed-in user. | ||
| export const load: LayoutServerLoad = ({ locals }) => { | ||
| export const load: LayoutServerLoad = async ({ locals }) => { | ||
| if (!locals.user) throw redirect(302, '/login'); | ||
| return { user: locals.user }; | ||
| return { user: locals.user, orgs: await listOrgMemberships(locals.user.id) }; | ||
| }; |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Inspect how locals.user is populated and whether membership rows are already available.
fd hooks.server.ts src
rg -n -B3 -A15 'getSessionUser\(' src/hooks.server.ts 2>/dev/nullRepository: Bonobo791/Moderaty
Length of output: 832
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- session symbols ---'
ast-grep outline src/lib/server/session.ts --view expanded
printf '%s\n' '--- session implementation ---'
sed -n '1,260p' src/lib/server/session.ts
printf '%s\n' '--- organization symbols ---'
ast-grep outline src/lib/server/org.ts --view expanded
printf '%s\n' '--- organization implementation ---'
sed -n '1,260p' src/lib/server/org.ts
printf '%s\n' '--- locals declarations and org-list consumers ---'
rg -n -A8 -B4 'App\\.Locals|interface Locals|listOrgMemberships|orgs' srcRepository: Bonobo791/Moderaty
Length of output: 18304
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re
session = Path("src/lib/server/session.ts").read_text()
org = Path("src/lib/server/org.ts").read_text()
hooks = Path("src/hooks.server.ts").read_text()
layout = Path("src/routes/(app)/+layout.server.ts").read_text()
checks = {
"session calls resolveActiveOrg": bool(re.search(r"resolveActiveOrg\(row\.user\.id,\s*row\.session\.activeOrgId\)", session)),
"resolveActiveOrg selects all membership rows": bool(re.search(r"\.where\(eq\(memberships\.userId,\s*userId\)\)\s*\.all\(\)", org[org.index("export async function resolveActiveOrg"):org.index("/** Every org")])),
"listOrgMemberships performs a second membership query": bool(re.search(r"export async function listOrgMemberships[\s\S]*?\.where\(eq\(memberships\.userId,\s*userId\)\)\s*\.all\(\)", org)),
"hooks exposes only resolution.user": bool(re.search(r"event\.locals\.user\s*=\s*resolution\?\.user\s*\?\?\s*null", hooks)),
"layout calls listOrgMemberships": "listOrgMemberships(locals.user.id)" in layout,
}
for name, result in checks.items():
print(f"{'PASS' if result else 'FAIL'}: {name}")
if not all(checks.values()):
raise SystemExit(1)
PYRepository: Bonobo791/Moderaty
Length of output: 383
Reuse the membership rows from session resolution
resolveActiveOrg already fetches all membership and organization rows, but getSessionUser and hooks.server.ts expose only the active organization through locals.user. Return the membership list from session resolution and expose it on locals, then remove the second listOrgMemberships query from this layout.
🤖 Prompt for 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.
In `@src/routes/`(app)/+layout.server.ts around lines 21 - 29, Update session
resolution so getSessionUser/resolveActiveOrg returns the fetched membership
list and hooks.server.ts exposes it on locals alongside locals.user. Then change
the layout load to return that locals membership data instead of calling
listOrgMemberships, removing the duplicate query while preserving the existing
unauthenticated redirect and response shape.
Qodo + Codacy (valid): "oldest membership" sorted only by created_at in JS over rows with no SQL ORDER BY — a timestamp tie (batched inserts) made the active org (and orgRole/plan) depend on undefined row order. Both queries now ORDER BY created_at, org_id in SQL, so session resolution and the nav list share one total order. Mutation-checked: DESC ordering fails the tie-break test; restored green. Qodo (valid, minor): fellBack was true whenever activeOrgId was null, though no explicit choice was invalidated. It now means exactly "an explicit active org lost its membership" — the null case is the ordinary fresh-login path. Note: the tie-break test's pre-fix behavior was order-dependent by definition (undefined row order happened to pass); the fellBack test failed cleanly pre-fix (expected true to be false). Both are regression locks now.
Review-finding triage (executor, 9f565ce)All 6 inline findings reviewed (Amazon Q's summary had no findings; CodeRabbit is still processing — its findings get the same treatment when they land). ✅ VALID — fixed in
|
…merged session update
Review-finding triage round 2 (executor, a43eda6)CodeRabbit's three inline findings (posted against fede1c1) and CodeAnt's critical suggestion, re-checked against current code:
Gates: 311 tests green (310 + 1 new), |
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/routes/consent/`+page.server.ts:
- Around line 96-101: Shorten the contiguous transaction comment surrounding the
same-sub sign-up flow in the server consent handler to fewer than 500
characters. Preserve only the essential transactional guarantees and
retry/idempotency behavior, and move any remaining durable design rationale to
project documentation if needed.
🪄 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: ce15a27b-be88-46ab-8e31-ddeaa443aa07
📒 Files selected for processing (4)
src/lib/server/org.tssrc/lib/server/session.tssrc/routes/consent/+page.server.tssrc/routes/consent/consent.test.ts
| // same-sub sign-up; ensurePersonalOrg makes that race idempotent | ||
| // too. Every user needs a personal org — session resolution fails | ||
| // loudly on zero memberships. User, personal org, consent record, | ||
| // and first session commit as ONE unit — a session-write failure | ||
| // rolls everything back and the parked cookie lets the same | ||
| // submission retry cleanly. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Keep the transaction comment below 500 characters.
The contiguous comment at Lines 89-101 exceeds the allowed length. Shorten it or move durable design rationale to project documentation.
As per coding guidelines, "Do not store comment text longer than 500 characters."
🤖 Prompt for 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.
In `@src/routes/consent/`+page.server.ts around lines 96 - 101, Shorten the
contiguous transaction comment surrounding the same-sub sign-up flow in the
server consent handler to fewer than 500 characters. Preserve only the essential
transactional guarantees and retry/idempotency behavior, and move any remaining
durable design rationale to project documentation if needed.
Source: Coding guidelines
Phase A/B database-engineering review (executor, 1f7582b)Full pass over Phase A (migration 0012 + schema) and Phase B using the sqlite-engineering checklists (field-failures pre-flight, migrations, schema-design, runtimes, indexing). Findings and verdicts: FIXED — account deletion left tenancy behind (PII). VERIFIED CLEAN:
RECONCILED — dev ADVISORY (not fixed, needs maintainer decision): |
|




User description
Behavior
Second phase of the multi-tenancy plan. Production already has migration 0012 (Phase A gate cleared), so the tenant code can now ship.
org.ts(new):resolveActiveOrg— session'sactive_org_idwhen the membership still exists, else the user's OLDEST membership (deterministic); returns null only for zero memberships (a data bug).listOrgMembershipsfeeds the nav switcher (Phase D).asOrgRolenarrows raw role strings, throwing on unknown values.session.ts:SessionUsergainsorgId/orgName/orgRole;planis now the ACTIVE ORGANIZATION's plan (theusers.planselect is removed — the LEGACY comment from Phase A is now literally true).getSessionUserresolves the tenant on every resolution: fallback is logged loudly (console.info) AND repairs the session row; zero memberships throws (console.error+ throw) — never signs out, never improvises access.createSessionaccepts an optionalactiveOrgId(signup passes the personal org in Phase C).(app)/+layout.server.ts: exposesorgsfor the (Phase D) nav switcher; unused until then.No route behavior changes yet —
ownedChannelstill gates onchannels.userIduntil Phase C.Verification
npm run check/build/testgreen (308 tests; 4 new session tests)UPDATEmakes the fallback test fail; restored and greenManual check still owed (needs a browser + Google account)
Plan B4 asks for a dev sign-in smoke test (dashboard renders with no console errors using the org-carrying session). Not executable headlessly — flagging honestly; the deployed preview + a dev sign-in covers it.
Ref: Moderaty Multi-Tenancy Plan, Phase B (steps B1–B4).
CodeAnt-AI Description
Resolve the signed-in user’s active organization and keep organization data consistent
What Changed
Impact
✅ Consistent organization and plan context across signed-in pages✅ Reliable recovery when an organization membership is removed✅ Complete removal of personal tenancy data on account deletion💡 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.