Skip to content

fluent#2

Merged
Connorbelez merged 2 commits intomainfrom
03-11-fluent
Mar 12, 2026
Merged

fluent#2
Connorbelez merged 2 commits intomainfrom
03-11-fluent

Conversation

@Connorbelez
Copy link
Copy Markdown
Owner

@Connorbelez Connorbelez commented Mar 12, 2026

fluent

Added convex fluent Demo + fixed e2e RBAC auth

Summary by Sourcery

Add a fluent-convex demo experience with supporting backend schema and auth improvements, and wire up dedicated e2e authentication/RBAC testing and CI.

New Features:

  • Introduce a fluent-convex demo route and backend module showcasing middleware, validation, RBAC, callables, and custom plugins.
  • Add Convex schema tables for fluent-convex demo widgets and widget-user relationships.
  • Expose new routes for the fluent demo and an internal e2e organization switch helper.

Bug Fixes:

  • Harden auth event handling and middleware to upsert missing users and backfill related WorkOS data, resolving race conditions where user.updated arrives before user.created.
  • Ensure WorkOS sync also persists the current user and re-emits user.updated events so component state stays consistent.
  • Fix RBAC e2e auth flows by introducing deterministic multi-role login and org switching.

Enhancements:

  • Add reusable Convex fluent builder utilities for auth, RBAC, logging, timing, and Zod integration.
  • Improve WorkOS demo sync UI to surface user sync counts and clarify status messaging.
  • Suppress noisy TanStack Start SSR hydration warnings in the root document for cleaner logs.
  • Document architectural patterns and event-ordering lessons for future agents.

Build:

  • Load .env.local into Playwright via dotenv and flag VITE_E2E in the e2e dev script.

CI:

  • Add a GitHub Actions workflow to run Playwright-based E2E tests against the deployed Convex backend.

Documentation:

  • Extend AGENTS.md and AGENT_LESSONS.md with guidance on patterns, dependency injection, and webhook event ordering pitfalls.

Tests:

  • Introduce Playwright helpers, setup flows, and e2e suites for auth, protected routes, and RBAC scenarios using stored authentication state.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Fluent Convex demo page showcasing builder APIs, middleware, validation, and role-based access control examples.
    • Introduced organization switching capability for testing multi-org workflows.
  • Tests

    • Added comprehensive end-to-end test suite covering authentication flows, role-based access, protected routes, and sign-out functionality.
  • Documentation

    • Updated development guides with code quality patterns and webhook event handling best practices.
  • Chores

    • Enhanced authentication webhook handling and data synchronization processes.
    • Added environment configuration for testing workflows.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Mar 12, 2026

Reviewer's Guide

Implements a comprehensive fluent-convex demo (backend + frontend) showcasing middleware, validation, RBAC, callables, and custom plugins, while hardening WorkOS auth/RBAC integration (idempotent user syncing, backfill of org/roles), adding E2E Playwright tests and CI workflow for WorkOS-backed auth/RBAC, and various DX/UX improvements.

Sequence diagram for WorkOS user.updated handling with backfill and upserts

sequenceDiagram
  participant WorkOS
  participant AuthKit as AuthKit_WorkOS
  participant ConvexAuth as Convex_authKitEvent
  participant DB as Convex_DB
  participant Scheduler
  participant SyncAction as syncUserRelatedData
  participant WorkOSUserMgmt as WorkOS_userManagement
  participant WorkOSOrgs as WorkOS_organizations
  participant UpsertMutation as upsertRelatedData

  WorkOS->>AuthKit: emit user.updated
  AuthKit->>ConvexAuth: dispatch authKitEvent user.updated

  ConvexAuth->>DB: query users by authId
  alt user not found
    ConvexAuth->>DB: insert users (from event data)
    ConvexAuth->>Scheduler: runAfter(0, syncUserRelatedData, { userId })
    ConvexAuth-->>AuthKit: return (upsert scheduled)
  else user exists
    ConvexAuth->>DB: patch users with latest profile
    ConvexAuth-->>AuthKit: return
  end

  Note over Scheduler,SyncAction: Backfill path when user was missing

  Scheduler->>SyncAction: invoke with userId
  SyncAction->>WorkOSUserMgmt: listOrganizationMemberships({ userId })
  WorkOSUserMgmt-->>SyncAction: membershipsResult

  SyncAction->>SyncAction: collect orgIds, memberships, roleSlugs

  loop for each organizationId
    SyncAction->>WorkOSOrgs: getOrganization(orgId)
    WorkOSOrgs-->>SyncAction: organization
    SyncAction->>SyncAction: push org data, denormalize org name on memberships
    SyncAction->>WorkOSOrgs: listOrganizationRoles({ organizationId })
    WorkOSOrgs-->>SyncAction: rolesResult
    SyncAction->>SyncAction: add unique roles(slug, permissions)
  end

  SyncAction->>UpsertMutation: runMutation(upsertRelatedData, { orgs, memberships, roles })
  UpsertMutation->>DB: upsert organizations
  UpsertMutation->>DB: upsert memberships
  UpsertMutation->>DB: upsert roles
  UpsertMutation-->>SyncAction: done
  SyncAction-->>Scheduler: log summary and finish
Loading

Sequence diagram for E2E WorkOS login and org switching via /e2e/switch-org

sequenceDiagram
  actor Tester as Playwright_test
  participant Page as Browser_page
  participant App as App_frontend
  participant WorkOSAuth as WorkOS_AuthKit_UI
  participant WorkOS
  participant SwitchRoute as E2e_switch_org_route

  Tester->>Page: goto(/sign-in)
  Page->>App: GET /sign-in
  App-->>Page: redirect to WorkOS hosted login
  Page->>WorkOSAuth: load WorkOS AuthKit page

  Tester->>WorkOSAuth: fill email, password
  Tester->>WorkOSAuth: click Continue / Sign in
  WorkOSAuth->>WorkOS: authenticate user
  WorkOS-->>WorkOSAuth: auth success + org choices

  alt user has multiple orgs
    Tester->>WorkOSAuth: click first org button
  end

  WorkOSAuth-->>Page: redirect back to app callback
  Page->>App: GET /callback
  App-->>Page: establish session and redirect to /

  Note over Tester,Page: Now authenticated as base user

  Tester->>Page: goto(/e2e/switch-org?orgId=targetOrg)
  Page->>SwitchRoute: loader with search.orgId
  SwitchRoute->>SwitchRoute: check import.meta.env.VITE_E2E
  alt VITE_E2E not set
    SwitchRoute-->>Page: redirect to /
  else E2E mode enabled
    SwitchRoute->>SwitchRoute: validate orgId
    SwitchRoute->>WorkOSAuth: switchToOrganization({ organizationId })
    WorkOSAuth-->>SwitchRoute: session updated to target org
    SwitchRoute-->>Page: redirect to /
  end

  Note over Tester,App: Subsequent requests run in chosen org context (admin or member)
Loading

ER diagram for new fluent-convex demo tables and their relationships

erDiagram
  demo_fluent_widgets {
    string _id
    string name
    string createdBy
    number createdAt
  }

  demo_fluent_widget_users {
    string _id
    string widgetId
    string userId
    string role
  }

  users {
    string _id
    string authId
    string email
    string firstName
    string lastName
  }

  demo_fluent_widgets ||--o{ demo_fluent_widget_users : has
  users ||--o{ demo_fluent_widget_users : referenced_by_userId
Loading

Class diagram for fluent Convex builder, middleware, and reusable chains

classDiagram
  class ConvexBuilder {
  }

  class ConvexBuilderWithFunctionKind {
  }

  class TimedBuilder {
    +withTiming(operationName)
  }

  class FluentModule {
    +convex
    +authMiddleware
    +requireAdmin(permission)
    +requirePermission(permission)
    +withLogging(operationName)
    +authedQuery
    +authedMutation
    +adminMutation
  }

  class AuthMiddlewareContext {
    +auth
    +db
    +user
    +identity
    +scheduler
  }

  class RequireAdminContext {
    +db
    +user
    +isAdmin
  }

  class RequirePermissionContext {
    +db
    +user
    +permission
  }

  class UsersTableDoc {
    +authId
    +email
    +firstName
    +lastName
  }

  class OrganizationMembershipsDoc {
    +userWorkosId
    +roleSlug
    +roleSlugs
  }

  class RolesDoc {
    +slug
    +permissions
  }

  FluentModule --> ConvexBuilder : uses as base builder
  TimedBuilder --> ConvexBuilderWithFunctionKind : extends
  FluentModule --> TimedBuilder : plugin type

  AuthMiddlewareContext --> UsersTableDoc : user
  RequireAdminContext --> OrganizationMembershipsDoc : reads
  RequirePermissionContext --> OrganizationMembershipsDoc : reads
  RequirePermissionContext --> RolesDoc : reads
Loading

Class diagram for fluentConvex demo endpoints and demo_fluent tables

classDiagram
  class DemoFluentWidgetsTable {
    +_id
    +name
    +createdBy
    +createdAt
  }

  class DemoFluentWidgetUsersTable {
    +_id
    +widgetId
    +userId
    +role
  }

  class FluentConvexDemoModule {
    +listWidgets(input)
    +createWidget(input)
    +deleteWidget(input)
    +getMyProfile(input)
    +createWidgetLogged(input)
    +validateProperty(input)
    +validateObject(input)
    +validateZod(input)
    +addPositiveWidget(input)
    +resetWidgets(input)
    +addWidgetUser(input)
    +listWidgetUsers(input)
    +removeWidgetUser(input)
    +widgetCount(input)
    +widgetSummary(input)
    +widgetCountProtected(input)
    +timedWidgetList(input)
    +seedWidgets(input)
  }

  class WithZodPlugin {
    +input(zodSchema)
    +returns(zodSchema)
  }

  FluentConvexDemoModule --> DemoFluentWidgetsTable : reads_writes
  FluentConvexDemoModule --> DemoFluentWidgetUsersTable : reads_writes
  FluentConvexDemoModule ..> WithZodPlugin : extends for Zod validation

  DemoFluentWidgetsTable "1" --> "*" DemoFluentWidgetUsersTable : widget_has_users
Loading

File-Level Changes

Change Details Files
Make WorkOS user sync and auth handling idempotent and resilient to out-of-order events, and expose a backfill path for orgs/memberships/roles.
  • Change auth user.updated handler to upsert missing users instead of warning and returning, then schedule a syncUserRelatedData backfill.
  • Add internal upsertRelatedData mutation to upsert organizations, memberships, and roles in bulk from WorkOS data.
  • Add syncUserRelatedData internal action that fetches a user’s org memberships, orgs, and roles from WorkOS APIs and writes them via upsertRelatedData.
convex/auth.ts
Ensure the WorkOS demo sync includes the current user and re-emits a user.updated event so components see fresh user state, and report updated sync stats in the UI.
  • Add upsertUserFromApi internal mutation to upsert the current user into the users table during sync.
  • Update syncAllFromWorkosApi to fetch the current WorkOS user, upsert it, bump userCount, trigger a WorkOS user.update, and enqueue a webhook so AuthKit components refresh.
  • Include userCount in the syncAllFromWorkosApi return payload and render it in the OrganizationsTab sync summary text.
convex/demo/workosAuth.ts
src/routes/demo/workos.tsx
Introduce reusable fluent-convex builder utilities (auth middleware, RBAC helpers, logging middleware, TimedBuilder plugin, and authed/admin chains) and a full demo backend module using them.
  • Create convex/fluent.ts with a shared convex builder, authMiddleware that enriches context and auto-creates missing users plus backfill scheduling, role-based requireAdmin and permission-based requirePermission middlewares, a withLogging onion middleware, TimedBuilder plugin, and authedQuery/authedMutation/adminMutation chains.
  • Add convex/demo/fluentConvex.ts implementing demo endpoints for listing/creating/deleting widgets, auth/logging examples, three validation styles (property/object/Zod), Zod refinements, RBAC-protected mutations/queries, callable sharing examples, timing via TimedBuilder, and seedWidgets helper.
  • Extend Convex schema with demo_fluent_widgets and demo_fluent_widget_users tables, including an index for widget-user lookup.
convex/fluent.ts
convex/demo/fluentConvex.ts
convex/schema.ts
convex/_generated/api.d.ts
Add a fluent-convex demo frontend page wiring all backend examples (builder basics, middleware, validation modes, RBAC flows, callables, plugins) into an interactive UI.
  • Create /demo/convex-fluent route component with multiple cards: builder basics CRUD, auth/logging middleware demo, validation tabs (property/object/Zod/Zod refinements), admin-only reset, permission-gated widget-user management, callable-based stats, and TimedBuilder-based list.
  • Wire the new demo route into the router (routeTree.gen), header navigation, and disable SSR for the page for a smoother client-only demo experience.
src/routes/demo/convex-fluent.tsx
src/routeTree.gen.ts
src/components/header.tsx
convex/_generated/api.d.ts
Add E2E-only org-switch route and comprehensive Playwright-based auth/RBAC test harness, including environment loading and CI workflow.
  • Add /e2e/switch-org route that uses WorkOS switchToOrganization and is gated behind VITE_E2E, for test-only org context switching.
  • Configure Playwright to load .env.local via dotenv, define multiple projects (setup, public, authenticated, admin, member) with appropriate testDirs and storageState, and add VITE_E2E flag to dev:e2e script.
  • Add Playwright helper for WorkOS login, an auth.setup.ts to capture storage state for user/admin/member, and tests for login/logout, public routing, WorkOS redirects, and RBAC behavior for admin and member sessions.
  • Introduce a GitHub Actions e2e workflow that runs Playwright tests with WorkOS and test account secrets from repo configuration.
src/routes/e2e/switch-org.tsx
playwright.config.ts
package.json
.github/workflows/e2e.yml
e2e/helpers/workos-login.ts
e2e/auth.setup.ts
e2e/auth/login.spec.ts
e2e/auth/protected-routes.spec.ts
e2e/rbac/admin.spec.ts
e2e/rbac/member.spec.ts
Improve developer/UX ergonomics around console noise and engineering documentation.
  • Inject a root-level script that suppresses a known TanStack Start SSR hydration warning to keep console output clean in development.
  • Document additional engineering patterns and lessons (DI, strategy, IoC, DRY/YAGNI/KISS, unordered WorkOS events and upsert semantics) in AGENTS and AGENT_LESSONS guides.
src/routes/__root.tsx
AGENTS.md
AGENT_LESSONS.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 12, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f6d9f276-9179-4cd8-bbe3-7836c664e796

📥 Commits

Reviewing files that changed from the base of the PR and between d21da47 and 1fcb291.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • convex/_generated/api.d.ts is excluded by !**/_generated/**
📒 Files selected for processing (24)
  • .env.local.example
  • .github/workflows/e2e.yml
  • .gitignore
  • AGENTS.md
  • AGENT_LESSONS.md
  • convex/auth.ts
  • convex/demo/fluentConvex.ts
  • convex/demo/workosAuth.ts
  • convex/fluent.ts
  • convex/schema.ts
  • e2e/auth.setup.ts
  • e2e/auth/login.spec.ts
  • e2e/auth/protected-routes.spec.ts
  • e2e/helpers/workos-login.ts
  • e2e/rbac/admin.spec.ts
  • e2e/rbac/member.spec.ts
  • package.json
  • playwright.config.ts
  • src/components/header.tsx
  • src/routeTree.gen.ts
  • src/routes/__root.tsx
  • src/routes/demo/convex-fluent.tsx
  • src/routes/demo/workos.tsx
  • src/routes/e2e/switch-org.tsx

📝 Walkthrough

Walkthrough

This pull request introduces end-to-end testing infrastructure with Playwright, establishes a Fluent Convex API framework featuring RBAC middleware and authentication utilities, adds comprehensive demo implementations, and enhances backend authentication logic with user data synchronization from WorkOS. Documentation of architectural patterns and lessons learned is also added.

Changes

Cohort / File(s) Summary
E2E Testing Framework
.github/workflows/e2e.yml, playwright.config.ts, e2e/auth.setup.ts, e2e/helpers/workos-login.ts
GitHub Actions workflow for E2E test execution, Playwright configuration with setup projects and storage state, authentication setup hooks that sign in users with different roles via WorkOS, and WorkOS login helper automating the AuthKit flow with email/password/org selection.
E2E Test Specifications
e2e/auth/login.spec.ts, e2e/auth/protected-routes.spec.ts, e2e/rbac/admin.spec.ts, e2e/rbac/member.spec.ts
Test suites for login verification, session clearing, public route access, protected route redirects, and RBAC validation (admin/member org context and role badges).
Fluent Convex Framework
convex/fluent.ts
New Convex builder with middleware system: authMiddleware enriching context with user identity and auto-creating users, requireAdmin and requirePermission(permission) RBAC middleware, withLogging(operationName) for operation logging, TimedBuilder plugin for operation timing, and pre-configured chains (authedQuery, authedMutation, adminMutation).
Demo & Example Implementations
convex/demo/fluentConvex.ts, src/routes/demo/convex-fluent.tsx
Comprehensive demo of Fluent Convex APIs showcasing builder basics, middleware effects, validation approaches (property/object/Zod), RBAC admin/permission checks, callables, custom plugins, and seed data with multi-section UI cards and error handling.
Authentication Backend
convex/auth.ts
Enhanced webhook handlers with console logging, new upsertRelatedData mutation for batch upserting orgs/memberships/roles, and syncUserRelatedData action that fetches WorkOS data and backfills related organization/membership/role information when user.updated arrives before user.created.
Demo WorkOS Integration
convex/demo/workosAuth.ts
New upsertUserFromApi mutation for upserting users from WorkOS API, modified syncAllFromWorkosApi to upsert users and touch WorkOS for event generation, now returning userCount alongside existing counts.
Configuration & Schema
.env.local.example, .gitignore, convex/schema.ts, package.json
E2E test environment variables (VITE\_E2E, TEST\_ACCOUNT\_EMAIL, etc.), .auth/ directory in gitignore, demo\_fluent\_widgets and demo\_fluent\_widget\_users tables with relationships, dev:e2e script prefixed with VITE\_E2E=true, dotenv dependency.
Frontend Routing & UI
src/routeTree.gen.ts, src/routes/__root.tsx, src/routes/e2e/switch-org.tsx, src/routes/demo/workos.tsx, src/components/header.tsx
New routes for /e2e/switch-org (org switching for E2E tests) and /demo/convex-fluent (demo page), hydration warning suppression in root document, updated WorkOS sync display to include userCount, and demo link in header.
Documentation
AGENTS.md, AGENT_LESSONS.md
Code quality guidelines emphasizing loose coupling, dependency injection, and design patterns (DRY, YAGNI, KISS, Strategy, IoC); lesson documenting WorkOS webhook ordering issues and upsert-based fix.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant App
    participant WorkOS
    participant Playwright

    Playwright->>Browser: Navigate to /sign-in
    Browser->>App: Request /sign-in
    App->>Browser: Redirect to WorkOS AuthKit
    Browser->>WorkOS: POST email
    WorkOS->>Browser: Show password prompt
    Browser->>WorkOS: POST password
    WorkOS->>Browser: Check org membership
    alt Multi-org user
        Browser->>WorkOS: Show org picker
        Browser->>WorkOS: Select organization
    end
    WorkOS->>Browser: Redirect to /callback
    Browser->>App: Handle callback with auth token
    App->>Browser: Redirect to authenticated page
    Browser->>Playwright: Verify logged-in state
Loading
sequenceDiagram
    participant WorkOS as WorkOS Webhook
    participant Convex as Convex Backend
    participant Database as Database
    participant Action as syncUserRelatedData

    WorkOS->>Convex: user.updated event<br/>(before user.created)
    Convex->>Database: Check if user exists
    alt User not found
        Database->>Convex: User missing
        Convex->>Database: Create user from event
        Convex->>Action: Schedule syncUserRelatedData
    end
    Action->>WorkOS: Fetch user org memberships
    Action->>WorkOS: Fetch org details & roles
    Action->>Convex: Aggregate data
    Convex->>Database: Upsert orgs, memberships, roles<br/>(batch mutation)
    Database->>Convex: Success
    Convex->>Action: Log completion
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Hops through tests with Fluent delight,
RBAC guards each function right,
WorkOS syncs both day and night,
E2E flows now shine so bright!
Our schema grows, permissions tight— 🎉

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 03-11-fluent
📝 Coding Plan for PR comments
  • Generate coding plan

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Owner Author

Connorbelez commented Mar 12, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

@Connorbelez Connorbelez marked this pull request as ready for review March 12, 2026 15:52
Copilot AI review requested due to automatic review settings March 12, 2026 15:52
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • In convex/fluent.ts, authMiddleware downcasts the context to Record<string, unknown> to access scheduler; consider extending the $context typing to include scheduler instead so you can avoid the unsafe cast and keep the middleware fully type-safe.
  • Several backend paths now log to console (auth.ts user events, syncUserRelatedData, withLogging, TimedBuilder), which could get noisy in production; you might want to gate these behind a debug flag or central logger so you can turn them down without changing code.
  • The requirePermission middleware in convex/fluent.ts performs one roles query per role slug; if users can accumulate many roles, consider fetching all needed roles in a single query (e.g. by slug set) to reduce round trips.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `convex/fluent.ts`, `authMiddleware` downcasts the context to `Record<string, unknown>` to access `scheduler`; consider extending the `$context` typing to include `scheduler` instead so you can avoid the unsafe cast and keep the middleware fully type-safe.
- Several backend paths now log to `console` (`auth.ts` user events, `syncUserRelatedData`, `withLogging`, `TimedBuilder`), which could get noisy in production; you might want to gate these behind a debug flag or central logger so you can turn them down without changing code.
- The `requirePermission` middleware in `convex/fluent.ts` performs one `roles` query per role slug; if users can accumulate many roles, consider fetching all needed roles in a single query (e.g. by slug set) to reduce round trips.

## Individual Comments

### Comment 1
<location path="convex/demo/fluentConvex.ts" line_range="120" />
<code_context>
+	export const validateZod = convex
</code_context>
<issue_to_address>
**issue (bug_risk):** Zod schema uses `z.email()` which doesn’t exist and will throw at runtime.

In `validateZod` you import `z` from `zod/v4` and call `z.email()` in the `.input` schema. Zod only exposes `.email()` on string schemas, so this will throw at runtime. Update the field to something like:
```ts
email: z.string().email(),
```
or another appropriate string schema for email.
</issue_to_address>

### Comment 2
<location path="AGENTS.md" line_range="30" />
<code_context>
+- DRY: Don't Repeat Yourself. If you find yourself writing the same code more than once, consider abstracting it into a reusable function or module.
+- YAGNI: You Aren't Gonna Need It. Don't add functionality until it's necessary
+- KISS: Keep It Simple Stupid. Avoid unnecessary complexity in your code. If a simpler solution exists, use it.
+- Dependancy Injection: Instead of hardcoding dependencies, inject them into your functions or classes. This makes your code more flexible and easier to test.
+- Strategy Pattern: Define a family of algorithms, encapsulate each one, and make them interchangeable. This allows you to select an algorithm at runtime without changing the client code. 
+- Inversion of Control: Instead of calling functions directly, use callbacks, events, or other mechanisms to allow the flow of control to be determined by the runtime environment. This promotes loose coupling and flexibility in your code.
</code_context>
<issue_to_address>
**issue (typo):** Fix spelling of "Dependancy" to "Dependency".

Typo: change "Dependancy Injection" to "Dependency Injection" to use the correct term.

```suggestion
- Dependency Injection: Instead of hardcoding dependencies, inject them into your functions or classes. This makes your code more flexible and easier to test.
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

.query()
.extend(WithZod)
.input(
z.object({
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): Zod schema uses z.email() which doesn’t exist and will throw at runtime.

In validateZod you import z from zod/v4 and call z.email() in the .input schema. Zod only exposes .email() on string schemas, so this will throw at runtime. Update the field to something like:

email: z.string().email(),

or another appropriate string schema for email.

Comment thread AGENTS.md
- DRY: Don't Repeat Yourself. If you find yourself writing the same code more than once, consider abstracting it into a reusable function or module.
- YAGNI: You Aren't Gonna Need It. Don't add functionality until it's necessary
- KISS: Keep It Simple Stupid. Avoid unnecessary complexity in your code. If a simpler solution exists, use it.
- Dependancy Injection: Instead of hardcoding dependencies, inject them into your functions or classes. This makes your code more flexible and easier to test.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (typo): Fix spelling of "Dependancy" to "Dependency".

Typo: change "Dependancy Injection" to "Dependency Injection" to use the correct term.

Suggested change
- Dependancy Injection: Instead of hardcoding dependencies, inject them into your functions or classes. This makes your code more flexible and easier to test.
- Dependency Injection: Instead of hardcoding dependencies, inject them into your functions or classes. This makes your code more flexible and easier to test.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a “fluent-convex” demo module + UI and introduces Playwright E2E coverage for WorkOS auth/RBAC flows, along with WorkOS user-sync hardening to reduce auth race conditions.

Changes:

  • Added a new Convex fluent builder demo (backend functions, schema tables, and a frontend demo page).
  • Hardened WorkOS auth syncing/backfill logic and added an E2E-only org-switch route to support RBAC tests.
  • Introduced Playwright E2E test projects + GitHub Actions workflow to run them in CI.

Reviewed changes

Copilot reviewed 23 out of 26 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/routes/e2e/switch-org.tsx Adds an E2E-only route to switch WorkOS organization context for tests.
src/routes/demo/workos.tsx Displays additional sync result info (user count).
src/routes/demo/convex-fluent.tsx New frontend demo page showcasing fluent-convex patterns.
src/routes/__root.tsx Injects a script intended to suppress a known hydration warning.
src/routeTree.gen.ts Registers the new demo route + E2E switch-org route.
src/components/header.tsx Adds navigation entry for the fluent-convex demo.
playwright.config.ts Loads env + expands Playwright projects (setup/authenticated/admin/member).
package.json Enables VITE_E2E for the dev:e2e server; adds dotenv devDependency.
e2e/rbac/member.spec.ts New member RBAC E2E assertions.
e2e/rbac/admin.spec.ts New admin RBAC E2E assertions.
e2e/helpers/workos-login.ts Adds WorkOS AuthKit hosted-login automation helper.
e2e/auth/protected-routes.spec.ts Adds public-route + sign-in redirect E2E checks.
e2e/auth/login.spec.ts Adds authenticated demo-page + sign-out E2E checks.
e2e/auth.setup.ts Adds auth/session bootstrap and role/org storageState generation.
convex/schema.ts Adds demo tables for fluent widgets + widget-user roles.
convex/fluent.ts Introduces fluent builder utilities: auth middleware, RBAC helpers, logging, plugin.
convex/demo/workosAuth.ts Adds user upsert + sync tweaks to keep user state fresh/consistent.
convex/demo/fluentConvex.ts Implements the fluent-convex demo queries/mutations/callables/plugins.
convex/auth.ts Makes user.updated handling idempotent and adds related-data backfill action/mutation.
convex/_generated/api.d.ts Updates generated API types for new modules.
bun.lock Locks new dependency versions (dotenv).
AGENT_LESSONS.md Documents lesson about unordered WorkOS events and upsert-based handling.
AGENTS.md Adds additional engineering-pattern guidance.
.gitignore Ignores .auth/ storageState output from Playwright.
.github/workflows/e2e.yml Adds CI job to run Playwright E2E tests with WorkOS secrets.
.env.local.example Documents required E2E environment variables.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread convex/auth.ts
Comment on lines +214 to +219
await ctx.db.insert("users", {
authId: event.data.id,
email: event.data.email,
firstName: `${event.data.firstName}`,
lastName: `${event.data.lastName}`,
});
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

Using template literals for firstName/lastName here will stringify undefined/null into the literal strings "undefined"/"null" if WorkOS omits those fields in the event payload. Prefer event.data.firstName ?? "" (and same for lastName) or leave existing values unchanged when fields are absent.

Copilot uses AI. Check for mistakes.
Comment on lines +488 to +499
const [selectedWidgetId, setSelectedWidgetId] = useState<string>("");
const [userId, setUserId] = useState("");
const [role, setRole] = useState("viewer");
const [result, setResult] = useState<{
success?: string;
error?: string;
}>({});

const widgetUsers = useQuery(
api.demo.fluentConvex.listWidgetUsers,
selectedWidgetId ? { widgetId: selectedWidgetId as never } : "skip"
);
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

selectedWidgetId as never bypasses Convex's generated ID types and makes it easier for invalid IDs to slip through at compile time. Prefer typing selectedWidgetId as Id<"demo_fluent_widgets"> | "" (and similarly for widget-user IDs) so useQuery/mutations accept the value without unsafe casts.

Copilot uses AI. Check for mistakes.
Comment on lines +523 to +526
setResult({});
try {
await removeWidgetUser({ id: id as never });
setResult({ success: "User removed" });
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

removeWidgetUser({ id: id as never }) is also bypassing Convex's generated ID typing. Consider typing the id parameter as Id<"demo_fluent_widget_users"> (and/or lifting the ID type from the query result) so the mutation call doesn't need a cast.

Copilot uses AI. Check for mistakes.
Comment thread AGENTS.md
- DRY: Don't Repeat Yourself. If you find yourself writing the same code more than once, consider abstracting it into a reusable function or module.
- YAGNI: You Aren't Gonna Need It. Don't add functionality until it's necessary
- KISS: Keep It Simple Stupid. Avoid unnecessary complexity in your code. If a simpler solution exists, use it.
- Dependancy Injection: Instead of hardcoding dependencies, inject them into your functions or classes. This makes your code more flexible and easier to test.
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

Spelling: "Dependancy Injection" should be "Dependency Injection".

Suggested change
- Dependancy Injection: Instead of hardcoding dependencies, inject them into your functions or classes. This makes your code more flexible and easier to test.
- Dependency Injection: Instead of hardcoding dependencies, inject them into your functions or classes. This makes your code more flexible and easier to test.

Copilot uses AI. Check for mistakes.
Comment thread playwright.config.ts
Comment on lines +4 to +5

dotenv.config({ path: path.resolve(import.meta.dirname, ".env.local") });
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

dotenv.config({ path: path.resolve(import.meta.dirname, ...) }) will crash when Playwright runs under Node ESM because import.meta.dirname is not a standard Node field (it will be undefined). Use fileURLToPath(new URL('.', import.meta.url)) (or similar) to derive the config directory before resolving .env.local.

Suggested change
dotenv.config({ path: path.resolve(import.meta.dirname, ".env.local") });
import { fileURLToPath } from "node:url";
const __dirname = fileURLToPath(new URL(".", import.meta.url));
dotenv.config({ path: path.resolve(__dirname, ".env.local") });

Copilot uses AI. Check for mistakes.
Comment thread convex/fluent.ts
Comment on lines +128 to +138
let hasPermission = false;
for (const slug of uniqueSlugs) {
const role = await context.db
.query("roles")
.withIndex("slug", (q) => q.eq("slug", slug))
.unique();
if (role?.permissions.includes(permission)) {
hasPermission = true;
break;
}
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

requirePermission performs an awaited role lookup inside a loop, which turns into N sequential indexed queries per request. This can become a noticeable latency multiplier for users with multiple role slugs; consider fetching the roles concurrently (e.g. Promise.all) and then checking permissions in-memory.

Suggested change
let hasPermission = false;
for (const slug of uniqueSlugs) {
const role = await context.db
.query("roles")
.withIndex("slug", (q) => q.eq("slug", slug))
.unique();
if (role?.permissions.includes(permission)) {
hasPermission = true;
break;
}
}
const slugsArray = Array.from(uniqueSlugs);
const roles = await Promise.all(
slugsArray.map((slug) =>
context.db
.query("roles")
.withIndex("slug", (q) => q.eq("slug", slug))
.unique()
)
);
const hasPermission = roles.some(
(role) => role && role.permissions.includes(permission)
);

Copilot uses AI. Check for mistakes.
Comment on lines +509 to +513
await addWidgetUser({
widgetId: selectedWidgetId as never,
userId: userId.trim(),
role,
});
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

widgetId: selectedWidgetId as never is another unsafe cast around Convex IDs. If the state is typed as the generated Id<...> type, you can remove these casts and keep end-to-end type safety from UI → mutation args.

Copilot uses AI. Check for mistakes.
<p className="mt-1 text-muted-foreground text-xs">
Synced {syncResult.orgCount} orgs,{" "}
{syncResult.membershipCount} memberships,{" "}
Synced {syncResult.userCount} user, {syncResult.orgCount}{" "}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

Minor grammar: Synced {syncResult.userCount} user, ... reads awkwardly. Since userCount is numeric, consider pluralizing ("user" vs "users") or wording like "Synced {userCount} users".

Suggested change
Synced {syncResult.userCount} user, {syncResult.orgCount}{" "}
Synced {syncResult.userCount} user
{syncResult.userCount === 1 ? "" : "s"}, {syncResult.orgCount}{" "}

Copilot uses AI. Check for mistakes.
Comment thread src/routes/__root.tsx
Comment on lines +19 to +21
// Suppress known TanStack Start SSR hydration warning (dev-only, harmless)
const SUPPRESS_WARNINGS_SCRIPT = `(function(){if(typeof window!=='undefined'){var ow=console.warn;console.warn=function(){if(typeof arguments[0]==='string'&&arguments[0].indexOf('useRouter must be used inside')!==-1)return;ow.apply(console,arguments)}}})();`;

Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

The comment says this warning suppression is "dev-only", but SUPPRESS_WARNINGS_SCRIPT is injected unconditionally into every page. This will also run in production builds and can mask real warnings. Gate the injection behind a dev-only condition (e.g. import.meta.env.DEV) or only include it when VITE_E2E/debug mode is enabled.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Connorbelez commented Mar 12, 2026

Merge activity

  • Mar 12, 4:40 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Mar 12, 4:41 PM UTC: Graphite rebased this pull request as part of a merge.
  • Mar 12, 4:42 PM UTC: @Connorbelez merged this pull request with Graphite.

@Connorbelez Connorbelez changed the base branch from 03-09-dx to graphite-base/2 March 12, 2026 16:40
@Connorbelez Connorbelez changed the base branch from graphite-base/2 to main March 12, 2026 16:40
@Connorbelez Connorbelez merged commit 6ed9496 into main Mar 12, 2026
1 check was pending
@Connorbelez Connorbelez mentioned this pull request Mar 13, 2026
Merged
@coderabbitai coderabbitai Bot mentioned this pull request Mar 15, 2026
Merged
Connorbelez added a commit that referenced this pull request Mar 16, 2026
SPEC 1.2 updates:
- §2 File Structure: corrected paths to convex/engine/ nesting
- §9 Developer Checklist: fixed all file paths
- §10 Open Questions: closed #1 (guard timing), #2 (confirmObligationPayment),
  #3 (generateObligations helper)
- Removed effects/mortgage.ts, added effects/workosProvisioning.ts

PRD 1.2 updates:
- GT DoD #3: marked deal machine as descoped to Project 4
- Renamed seedInvestor → seedLender (schema table is lenders)
- Marked seedDeal as descoped

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Connorbelez added a commit that referenced this pull request Mar 16, 2026
SPEC 1.2 updates:
- §2 File Structure: corrected paths to convex/engine/ nesting
- §9 Developer Checklist: fixed all file paths
- §10 Open Questions: closed #1 (guard timing), #2 (confirmObligationPayment),
  #3 (generateObligations helper)
- Removed effects/mortgage.ts, added effects/workosProvisioning.ts

PRD 1.2 updates:
- GT DoD #3: marked deal machine as descoped to Project 4
- Renamed seedInvestor → seedLender (schema table is lenders)
- Marked seedDeal as descoped

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Connorbelez added a commit that referenced this pull request Mar 16, 2026
#82)

ft. ENG-23: cross-entity effects, reconciliation, and engine hardening

- Replace obligation effect stubs with real cross-entity dispatch
  (emitObligationOverdue/Settled now read obligation → transition mortgage)
- Add transitionMortgageInternal for scheduler/effect use
- Add confirmObligationPayment (authedMutation + obligation:manage)
- Add notifyAdminNewRequest as entry action on pending_review state
- Filter assign-action warnings in effect scheduler (isBuiltInAction)
- Extend reconciliation to handle mortgage + obligation entity types
- Add machineContext explanatory comments to governed table schemas
- Fix reconciliation test to use non-governed entity type (deal)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

ft. ENG-23: seed mutations for all Phase 1 entities

- Create convex/seed/ directory with 8 files
- seedBroker: 2 brokers with FSRA license numbers, Ontario-based
- seedBorrower: 5 borrowers with Ontario addresses, verified IDV
- seedLender: 3 lenders with accreditation, broker-assigned
- seedMortgage: 5 mortgages with varied terms + auto-generated obligations
- generateObligations: interest-only bridge loan payment schedule
- seedObligationStates: transitions obligations to due/overdue/settled mix
- seedOnboardingRequest: 3 requests (pending_review, approved, rejected)
- seedAll: orchestrator in dependency order, idempotent

All seeds use executeTransition for non-initial states (proper journal entries).
Each entity gets a SEED_CREATED journal entry for audit trail continuity.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

ft. ENG-23: reconciliation cron + internal query

- Add daily reconciliation cron (06:00 UTC) that runs Layer 1 check
- Create reconciliationAction.ts with internalQuery (no auth required)
  and internalAction for cron scheduling
- Discrepancies logged as P0 errors via console.error
- On-demand adminQuery reconciliation remains available

Resolves SPEC Open Question #5: daily cron + on-demand admin query.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

ft. ENG-23: integration tests — guard failure, missing effects, cross-entity

- Add guard failure test: mortgage DEFAULT_THRESHOLD_REACHED with 0 missed
  payments → rejected, status unchanged
- Add missing effect handler test: unknown action → warn + succeed
- Create crossEntity.test.ts with 4 tests proving full obligation→mortgage
  cascade: overdue dispatch, settlement cure, and journal chain consistency
- Fix pre-existing test: "throws when no machine is registered" assertion

Cross-entity tests manually invoke effects (convex-test doesn't auto-run
scheduled functions), verifying the complete chain:
  obligation due→overdue → mortgage active→delinquent
  obligation overdue→settled → mortgage delinquent→active (cure)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

docs: ENG-23 — update SPEC 1.2 and PRD 1.2 on Notion

SPEC 1.2 updates:
- §2 File Structure: corrected paths to convex/engine/ nesting
- §9 Developer Checklist: fixed all file paths
- §10 Open Questions: closed #1 (guard timing), #2 (confirmObligationPayment),
  #3 (generateObligations helper)
- Removed effects/mortgage.ts, added effects/workosProvisioning.ts

PRD 1.2 updates:
- GT DoD #3: marked deal machine as descoped to Project 4
- Renamed seedInvestor → seedLender (schema table is lenders)
- Marked seedDeal as descoped

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

fix: ENG-23 — type reconciliationAction entityType parameter

Use keyof typeof ENTITY_TABLE_MAP instead of string for type safety
in collectLatestEntries function parameter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

fix: ENG-23 — address code review findings in seed mutations

- Fix idempotency: reuse existing user when entity doesn't exist yet
  (seedBorrower, seedLender, seedOnboardingRequest, seedMortgage)
- Fix seedMortgage: reuse existing property instead of duplicating
- Fix generateObligations: clamp day to prevent setMonth date drift
- Note: seedLender entityType "borrower" is intentional — lenders are
  not yet a governed entity type in the GT engine

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

## Summary by Sourcery

Implement cross-entity coordination between obligations and mortgages, extend reconciliation and scheduling infrastructure, and add idempotent seed data plus tests and cron-based health checks.

New Features:
- Enable cross-entity effects so obligation state changes drive corresponding mortgage transitions.
- Introduce internal and admin-facing commands for mortgage transitions and confirming obligation payments.
- Add an internal reconciliation query and daily cron-driven reconciliation check across governed entities.
- Seed phase-1 entities (brokers, borrowers, lenders, mortgages, obligations, onboarding requests) with realistic, journal-backed demo data.
- Provide an orchestrated seedAll mutation to populate a complete demo dataset in dependency order.

Bug Fixes:
- Ensure reconciliation skips non-governed entity types while handling mortgages and obligations correctly.
- Prevent scheduling of XState built-in actions as effects and gracefully handle missing effect handlers.
- Fix reconciliation tests to use non-governed entities and relax an over-specific transition engine error assertion.
- Clamp generated obligation due dates to avoid calendar drift in monthly schedules.
- Improve seed mutations to be idempotent by reusing existing users, properties, and entities.

Enhancements:
- Document machineContext usage for governed tables and add notification entry actions to onboarding requests.
- Expand reconciliation utilities to support internal use and improve discrepancy reporting.
- Strengthen guard behavior for mortgage default thresholds via explicit tests.

Documentation:
- Clarify governed vs non-governed entities and machine context expectations in schema comments.

Tests:
- Add cross-entity integration tests covering obligation→mortgage cascades and journal chain consistency.
- Add tests for guard failures, missing effect handlers, and reconciliation behavior across entity types.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

## Release Notes

* **New Features**
  * Added automated daily health checks to verify system integrity.
  * Enabled obligation payment confirmation through the admin dashboard.
  * Added admin notifications for new onboarding requests.

* **Improvements**
  * Enhanced coordination between related financial entities to ensure consistent state transitions.
  * Strengthened validation to prevent invalid state changes.

* **Tests**
  * Added comprehensive test coverage for entity workflows and state validation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Connorbelez added a commit that referenced this pull request Mar 28, 2026
- Wrap handlePipelineLegConfirmed in try/catch to protect cash postings (#1, #11)
- Make initiateTransferInternal retry-safe with early return (#2, #5, #10)
- Fix misleading comment in startDealClosingPipeline (#4)
- Add validatePipelineFields guard to public createTransferRequest (#7, #12)
- Document supported provider codes for deal-closing (#8)
- Filter pipeline legs by specific pipelineId in getPipelineStatus (#6)
- Return null for unknown pipeline IDs in getTransfersByPipeline (#15)
- Validate leg2Amount early in createDealClosingPipeline (#13)
- computePipelineStatus prefers active legs over terminal after retry (#14)
- Updated tests for retry scenarios (37 passing)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Connorbelez added a commit that referenced this pull request Apr 20, 2026
fluent

Added convex fluent Demo + fixed e2e RBAC auth

## Summary by Sourcery

Add a fluent-convex demo experience with supporting backend schema and auth improvements, and wire up dedicated e2e authentication/RBAC testing and CI.

New Features:
- Introduce a fluent-convex demo route and backend module showcasing middleware, validation, RBAC, callables, and custom plugins.
- Add Convex schema tables for fluent-convex demo widgets and widget-user relationships.
- Expose new routes for the fluent demo and an internal e2e organization switch helper.

Bug Fixes:
- Harden auth event handling and middleware to upsert missing users and backfill related WorkOS data, resolving race conditions where user.updated arrives before user.created.
- Ensure WorkOS sync also persists the current user and re-emits user.updated events so component state stays consistent.
- Fix RBAC e2e auth flows by introducing deterministic multi-role login and org switching.

Enhancements:
- Add reusable Convex fluent builder utilities for auth, RBAC, logging, timing, and Zod integration.
- Improve WorkOS demo sync UI to surface user sync counts and clarify status messaging.
- Suppress noisy TanStack Start SSR hydration warnings in the root document for cleaner logs.
- Document architectural patterns and event-ordering lessons for future agents.

Build:
- Load .env.local into Playwright via dotenv and flag VITE_E2E in the e2e dev script.

CI:
- Add a GitHub Actions workflow to run Playwright-based E2E tests against the deployed Convex backend.

Documentation:
- Extend AGENTS.md and AGENT_LESSONS.md with guidance on patterns, dependency injection, and webhook event ordering pitfalls.

Tests:
- Introduce Playwright helpers, setup flows, and e2e suites for auth, protected routes, and RBAC scenarios using stored authentication state.
Connorbelez added a commit that referenced this pull request Apr 20, 2026
#82)

ft. ENG-23: cross-entity effects, reconciliation, and engine hardening

- Replace obligation effect stubs with real cross-entity dispatch
  (emitObligationOverdue/Settled now read obligation → transition mortgage)
- Add transitionMortgageInternal for scheduler/effect use
- Add confirmObligationPayment (authedMutation + obligation:manage)
- Add notifyAdminNewRequest as entry action on pending_review state
- Filter assign-action warnings in effect scheduler (isBuiltInAction)
- Extend reconciliation to handle mortgage + obligation entity types
- Add machineContext explanatory comments to governed table schemas
- Fix reconciliation test to use non-governed entity type (deal)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

ft. ENG-23: seed mutations for all Phase 1 entities

- Create convex/seed/ directory with 8 files
- seedBroker: 2 brokers with FSRA license numbers, Ontario-based
- seedBorrower: 5 borrowers with Ontario addresses, verified IDV
- seedLender: 3 lenders with accreditation, broker-assigned
- seedMortgage: 5 mortgages with varied terms + auto-generated obligations
- generateObligations: interest-only bridge loan payment schedule
- seedObligationStates: transitions obligations to due/overdue/settled mix
- seedOnboardingRequest: 3 requests (pending_review, approved, rejected)
- seedAll: orchestrator in dependency order, idempotent

All seeds use executeTransition for non-initial states (proper journal entries).
Each entity gets a SEED_CREATED journal entry for audit trail continuity.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

ft. ENG-23: reconciliation cron + internal query

- Add daily reconciliation cron (06:00 UTC) that runs Layer 1 check
- Create reconciliationAction.ts with internalQuery (no auth required)
  and internalAction for cron scheduling
- Discrepancies logged as P0 errors via console.error
- On-demand adminQuery reconciliation remains available

Resolves SPEC Open Question #5: daily cron + on-demand admin query.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

ft. ENG-23: integration tests — guard failure, missing effects, cross-entity

- Add guard failure test: mortgage DEFAULT_THRESHOLD_REACHED with 0 missed
  payments → rejected, status unchanged
- Add missing effect handler test: unknown action → warn + succeed
- Create crossEntity.test.ts with 4 tests proving full obligation→mortgage
  cascade: overdue dispatch, settlement cure, and journal chain consistency
- Fix pre-existing test: "throws when no machine is registered" assertion

Cross-entity tests manually invoke effects (convex-test doesn't auto-run
scheduled functions), verifying the complete chain:
  obligation due→overdue → mortgage active→delinquent
  obligation overdue→settled → mortgage delinquent→active (cure)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

docs: ENG-23 — update SPEC 1.2 and PRD 1.2 on Notion

SPEC 1.2 updates:
- §2 File Structure: corrected paths to convex/engine/ nesting
- §9 Developer Checklist: fixed all file paths
- §10 Open Questions: closed #1 (guard timing), #2 (confirmObligationPayment),
  #3 (generateObligations helper)
- Removed effects/mortgage.ts, added effects/workosProvisioning.ts

PRD 1.2 updates:
- GT DoD #3: marked deal machine as descoped to Project 4
- Renamed seedInvestor → seedLender (schema table is lenders)
- Marked seedDeal as descoped

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

fix: ENG-23 — type reconciliationAction entityType parameter

Use keyof typeof ENTITY_TABLE_MAP instead of string for type safety
in collectLatestEntries function parameter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

fix: ENG-23 — address code review findings in seed mutations

- Fix idempotency: reuse existing user when entity doesn't exist yet
  (seedBorrower, seedLender, seedOnboardingRequest, seedMortgage)
- Fix seedMortgage: reuse existing property instead of duplicating
- Fix generateObligations: clamp day to prevent setMonth date drift
- Note: seedLender entityType "borrower" is intentional — lenders are
  not yet a governed entity type in the GT engine

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

## Summary by Sourcery

Implement cross-entity coordination between obligations and mortgages, extend reconciliation and scheduling infrastructure, and add idempotent seed data plus tests and cron-based health checks.

New Features:
- Enable cross-entity effects so obligation state changes drive corresponding mortgage transitions.
- Introduce internal and admin-facing commands for mortgage transitions and confirming obligation payments.
- Add an internal reconciliation query and daily cron-driven reconciliation check across governed entities.
- Seed phase-1 entities (brokers, borrowers, lenders, mortgages, obligations, onboarding requests) with realistic, journal-backed demo data.
- Provide an orchestrated seedAll mutation to populate a complete demo dataset in dependency order.

Bug Fixes:
- Ensure reconciliation skips non-governed entity types while handling mortgages and obligations correctly.
- Prevent scheduling of XState built-in actions as effects and gracefully handle missing effect handlers.
- Fix reconciliation tests to use non-governed entities and relax an over-specific transition engine error assertion.
- Clamp generated obligation due dates to avoid calendar drift in monthly schedules.
- Improve seed mutations to be idempotent by reusing existing users, properties, and entities.

Enhancements:
- Document machineContext usage for governed tables and add notification entry actions to onboarding requests.
- Expand reconciliation utilities to support internal use and improve discrepancy reporting.
- Strengthen guard behavior for mortgage default thresholds via explicit tests.

Documentation:
- Clarify governed vs non-governed entities and machine context expectations in schema comments.

Tests:
- Add cross-entity integration tests covering obligation→mortgage cascades and journal chain consistency.
- Add tests for guard failures, missing effect handlers, and reconciliation behavior across entity types.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

## Release Notes

* **New Features**
  * Added automated daily health checks to verify system integrity.
  * Enabled obligation payment confirmation through the admin dashboard.
  * Added admin notifications for new onboarding requests.

* **Improvements**
  * Enhanced coordination between related financial entities to ensure consistent state transitions.
  * Strengthened validation to prevent invalid state changes.

* **Tests**
  * Added comprehensive test coverage for entity workflows and state validation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants