Skip to content

refactor(cloud): simplify pass on cloud infra (main)#41

Merged
Gradata merged 6 commits into
mainfrom
refactor/simplify-cloud-infra
Apr 15, 2026
Merged

refactor(cloud): simplify pass on cloud infra (main)#41
Gradata merged 6 commits into
mainfrom
refactor/simplify-cloud-infra

Conversation

@Gradata

@Gradata Gradata commented Apr 14, 2026

Copy link
Copy Markdown
Owner

Summary

  • Convert in-scope route handlers to use get_brain_for_request / get_current_user_id as FastAPI Depends(), eliminating repeated HTTPBearer + credentials plumbing across activity, analytics, brains, corrections, lessons, meta_rules, rule_patches, users.
  • Extract _brain_detail builder in brains.py (removes 3x duplicated BrainDetail construction) and _hydrate_workspaces helper in users.py (unifies profile GET/PATCH hydration).
  • Collapse _is_demo_data / _is_demo_metadata pair to a single _is_demo.
  • Run analytics lesson / correction / event fetches concurrently via asyncio.gather.
  • Tidy operator.list_customers sort dispatch into a dict lookup; normalize team.py role value extraction now that InviteRole is a str, Enum.
  • Drop narrating comments and unused Request parameters throughout.
  • Switch test_new_routes.py fixture to FastAPI dependency_overrides, which is the correct way to stub a Depends() dependency.

Net: 12 files, -132 LOC (98 insertions, 230 deletions).

Out-of-scope files (touched by PRs #33 / #36 / #37) were not modified: auth.py, config.py, middleware.py, sentry_init.py, billing.py, gdpr.py, telemetry.py, rate_limit.py, cloud/migrations/*.sql.

Test plan

  • pytest cloud/tests/ — 115/115 passing on baseline and after changes
  • CI green on the PR
  • Ruff/pyright clean (no new violations introduced)

Generated with Gradata

Use get_brain_for_request / get_current_user_id as FastAPI Depends()
throughout the route layer, dropping the repeated HTTPBearer +
credentials plumbing and the inline get_brain_for_request calls.
Extract BrainDetail and workspace-hydration helpers, collapse the
duplicated _is_demo_data / _is_demo_metadata pair, run analytics
fetches concurrently via asyncio.gather, and tidy operator sort
dispatch + team role-value extraction.

Switch test fixture to FastAPI dependency_overrides so the refactored
Depends() wiring can be stubbed cleanly.

12 files changed, -132 LOC net. Zero regressions: 115/115 cloud tests
passing.

Co-Authored-By: Gradata <noreply@gradata.ai>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@Gradata has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 41 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 0 minutes and 41 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 110e7e98-da8b-4208-873c-2229f31cd664

📥 Commits

Reviewing files that changed from the base of the PR and between 8a098bc and 96f3f82.

📒 Files selected for processing (2)
  • cloud/app/routes/operator.py
  • cloud/migrations/005_lessons_unique_constraint.sql
📝 Walkthrough

Walkthrough

The PR refactors multiple route handlers to remove HTTPBearer security setup and shift brain lookup/authorization from inside handlers into the dependency injection layer via Depends(get_brain_for_request). It also consolidates helper functions, optimizes database queries with concurrent operations, and introduces a unique constraint on the lessons table.

Changes

Cohort / File(s) Summary
Route Handler Auth Refactoring (Simple Signature)
cloud/app/routes/activity.py, cloud/app/routes/corrections.py, cloud/app/routes/lessons.py, cloud/app/routes/meta_rules.py
Removed HTTPBearer/credentials parameters and shifted brain resolution to Depends(get_brain_for_request) dependency injection. Handlers now receive injected brain: dict and use brain["id"] for database operations.
Analytics Route Refactoring
cloud/app/routes/analytics.py
Removed HTTPBearer auth plumbing; replaced sequential database calls with concurrent asyncio.gather for lessons, corrections, and events; deleted _fetch_all helper function.
Brains Route Refactoring
cloud/app/routes/brains.py
Updated four endpoints (get_brain, update_brain, delete_brain, clear_demo) to use injected brain: dict; centralized BrainDetail construction into new _brain_detail() helper; consolidated demo-flag detection from _is_demo_data and _is_demo_metadata into single _is_demo() predicate.
Rule Patches Route Refactoring
cloud/app/routes/rule_patches.py
Removed HTTPBearer setup and credentials parameters; both list_rule_patches and rollback_patch now depend on Depends(get_brain_for_request). Removed brain_id parameter from rollback_patch function signature.
Sorting Logic & Member Tracking Optimization
cloud/app/routes/operator.py, cloud/app/routes/team.py
Operator: consolidated sorting logic using sort_keys dictionary and single rows.sort() call. Team: replaced imperative loop with comprehension for last_sync_at tracking; consolidated timestamp handling; changed role extraction to unconditional .value usage.
Test Fixture & Migration Updates
cloud/tests/test_new_routes.py, cloud/migrations/005_lessons_unique_constraint.sql
Test: updated valid_bearer_patches fixture to use dependency_overrides instead of monkeypatch for overriding get_brain_for_request. Migration: added UNIQUE (brain_id, description) constraint to lessons table with deduplication of existing data by updated_at, confidence, and id.
Documentation Cleanup
cloud/app/routes/sync.py
Removed explanatory inline comments; updated lessons comment to clarify upsert idempotency by (brain_id, description). No functional changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title references cloud infrastructure refactoring and matches the changeset's primary focus of simplifying route handlers by removing HTTPBearer plumbing and consolidating dependencies across multiple files.
Description check ✅ Passed The description provides detailed coverage of all major changes: dependency injection conversion, helper consolidation, concurrent fetching, sort refactoring, test fixture updates, and code reduction metrics.
Docstring Coverage ✅ Passed Docstring coverage is 92.59% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/simplify-cloud-infra

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

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Apr 14, 2026

Copy link
Copy Markdown

Deploying gradata-dashboard with  Cloudflare Pages  Cloudflare Pages

Latest commit: 96f3f82
Status: ✅  Deploy successful!
Preview URL: https://1ed7e6fb.gradata-dashboard.pages.dev
Branch Preview URL: https://refactor-simplify-cloud-infr.gradata-dashboard.pages.dev

View logs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cloud/app/routes/sync.py (1)

53-68: ⚠️ Potential issue | 🔴 Critical

The upsert lacks a unique constraint and will insert duplicates instead of updating existing lessons.

The comment claims lessons are idempotent by (brain_id, description), but the lessons table in cloud/migrations/001_initial_schema.sql defines only a UUID primary key and an index on brain_id. Without a UNIQUE constraint on (brain_id, description), PostgREST's resolution=merge-duplicates header (used in db.upsert) cannot detect conflicts—it relies on unique constraints to identify which rows should be updated. Every sync will insert new lesson rows instead of updating existing ones.

Add a UNIQUE constraint to the lessons table:

ALTER TABLE lessons 
ADD CONSTRAINT lessons_brain_id_description_key 
UNIQUE (brain_id, description);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cloud/app/routes/sync.py` around lines 53 - 68, The upsert of lessons via
db.upsert("lessons", ...) cannot merge duplicates because the lessons table
lacks a UNIQUE key on (brain_id, description); add a UNIQUE constraint named
e.g. lessons_brain_id_description_key to the lessons table (or include it in the
migration 001_initial_schema.sql) so PostgREST's resolution=merge-duplicates can
detect conflicts, then run the migration/update schema before relying on the
db.upsert in sync.py (references: "lessons" table, db.upsert,
resolution=merge-duplicates, constraint name lessons_brain_id_description_key).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cloud/app/routes/operator.py`:
- Around line 223-229: The current sort key for last_active in sort_keys lets
None values flip to the front when rows.sort(..., reverse=(order == "desc")) is
used; update the last_active key in the sort_keys dict to use a boolean that is
True when a value exists (e.g., r.last_active is not None) so the tuple becomes
(r.last_active is not None, r.last_active or "") ensuring actual dates always
sort before None whether reverse is True or False; adjust the existing comment
if needed and keep references to rows.sort, sort_keys, last_active, sort and
order.

In `@cloud/app/routes/users.py`:
- Around line 18-32: _hydrate_workspaces currently issues an N+1 by calling
db.select per workspace; change it to batch-fetch all workspaces in one query
and merge roles in-memory: extract workspace_ids from memberships (from the
existing role_by_ws), call db.select("workspaces", filters={"id":
workspace_ids}) once to get all rows, build a map of workspace_id -> workspace
row, then iterate role_by_ws to produce workspaces list by merging the role into
each mapped row (use function _hydrate_workspaces and the local variables
role_by_ws/workspace_ids to locate where to change).

---

Outside diff comments:
In `@cloud/app/routes/sync.py`:
- Around line 53-68: The upsert of lessons via db.upsert("lessons", ...) cannot
merge duplicates because the lessons table lacks a UNIQUE key on (brain_id,
description); add a UNIQUE constraint named e.g.
lessons_brain_id_description_key to the lessons table (or include it in the
migration 001_initial_schema.sql) so PostgREST's resolution=merge-duplicates can
detect conflicts, then run the migration/update schema before relying on the
db.upsert in sync.py (references: "lessons" table, db.upsert,
resolution=merge-duplicates, constraint name lessons_brain_id_description_key).
🪄 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

Run ID: 3d5b10ff-83b9-4a47-8cc0-f0d5bd621185

📥 Commits

Reviewing files that changed from the base of the PR and between ce1ad3c and 060ce6f.

📒 Files selected for processing (12)
  • cloud/app/routes/activity.py
  • cloud/app/routes/analytics.py
  • cloud/app/routes/brains.py
  • cloud/app/routes/corrections.py
  • cloud/app/routes/lessons.py
  • cloud/app/routes/meta_rules.py
  • cloud/app/routes/operator.py
  • cloud/app/routes/rule_patches.py
  • cloud/app/routes/sync.py
  • cloud/app/routes/team.py
  • cloud/app/routes/users.py
  • cloud/tests/test_new_routes.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (11)
cloud/app/routes/team.py (1)

78-79: Nice cleanup on timestamp and role normalization.

These changes remove small branches/state without changing the persisted shape: invite timestamps now share a single clock value, and role extraction is consistently normalized before write/update.

Also applies to: 109-119, 195-195

cloud/app/routes/corrections.py (1)

7-7: Brain scoping stays clear after the handler simplification.

The query is still anchored on the resolved brain id, and the route body is easier to follow without the duplicated setup code.

Also applies to: 19-19

cloud/app/routes/meta_rules.py (1)

7-7: This keeps the meta-rule listing path lean without changing its scope.

The endpoint still filters on the validated brain id, but the request/auth plumbing is no longer repeated inside the handler.

Also applies to: 19-19

cloud/app/routes/analytics.py (1)

21-38: Good cleanup in the analytics aggregation path.

The handler stays brain-scoped while pulling the three independent datasets together before computing aggregates, which trims avoidable latency from this endpoint.

cloud/app/routes/rule_patches.py (1)

19-19: The list and rollback flows still enforce the right brain boundary.

Both handlers stay anchored on the resolved brain id, and the rollback path still rechecks lesson ownership before writing anything.

Also applies to: 52-52

cloud/app/routes/lessons.py (1)

19-19: This keeps the lesson query path straightforward.

Scoping off the resolved brain id and making the in-process min_confidence filter explicit both improve readability without changing the endpoint’s behavior.

Also applies to: 42-44

cloud/app/routes/brains.py (2)

31-45: Good extraction of the repeated BrainDetail shaping.

Pulling the count/detail assembly into _brain_detail removes duplicate logic across the list, detail, and update paths and makes this surface easier to maintain.

Also applies to: 67-74, 79-95


98-103: The demo cleanup path is clearer after this consolidation.

Reusing the resolved brain id throughout the delete/clear flow and collapsing demo detection to a single predicate makes the behavior easier to audit.

Also applies to: 112-152

cloud/tests/test_new_routes.py (1)

8-20: Switching this fixture to the app-level override is the right test seam.

It exercises the same dependency entry point the routes use and avoids brittle per-module patching as the handlers get simplified.

cloud/app/routes/activity.py (1)

29-34: Dependency-injection refactor is clean and consistent.

Depends(get_brain_for_request) on Line 31 removes manual auth/brain plumbing while preserving route behavior.

cloud/app/routes/users.py (1)

35-40: Helper reuse improves maintainability.

Using _hydrate_workspaces in both profile read and update paths removes duplicated join logic and keeps response shaping consistent.

Also applies to: 73-78

Comment thread cloud/app/routes/operator.py Outdated
Comment thread cloud/app/routes/users.py Outdated
- operator.py: sort_keys["last_active"] now uses (r.last_active is not None,
  r.last_active or "") so actual dates always sort before None in both asc and
  desc, instead of flipping with the reverse= flag.
- users.py: _hydrate_workspaces now batch-fetches all workspaces in a single
  IN(...) query instead of N+1 selects.
- db.py: SupabaseClient.select gained list/tuple filter support that emits
  PostgREST in.(...) syntax (with empty-list short-circuit). Enables the
  workspace batch above and any future bulk lookups.
- migrations/005_lessons_unique_constraint.sql: NEW. Adds the missing UNIQUE
  constraint on lessons(brain_id, description) that /sync upserts depend on.
  Includes a safety pre-step that collapses any pre-existing duplicates by
  (brain_id, description), keeping the most recent row by updated_at, then
  confidence, then id.

Manual Supabase apply required for migration 005 alongside any other pending
migrations.

Co-Authored-By: Gradata <noreply@gradata.ai>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@Gradata

Gradata commented Apr 14, 2026

Copy link
Copy Markdown
Owner Author

Addressed all 3 CR actionables in 1bb7c1f:

  • operator.py sort: is not None ensures dates always rank before None in both asc/desc
  • users.py: N+1 → single IN(...) query via new db.select list-filter support
  • migration 005: UNIQUE constraint on lessons(brain_id, description) with safety duplicate-collapse pre-step

Cloud test suite green (20/20 operator+team). Manual Supabase apply required for 005. @coderabbitai full review

@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown

@Gradata Thanks for the detailed summary of the fixes in 1bb7c1f! Here's a quick acknowledgment of each:

  • operator.py sort — Using is not None as the sort key correctly places all None dates at the end regardless of asc/desc direction. ✅
  • users.py N+1 → IN(...) query — Collapsing the per-workspace fetches into a single db.select with a list filter is a solid improvement for scalability. ✅
  • Migration 005 — The duplicate-collapse pre-step before adding the UNIQUE(brain_id, description) constraint is the right safety pattern for applying to existing data. Good to flag it as requiring a manual Supabase apply. ✅

Triggering a full re-review of the PR now.

✅ Actions performed

Full review triggered.

@Gradata

Gradata commented Apr 14, 2026

Copy link
Copy Markdown
Owner Author

CR feedback re-audit (post 1bb7c1f):

  • operator.py sort_keys["last_active"] uses is not None — verified in code (line 228)
  • users.py _hydrate_workspaces batch-fetches via single IN(...) query — verified (lines 18-39)
  • migration 005_lessons_unique_constraint.sql present — verified

All 3 CR actionables (2 inline + 1 outside-diff) addressed in 1bb7c1f. 41 cloud tests (test_operator, test_new_routes, test_sync, test_team) pass. No new CR comments since the fix commit. reviewDecision=CHANGES_REQUESTED appears stale — CodeRabbit's own follow-up acknowledged the fixes.

Conflicts in cloud/app/db.py, routes/brains.py, routes/users.py.

Resolution strategy (preserve main's behavior additions, keep simplify
intent where non-conflicting):

- db.py: accepted main's explicit in_= parameter (required by activity.py,
  rule_patches.py, brains.py callers merged via #44). Kept simplify's
  _raise_db_error for uniform error handling.
- routes/brains.py: kept main's batched lessons+corrections IN-query for
  list_brains (perf) + main's new POST /brains endpoint. Preserved
  simplify's _brain_detail helper, Depends(get_brain_for_request)
  pattern, and _is_demo consolidation.
- routes/users.py: accepted main's version wholesale (adds email,
  _derive_plan, _primary_workspace_id scoped notification updates).
  Simplify's _hydrate_workspaces helper dropped — main's inline form
  carries the required new behavior and this PR is a refactor (no
  behavior changes).

No new behavior introduced by this merge commit.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@Gradata

Gradata commented Apr 14, 2026

Copy link
Copy Markdown
Owner Author

Rebased on main (merged origin/main, resolved 3 conflicts).

Files resolved:

  • cloud/app/db.py — accepted main's explicit in_= parameter (callers in activity.py, rule_patches.py, brains.py rely on it); kept simplify's _raise_db_error helper.
  • cloud/app/routes/brains.py — kept main's batched list_brains (IN-query perf) + new POST /brains endpoint; preserved simplify's _brain_detail helper, Depends(get_brain_for_request) pattern, and _is_demo consolidation for get/update/delete/clear_demo.
  • cloud/app/routes/users.py — accepted main's version wholesale to preserve new behavior (email resolution, _derive_plan, scoped notification updates). This PR is a refactor, so no behavior changes from our side.

Tests: pytest cloud/tests/ -q --ignore=cloud/tests/test_proof.py → 115 passed.

Note: cloud/tests/test_proof.py::test_export_script_handles_empty_run_dir fails on pristine origin/main — the test references cloud/scripts/export_ab_proof.py which was never committed in PR #44. Pre-existing bug unrelated to this rebase; recommend tracking separately.

@Gradata

Gradata commented Apr 14, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Gradata

Gradata commented Apr 15, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Gradata

Gradata commented Apr 15, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review — fixes pushed since last review, please re-verify

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown

@Gradata: Acknowledged — triggering a fresh review to re-verify all pushed fixes, including the operator.py sort key, the _hydrate_workspaces batch query, and the migration 005 constraint.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
cloud/app/routes/operator.py (1)

224-231: ⚠️ Potential issue | 🟠 Major

last_active sorting still puts None first when order="asc".

On Line 231, reverse flips tuple ordering. With Line 229 key (r.last_active is not None, r.last_active or ""), ascending order places None first, so the “None always at end” behavior is not met.

💡 Proposed fix
-    # Sort Nones consistently: actual dates rank before None in both asc and desc
-    # (True > False, so `is not None` ensures real dates always sort before None).
-    sort_keys = {
+    # Sort Nones consistently: keep None last for both asc and desc on last_active.
+    sort_keys = {
         "mrr": lambda r: r.mrr_usd,
         "active_users": lambda r: r.active_users,
-        "last_active": lambda r: (r.last_active is not None, r.last_active or ""),
     }
-    rows.sort(key=sort_keys[sort], reverse=(order == "desc"))
+    if sort == "last_active":
+        rows.sort(key=lambda r: r.last_active or "", reverse=(order == "desc"))
+        rows.sort(key=lambda r: r.last_active is None)  # stable pass keeps None at end
+    else:
+        rows.sort(key=sort_keys[sort], reverse=(order == "desc"))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cloud/app/routes/operator.py` around lines 224 - 231, The current tuple key
for "last_active" in sort_keys still yields None values first when
rows.sort(..., reverse=(order=="desc")) flips the tuple order; fix by handling
"last_active" as a special case: when sort == "last_active", first sort rows by
r.last_active with reverse=(order=="desc") (e.g., key=lambda r: r.last_active or
minimal-sentinel) then run a second stable sort to push None to the end using
rows.sort(key=lambda r: r.last_active is None), otherwise keep the existing
sort_keys usage; update references to sort_keys, last_active, rows.sort and
order accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cloud/migrations/005_lessons_unique_constraint.sql`:
- Around line 15-29: Wrap the deduplication and constraint-add steps in an
explicit transaction so the DELETE from lessons (using the ranked CTE) and the
ALTER TABLE ADD CONSTRAINT lessons_brain_id_description_key are atomic; begin a
transaction before the WITH ranked (...) ... DELETE FROM lessons ... and commit
only after the ALTER TABLE succeeds (rollback on error). Also adjust formatting
so the WHERE clause for the DELETE starts on its own line to satisfy SQLFluff
LT14.

---

Duplicate comments:
In `@cloud/app/routes/operator.py`:
- Around line 224-231: The current tuple key for "last_active" in sort_keys
still yields None values first when rows.sort(..., reverse=(order=="desc"))
flips the tuple order; fix by handling "last_active" as a special case: when
sort == "last_active", first sort rows by r.last_active with
reverse=(order=="desc") (e.g., key=lambda r: r.last_active or minimal-sentinel)
then run a second stable sort to push None to the end using rows.sort(key=lambda
r: r.last_active is None), otherwise keep the existing sort_keys usage; update
references to sort_keys, last_active, rows.sort and order accordingly.
🪄 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

Run ID: aaeab1d2-d14d-4ad8-988e-68ea06f79937

📥 Commits

Reviewing files that changed from the base of the PR and between 060ce6f and 8a098bc.

📒 Files selected for processing (7)
  • cloud/app/routes/activity.py
  • cloud/app/routes/brains.py
  • cloud/app/routes/operator.py
  • cloud/app/routes/rule_patches.py
  • cloud/app/routes/sync.py
  • cloud/app/routes/team.py
  • cloud/migrations/005_lessons_unique_constraint.sql
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages
🧰 Additional context used
🪛 SQLFluff (4.1.0)
cloud/migrations/005_lessons_unique_constraint.sql

[error] 25-25: The 'WHERE' keyword should always start a new line.

(LT14)

🔇 Additional comments (12)
cloud/app/routes/team.py (3)

79-80: LGTM! Clean simplification of max calculation.

The comprehension with max() is more idiomatic and readable than the previous imperative loop. The if sync_times else None correctly handles the empty case.


110-120: LGTM! Timestamp consolidation and role extraction look correct.

Using a single now value for both created_at and expires_at ensures consistency. The direct body.role.value access is safe since InviteRole is a str Enum per the PR summary.


196-196: LGTM! Consistent role extraction.

Matches the pattern established in create_invite.

cloud/app/routes/sync.py (1)

56-70: LGTM! Comment accurately reflects the new schema behavior.

The updated comment correctly documents that lessons are idempotent by (brain_id, description), which aligns with the UNIQUE constraint added in migration 005 and the db.upsert behavior using PostgREST's resolution=merge-duplicates.

cloud/app/routes/activity.py (1)

29-34: LGTM! Clean dependency injection refactor.

The brain_id path parameter is correctly resolved by FastAPI through get_brain_for_request dependency, which accepts brain_id as a parameter. The handler correctly uses brain["id"] for the database query.

cloud/app/routes/rule_patches.py (2)

17-22: LGTM! Dependency injection pattern applied correctly.

The brain_id is resolved via get_brain_for_request and brain["id"] is used appropriately for the lessons query.


48-52: LGTM! Rollback endpoint correctly uses injected brain.

The ownership verification on line 72 (lesson_rows[0].get("brain_id") != brain["id"]) ensures the patch belongs to the authenticated brain.

cloud/app/routes/brains.py (5)

173-176: LGTM! Clean refactor using dependency injection and helper.

The endpoint now uses Depends(get_brain_for_request) consistently with other routes and delegates to the shared _brain_detail helper.


179-197: LGTM! Update endpoint correctly merges changes before building detail.

The local brain dict is updated with the new values (line 195) before passing to _brain_detail, ensuring the response reflects the update.


200-206: LGTM! Delete endpoint refactored consistently.

Uses brain["id"] from the injected dependency for the soft-delete.


214-255: LGTM! Demo cleanup consolidation is clean.

The single _is_demo(value) predicate correctly handles both row data and brain metadata dicts, checking for isinstance(value, dict) before accessing is_demo.


36-50: The suggested db.count() optimization is not feasible.

The SupabaseClient class does not provide a count() method. The pattern of selecting only the id column and using len() is the standard approach throughout the codebase (used in public.py, billing.py, health.py, team.py, and elsewhere). The _brain_detail helper correctly follows this established pattern.

			> Likely an incorrect or invalid review comment.

Comment thread cloud/migrations/005_lessons_unique_constraint.sql
@Gradata

Gradata commented Apr 15, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review — CR Pro active, please re-verify.

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown

@Gradata: Acknowledged — triggering a fresh re-verification now. I'll review all changes including the post-rebase conflict resolutions in cloud/app/db.py, cloud/app/routes/brains.py, and cloud/app/routes/users.py, as well as the previously confirmed fixes (operator.py sort key, _hydrate_workspaces batch query, and migration 005 unique constraint).

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Gradata added 2 commits April 14, 2026 21:54
- cloud/migrations/005: wrap dedupe DELETE + ADD CONSTRAINT in
  BEGIN/COMMIT so a concurrent duplicate insert between steps can't
  leave the table deduped but un-constrained.
- operator customers list: sort last_active with a stable two-pass
  approach so None rows stay at the end in both asc and desc order
  (single tuple key flipped Nones to the top under reverse=True).

No behavior change to other sort keys. All 120 cloud tests pass
(test_proof skipped — pre-existing main-branch breakage fixed in
a separate PR, unrelated to this refactor).

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@Gradata

Gradata commented Apr 15, 2026

Copy link
Copy Markdown
Owner Author

Rebased + round-2 CR addressed

@Gradata

Gradata commented Apr 15, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@Gradata

Gradata commented Apr 15, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review — rebased on latest main, all prior fixes applied

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown

@Gradata: Acknowledged — triggering a fresh review now. I'll verify the rebase onto latest main, all prior fixes (operator.py sort key, _hydrate_workspaces batch IN query, migration 005 unique constraint), and the round-2 CR changes across the 12 changed files.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Gradata Gradata merged commit d8a5741 into main Apr 15, 2026
6 checks passed
@Gradata Gradata deleted the refactor/simplify-cloud-infra branch April 15, 2026 05:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant