feat(backend): track first organization onboarding in Bento - #2841
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds Bento first-organization recovery handling for user creation, email changes, deletion, and direct organization access. Adds queue delivery, authenticated trigger routes, deletion/API-key race protection, tests, and Bento setup guidance. ChangesFirst-organization recovery lifecycle
Estimated code review effort: 4 (Complex) | ~60 minutes Suggested labels: Sequence Diagram(s)sequenceDiagram
participant UserTrigger
participant BentoFirstOrg
participant PostgreSQL
participant Bento
UserTrigger->>BentoFirstOrg: process user lifecycle event
BentoFirstOrg->>PostgreSQL: query eligibility and organization access
PostgreSQL-->>BentoFirstOrg: return registration state
BentoFirstOrg->>Bento: update tags and emit lifecycle event
sequenceDiagram
participant role_bindings
participant Queue
participant on_user_org_access
participant BentoFirstOrg
participant Bento
role_bindings->>Queue: enqueue qualifying access change
Queue->>on_user_org_access: deliver role-binding webhook
on_user_org_access->>BentoFirstOrg: synchronize role-binding access
BentoFirstOrg->>Bento: remove awaiting tag and emit join event
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
Comment |
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
All reported issues were addressed across 13 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
Actionable comments posted: 13
🤖 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 `@docs/BENTO_EMAIL_PREFERENCES_SETUP.md`:
- Around line 75-90: Update the “First-Organization Recovery” documentation to
explicitly name user:joined_org as the exit or goal event emitted when active
direct organization access is established. Keep the existing
user:registered_without_org entry event and tag-removal behavior, and clarify
that operators should configure this event in the workflow.
In `@supabase/functions/_backend/triggers/on_user_create.ts`:
- Line 19: Update the on_user_create flow around syncBentoFirstOrgOnUserCreate
so Bento delivery failures are caught and logged without propagating an HTTP 500
or triggering queue_consumer retries. Keep this handling after core user
provisioning completes, and preserve the existing provisioning behavior.
In `@supabase/functions/_backend/triggers/on_user_org_access.ts`:
- Around line 6-27: The role-binding webhook currently performs ad hoc
table/type checks and does not validate the webhook ID format. Extend
triggerValidator to allow both INSERT and UPDATE events, apply it to
role_bindings in the app.post handler, then validate c.get('webhookBody').id as
a UUID before calling syncBentoFirstOrgOnRoleBindingWrite; retain separate UUID
validation because triggerValidator does not validate field types.
In `@supabase/functions/_backend/triggers/on_user_update.ts`:
- Around line 26-27: Update the email normalization in the user-update handler
to safely accept null or undefined record.email, and reuse the exported
normalizedEmail helper from utils/bento_first_org.ts for both newEmail and
oldEmail instead of duplicating trim/lowercase logic. Preserve the nullable
email values passed to syncUserPreferenceTags.
In `@supabase/functions/_backend/utils/bento_first_org.ts`:
- Around line 54-74: Update the cleanup calls in hasActiveDirectOrgAccess and
syncBentoFirstOrgOnUserCreate to use pgClient.release() after normal query
execution, replacing release(true). Only pass a truthy argument when a client is
unusable or must be destroyed, while preserving cleanup for both helper
invocations.
- Around line 15-25: Update the CurrentRoleBinding.granted_at type to match the
pg query result by using Date, or explicitly cast role_bindings.granted_at to a
string in SQL if downstream code requires string values; keep the selected data
and JSON serialization behavior consistent.
- Around line 94-106: Make the first-org event handling idempotent across queue
retries by adding a durable, atomic claim keyed by user.id for registration
events and role_binding_id for joined-org events, or by passing a
Bento-supported unique key to trackBentoEvent. Update the relevant handler flow
around setAwaitingFirstOrgTag and trackBentoEvent so the claim is recorded
before delivery and repeated retries skip already-claimed events while
preserving the existing tag behavior.
In `@supabase/migrations/20260803161022_on_user_org_access_queue.sql`:
- Around line 29-48: Update the WHEN clause of the on_user_org_access trigger to
compare OLD and NEW qualification states, so it executes only when a
role_bindings row transitions into the qualifying user/org/direct/non-expired
state. Preserve inserts as qualifying while preventing re-enqueueing for updates
that remain qualifying, including expiry extensions and org changes.
- Around line 50-57: Update the cron_tasks UPDATE statement in the migration to
capture its affected-row count and fail the migration when no row is updated.
Preserve the existing name, task_type, and target conditions, and ensure the
failure occurs before queue creation or message enqueueing proceeds.
In `@tests/bento-first-org-lifecycle.unit.test.ts`:
- Around line 251-269: Rename the test case around postUser to describe the
access-check query contract it actually verifies, since it does not seed or
return an expired invitation row. Keep the existing assertions for the generated
query text, parameters, lifecycle tag, and event count unchanged.
In `@tests/bento-first-org-route.unit.test.ts`:
- Around line 105-129: Extend the validation tests around requestPayload to
cover object payloads with a non-role_bindings table, an unsupported type, and a
missing record.id. Assert each returns status 400 with the corresponding
table_not_match, type_not_match, or missing-id validation error, and verify
syncBentoFirstOrgOnRoleBindingWriteMock is not called.
- Around line 131-135: Update the test “registers the same route in the Supabase
trigger router” to import the trigger router from
supabase/functions/triggers/index.ts and assert the /on_user_org_access route
through its registered behavior or route table rather than matching source text.
Remove the unused readFile import; if the router cannot be imported in this
environment, retain the source check and document that constraint in a comment.
In `@tests/user-preference-bento-tags.unit.test.ts`:
- Around line 64-72: Normalize each recorded argument from
syncBentoSubscriberTagsMock before asserting: handle both a single update object
and an array of updates, then iterate over the resulting update objects and
verify their segments and deleteSegments exclude SUPPRESSION_TAG. Remove the
unsafe single-object cast and follow the normalization pattern used in
bento-first-org-lifecycle.unit.test.ts.
🪄 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: c5297e5a-17f4-4214-8c7b-19871d850cab
📒 Files selected for processing (13)
cloudflare_workers/api/index.tsdocs/BENTO_EMAIL_PREFERENCES_SETUP.mdsupabase/functions/_backend/triggers/on_user_create.tssupabase/functions/_backend/triggers/on_user_org_access.tssupabase/functions/_backend/triggers/on_user_update.tssupabase/functions/_backend/utils/bento_first_org.tssupabase/functions/triggers/index.tssupabase/migrations/20260803161022_on_user_org_access_queue.sqlsupabase/tests/66_test_on_user_org_access_queue.sqltests/bento-first-org-lifecycle.unit.test.tstests/bento-first-org-route.unit.test.tstests/on-user-update-bento.unit.test.tstests/user-preference-bento-tags.unit.test.ts
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Cap-go/capacitor-updater(manual)
There was a problem hiding this comment.
All reported issues were addressed across 4 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 12 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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 `@supabase/functions/_backend/triggers/on_user_create.ts`:
- Around line 19-21: Rename prepareBentoFirstOrgOnUserCreate to a name
describing the broader user-provisioning eligibility decision, such as
shouldProvisionNewUser or prepareUserProvisioning, and update its declaration
and all call sites, including the shouldProvisionUser flow. Preserve the
existing gating of createApiKey and preference-tag synchronization.
In `@supabase/functions/_backend/triggers/on_user_delete.ts`:
- Around line 334-338: Update deleteUser and finishDeleteUserResponse so the
final status reflects both Bento cleanup and cleanupDeletedUserResources
outcomes. Make cleanupDeletedUserResources propagate RBAC, organization lookup,
and resource cleanup failures instead of returning silently, and pass that
result into finishDeleteUserResponse so failed Stripe/resource cleanup triggers
a retry while successful cleanup returns 200.
In `@supabase/functions/_backend/triggers/on_user_org_access.ts`:
- Line 3: Replace direct Hono construction in the trigger app with the shared
createHono factory, calling createHono('', version) and removing the Hono
import. Preserve the existing trigger routes and middleware behavior.
In `@supabase/functions/_backend/utils/supabase.ts`:
- Around line 797-803: Set a transaction-local lock_timeout immediately after
BEGIN in the transaction setup around pgClient and before any FOR UPDATE query,
using the existing pgClient.query flow. Keep the timeout scoped to this
transaction so competing delete_user() locks fail promptly without affecting
other pooled connections or transactions.
🪄 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: bdfae13f-7752-44d0-a7e7-f0cb963e88e9
📒 Files selected for processing (16)
docs/BENTO_EMAIL_PREFERENCES_SETUP.mdsupabase/functions/_backend/triggers/on_user_create.tssupabase/functions/_backend/triggers/on_user_delete.tssupabase/functions/_backend/triggers/on_user_org_access.tssupabase/functions/_backend/triggers/on_user_update.tssupabase/functions/_backend/utils/bento_first_org.tssupabase/functions/_backend/utils/hono.tssupabase/functions/_backend/utils/supabase.tssupabase/migrations/20260803161022_on_user_org_access_queue.sqlsupabase/tests/66_test_on_user_org_access_queue.sqltests/bento-first-org-lifecycle.unit.test.tstests/bento-first-org-route.unit.test.tstests/create-api-key-deletion-race.unit.test.tstests/on-user-delete-bento.unit.test.tstests/on-user-update-bento.unit.test.tstests/user-preference-bento-tags.unit.test.ts
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Cap-go/capacitor-updater(manual)
There was a problem hiding this comment.
All reported issues were addressed across 16 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
supabase/functions/_backend/utils/bento_first_org.ts (1)
132-148: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winKeep the previous export as a compatibility alias.
Line 132 replaces the exported
prepareBentoFirstOrgOnUserCreatesymbol. Existing importers will fail at module load. Keep a deprecated alias toprepareNewUserProvisioninguntil consumers migrate.Proposed compatibility alias
export async function prepareNewUserProvisioning( c: Context<MiddlewareKeyVariables>, user: Database['public']['Tables']['users']['Row'], ) { // ... } + +export const prepareBentoFirstOrgOnUserCreate = prepareNewUserProvisioningAs per coding guidelines, “Public API and plugin changes must remain backward compatible: do not remove or change existing fields, meanings, formats, or status codes.”
🤖 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 `@supabase/functions/_backend/utils/bento_first_org.ts` around lines 132 - 148, Preserve the existing exported prepareBentoFirstOrgOnUserCreate symbol as a deprecated compatibility alias for prepareNewUserProvisioning, while retaining the new function export and behavior. Ensure existing importers can continue resolving the previous name until they migrate.Source: Coding guidelines
🤖 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.
Outside diff comments:
In `@supabase/functions/_backend/utils/bento_first_org.ts`:
- Around line 132-148: Preserve the existing exported
prepareBentoFirstOrgOnUserCreate symbol as a deprecated compatibility alias for
prepareNewUserProvisioning, while retaining the new function export and
behavior. Ensure existing importers can continue resolving the previous name
until they migrate.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: f9d81cf4-6d06-4953-933a-e69ae0f56608
📒 Files selected for processing (9)
supabase/functions/_backend/triggers/on_user_create.tssupabase/functions/_backend/triggers/on_user_delete.tssupabase/functions/_backend/triggers/on_user_org_access.tssupabase/functions/_backend/utils/bento_first_org.tssupabase/functions/_backend/utils/stripe.tssupabase/functions/_backend/utils/supabase.tstests/bento-first-org-lifecycle.unit.test.tstests/create-api-key-deletion-race.unit.test.tstests/on-user-delete-bento.unit.test.ts
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Cap-go/capacitor-updater(manual)
There was a problem hiding this comment.
All reported issues were addressed across 9 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 5 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
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 `@supabase/functions/_backend/utils/bento_first_org.ts`:
- Around line 163-186: Update runBentoMutationWithFirstOrgReconciliation to
preserve the original mutate failure when
reconcileFirstOrgStateAfterBentoMutation also throws. Ensure reconciliation
errors do not replace mutationError; retain the existing behavior of rethrowing
the mutation error after successful reconciliation while exposing or chaining
both failures when reconciliation fails.
In `@tests/on-user-delete-bento.unit.test.ts`:
- Around line 220-281: The hanging-operation tests should derive their timer
advancement from the production timeout instead of using the literal 2_001.
Export BENTO_DELETED_USER_OPERATION_TIMEOUT_MS from bento_first_org.ts, import
it into these tests, and replace both advanceTimersByTimeAsync calls with
BENTO_DELETED_USER_OPERATION_TIMEOUT_MS + 1.
🪄 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: 7d7e9307-722e-4e91-b5ff-d303409d2e62
📒 Files selected for processing (6)
docs/BENTO_EMAIL_PREFERENCES_SETUP.mdsupabase/functions/_backend/triggers/on_user_delete.tssupabase/functions/_backend/utils/bento.tssupabase/functions/_backend/utils/bento_first_org.tstests/bento-first-org-lifecycle.unit.test.tstests/on-user-delete-bento.unit.test.ts
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Cap-go/capacitor-updater(manual)
|



Summary
onboarding:awaiting_first_organd emituser:registered_without_orguser:joined_orgSafety and rollout
onboarding:awaiting_first_organd reject bothonboarding:first_org_recovery_suppressedandonboarding_disabled.Verification
TZ=UTC bun test:unit— 1,323 passedbun lintbun lint:backendbun typecheck:backendbash scripts/check-supabase-migration-order.shbun run supabase:db:resetbun scripts/supabase-worktree.ts test db— 816 passedbun scripts/supabase-worktree.ts db lint -s public --fail-on warningwrangler deploy --dry-run --env=prod