Conversation
Reviewer's GuideImplements 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 upsertssequenceDiagram
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
Sequence diagram for E2E WorkOS login and org switching via /e2e/switch-orgsequenceDiagram
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)
ER diagram for new fluent-convex demo tables and their relationshipserDiagram
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
Class diagram for fluent Convex builder, middleware, and reusable chainsclassDiagram
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
Class diagram for fluentConvex demo endpoints and demo_fluent tablesclassDiagram
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
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (24)
📝 WalkthroughWalkthroughThis 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan for PR comments
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. Comment |
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- In
convex/fluent.ts,authMiddlewaredowncasts the context toRecord<string, unknown>to accessscheduler; consider extending the$contexttyping to includeschedulerinstead so you can avoid the unsafe cast and keep the middleware fully type-safe. - Several backend paths now log to
console(auth.tsuser 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
requirePermissionmiddleware inconvex/fluent.tsperforms onerolesquery 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>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({ |
There was a problem hiding this comment.
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.
| - 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. |
There was a problem hiding this comment.
issue (typo): Fix spelling of "Dependancy" to "Dependency".
Typo: change "Dependancy Injection" to "Dependency Injection" to use the correct term.
| - 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. |
There was a problem hiding this comment.
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.
| await ctx.db.insert("users", { | ||
| authId: event.data.id, | ||
| email: event.data.email, | ||
| firstName: `${event.data.firstName}`, | ||
| lastName: `${event.data.lastName}`, | ||
| }); |
There was a problem hiding this comment.
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.
| 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" | ||
| ); |
There was a problem hiding this comment.
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.
| setResult({}); | ||
| try { | ||
| await removeWidgetUser({ id: id as never }); | ||
| setResult({ success: "User removed" }); |
There was a problem hiding this comment.
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.
| - 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. |
There was a problem hiding this comment.
Spelling: "Dependancy Injection" should be "Dependency Injection".
| - 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. |
|
|
||
| dotenv.config({ path: path.resolve(import.meta.dirname, ".env.local") }); |
There was a problem hiding this comment.
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.
| 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") }); |
| 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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) | |
| ); |
| await addWidgetUser({ | ||
| widgetId: selectedWidgetId as never, | ||
| userId: userId.trim(), | ||
| role, | ||
| }); |
There was a problem hiding this comment.
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.
| <p className="mt-1 text-muted-foreground text-xs"> | ||
| Synced {syncResult.orgCount} orgs,{" "} | ||
| {syncResult.membershipCount} memberships,{" "} | ||
| Synced {syncResult.userCount} user, {syncResult.orgCount}{" "} |
There was a problem hiding this comment.
Minor grammar: Synced {syncResult.userCount} user, ... reads awkwardly. Since userCount is numeric, consider pluralizing ("user" vs "users") or wording like "Synced {userCount} users".
| Synced {syncResult.userCount} user, {syncResult.orgCount}{" "} | |
| Synced {syncResult.userCount} user | |
| {syncResult.userCount === 1 ? "" : "s"}, {syncResult.orgCount}{" "} |
| // 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)}}})();`; | ||
|
|
There was a problem hiding this comment.
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.
Merge activity
|
112b9eb to
1fcb291
Compare
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>
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>
#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 -->
- 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>
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.
#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 -->

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:
Bug Fixes:
Enhancements:
Build:
CI:
Documentation:
Tests:
Summary by CodeRabbit
Release Notes
New Features
Tests
Documentation
Chores