feat: add origination owner config#434
feat: add origination owner config#434Connorbelez wants to merge 5 commits intographite-base/434from
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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.
Sorry @Connorbelez, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
Reviewer's GuideAdds a canonical default origination owner configuration for FairLend MIC, seeds and validates it via new platform ownership tables and seed flows, and wires mortgage activation, tests, and e2e flows to mint mortgages and issue full share supply to that configured owner while failing closed on misconfiguration. Sequence diagram for mortgage activation and share issuance to default origination ownersequenceDiagram
actor AdminUser
participant AdminUI
participant ConvexServer
participant ActivateMortgage as activateMortgageAggregate
participant MintHandler as mintMortgageHandler
participant OwnerResolver as getRequiredDefaultOriginationOwner
participant IssueShares as issueSharesHandler
participant LedgerAccounts
participant LedgerEntries
AdminUser->>AdminUI: Commit origination case
AdminUI->>ConvexServer: admin.origination.commit.commitCase
ConvexServer->>ActivateMortgage: activateMortgageAggregate(args)
ActivateMortgage->>MintHandler: mintMortgageHandler(ctx, mortgageMintArgs)
MintHandler-->>LedgerAccounts: create TREASURY and POSITION accounts
MintHandler-->>LedgerEntries: insert MORTGAGE_MINTED entry
MintHandler-->>ActivateMortgage: mintedMortgageId
ActivateMortgage->>OwnerResolver: getRequiredDefaultOriginationOwner(ctx)
OwnerResolver->>PlatformSettings: query by_key(key=default)
OwnerResolver->>Lenders: load defaultOriginationLenderId
OwnerResolver->>InvestmentVehicles: load defaultOriginationInvestmentVehicleId
OwnerResolver->>InvestmentVehicleWorkspaces: load defaultOriginationWorkspaceId
OwnerResolver->>BankAccounts: load defaultFairlendTrustBankAccountId
OwnerResolver-->>ActivateMortgage: defaultOriginationOwner(lender, investmentVehicle, workspace, trustBankAccount, settings)
ActivateMortgage->>IssueShares: issueSharesHandler(ctx, issueArgs)
IssueShares-->>LedgerAccounts: ensure POSITION account for lenderId
IssueShares-->>LedgerEntries: insert SHARES_ISSUED entry
IssueShares-->>ActivateMortgage: issueResult
ActivateMortgage-->>ConvexServer: activationResult with committedMortgageId
ConvexServer-->>AdminUI: success
AdminUI-->>AdminUser: Case committed with treasury and MIC position
Sequence diagram for seeding canonical FairLend MIC platform ownershipsequenceDiagram
actor Dev
participant SeedAll as seedAll_adminAction
participant SeedPlatform as seedPlatformOwnership
participant SeedHelpers
participant DefaultOwner as upsertDefaultOriginationOwner
participant DbPlatform as platformSettings
participant DbLenders as lenders
participant DbVehicles as investmentVehicles
participant DbWorkspaces as investmentVehicleWorkspaces
participant DbBankAccounts as bankAccounts
Dev->>SeedAll: run seedAll
SeedAll->>SeedPlatform: runMutation(seedPlatformOwnership, { brokerId })
Note over SeedPlatform: ensure canonical FairLend MIC user
SeedPlatform->>SeedHelpers: ensureUserByEmail(FAIRLEND_MIC_LENDER_EMAIL)
SeedHelpers-->>SeedPlatform: { userId }
Note over SeedPlatform: upsert MIC lender
SeedPlatform->>SeedHelpers: findLenderByUserId(userId)
SeedHelpers-->>SeedPlatform: existingLender or null
alt lender exists
SeedPlatform-->>DbLenders: reuse lenderId
else lender missing
SeedPlatform->>DbLenders: insert FairLend MIC lender
SeedPlatform->>SeedHelpers: writeCreationJournalEntry(lender)
end
Note over SeedPlatform: upsert MIC investment vehicle
SeedPlatform->>DbVehicles: query by_lender(lenderId)
alt vehicle exists
SeedPlatform-->>DbVehicles: reuse investmentVehicleId
else vehicle missing
SeedPlatform->>DbVehicles: insert MIC vehicle
SeedPlatform->>SeedHelpers: writeCreationJournalEntry(vehicle)
end
Note over SeedPlatform: upsert MIC workspace
SeedPlatform->>DbWorkspaces: query by_vehicle(investmentVehicleId)
alt workspace exists
SeedPlatform-->>DbWorkspaces: reuse workspaceId
else workspace missing
SeedPlatform->>DbWorkspaces: insert workspace
SeedPlatform->>SeedHelpers: writeCreationJournalEntry(workspace)
end
Note over SeedPlatform: upsert trust bank account
SeedPlatform->>DbPlatform: query platformSettings by_key(default)
DbPlatform-->>SeedPlatform: existingSettings or null
SeedPlatform->>DbBankAccounts: query by_owner(trust, investmentVehicleId)
alt preferred or reusable account exists
SeedPlatform-->>DbBankAccounts: reuse trustBankAccountId
else none available
SeedPlatform->>DbBankAccounts: insert trust bank account
end
Note over SeedPlatform,DefaultOwner: upsert platformSettings.default
SeedPlatform->>DefaultOwner: upsertDefaultOriginationOwner(ctx, linkArgs)
DefaultOwner->>DbPlatform: query by_key(default)
DefaultOwner->>DbLenders: validate FairLend MIC lender and email
DefaultOwner->>DbVehicles: validate MIC vehicle name and legalName
DefaultOwner->>DbWorkspaces: validate workspace link
DefaultOwner->>DbBankAccounts: validate trust account ownership
DefaultOwner->>DbPlatform: insert or patch platformSettings.default
DefaultOwner-->>SeedPlatform: { settings }
SeedPlatform-->>SeedAll: SeedPlatformOwnershipResult(created, reused, ids)
SeedAll-->>Dev: aggregated seed summary including platform ownership
ER diagram for new platform ownership tables and relationshipserDiagram
Lenders {
string _id
string orgId
string userId
string brokerId
string status
string accreditationStatus
number createdAt
}
InvestmentVehicles {
string _id
string lenderId
string name
string legalName
string entityType
string status
number createdAt
number updatedAt
}
InvestmentVehicleWorkspaces {
string _id
string investmentVehicleId
string name
string status
number createdAt
number updatedAt
}
BankAccounts {
string _id
string ownerType
string ownerId
string status
string accountLast4
string currency
string country
number createdAt
number updatedAt
}
PlatformSettings {
string _id
string key
string defaultOriginationLenderId
string defaultOriginationInvestmentVehicleId
string defaultOriginationWorkspaceId
string defaultFairlendTrustBankAccountId
string updatedBy
string changeReason
string source
number createdAt
number updatedAt
}
Brokers {
string _id
string orgId
string userId
string status
}
Users {
string _id
string email
string authId
}
%% Relationships introduced or enforced in this PR
Lenders ||--o{ InvestmentVehicles : has
InvestmentVehicles ||--o{ InvestmentVehicleWorkspaces : has
InvestmentVehicles ||--o{ BankAccounts : owns_via_trust
PlatformSettings }o--|| Lenders : default_origination_lender
PlatformSettings }o--|| InvestmentVehicles : default_origination_vehicle
PlatformSettings }o--o{ InvestmentVehicleWorkspaces : default_workspace
PlatformSettings }o--o{ BankAccounts : default_trust_account
Brokers ||--o{ Lenders : has
Users ||--o{ Lenders : owns
Brokers ||--o{ Users : uses
Class diagram for default origination owner and platform ownership seeding modulesclassDiagram
class DefaultOriginationOwnerContract {
<<module>>
+string FAIRLEND_MIC_LENDER_EMAIL
+string FAIRLEND_MIC_INVESTMENT_VEHICLE_NAME
+string FAIRLEND_MIC_INVESTMENT_VEHICLE_LEGAL_NAME
}
class DefaultOriginationOwnerModule {
<<module>>
+string PLATFORM_SETTINGS_KEY
+getRequiredDefaultOriginationOwner(ctx)
+upsertDefaultOriginationOwner(ctx, args)
+getDefaultOriginationOwner
+setDefaultOriginationOwner
-snapshotPlatformSettings(settings)
-missingDefaultOriginationOwnerError()
-invalidDefaultOriginationOwnerError(message)
-normalizeConfiguredEmail(email)
-resolveDefaultOriginationOwnerLinks(ctx, linkArgs)
}
class DefaultOriginationOwnerCtx {
+any db
}
class DefaultOriginationOwnerLinkArgs {
+Id~bankAccounts~ defaultFairlendTrustBankAccountId
+Id~investmentVehicles~ defaultOriginationInvestmentVehicleId
+Id~lenders~ defaultOriginationLenderId
+Id~investmentVehicleWorkspaces~ defaultOriginationWorkspaceId
}
class UpsertDefaultOriginationOwnerArgs {
+string actorId
+ActorType actorType
+string changeReason
+CommandChannel channel
+string source
+Id~bankAccounts~ defaultFairlendTrustBankAccountId
+Id~investmentVehicles~ defaultOriginationInvestmentVehicleId
+Id~lenders~ defaultOriginationLenderId
+Id~investmentVehicleWorkspaces~ defaultOriginationWorkspaceId
}
class SeedPlatformOwnershipModule {
<<module>>
+seedPlatformOwnership
-upsertFairLendMicLender(ctx, args)
-upsertFairLendMicVehicle(ctx, args)
-upsertFairLendMicWorkspace(ctx, args)
-upsertFairLendMicTrustAccount(ctx, args)
}
class SeedPlatformOwnershipResult {
+SeedPlatformOwnershipCreatedCounts created
+SeedPlatformOwnershipReusedCounts reused
+Id~bankAccounts~ defaultFairlendTrustBankAccountId
+Id~investmentVehicles~ defaultOriginationInvestmentVehicleId
+Id~lenders~ defaultOriginationLenderId
+Id~investmentVehicleWorkspaces~ defaultOriginationWorkspaceId
+Id~platformSettings~ settingsId
}
class SeedPlatformOwnershipCreatedCounts {
+number bankAccounts
+number investmentVehicles
+number investmentVehicleWorkspaces
+number lenders
+number platformSettings
}
class SeedPlatformOwnershipReusedCounts {
+number bankAccounts
+number investmentVehicles
+number investmentVehicleWorkspaces
+number lenders
+number platformSettings
}
class SeedHelpersModule {
<<module>>
+ensureUserByEmail(ctx, args)
+findLenderByUserId(ctx, userId)
+seedAuthIdFromEmail(email)
+seedTimestamp(offsetMs)
+writeCreationJournalEntry(ctx, args)
+string SEED_SOURCE
}
class MortgageActivationModule {
<<module>>
+activateMortgageAggregate(ctx, args)
}
class IssueSharesHandlerModule {
<<module>>
+issueSharesHandler(ctx, args)
}
class MintMortgageHandlerModule {
<<module>>
+mintMortgageHandler(ctx, args)
}
DefaultOriginationOwnerModule --> DefaultOriginationOwnerContract : uses_constants
SeedPlatformOwnershipModule --> DefaultOriginationOwnerModule : calls_upsertDefaultOriginationOwner
SeedPlatformOwnershipModule --> DefaultOriginationOwnerContract : uses_constants
SeedPlatformOwnershipModule --> SeedHelpersModule : uses_seed_helpers
MortgageActivationModule --> DefaultOriginationOwnerModule : calls_getRequiredDefaultOriginationOwner
MortgageActivationModule --> IssueSharesHandlerModule : calls_issueSharesHandler
MortgageActivationModule --> MintMortgageHandlerModule : calls_mintMortgageHandler
DefaultOriginationOwnerModule ..> DefaultOriginationOwnerCtx : uses
DefaultOriginationOwnerModule ..> DefaultOriginationOwnerLinkArgs : uses
DefaultOriginationOwnerModule ..> UpsertDefaultOriginationOwnerArgs : uses
SeedPlatformOwnershipModule ..> SeedPlatformOwnershipResult : returns
SeedPlatformOwnershipResult o-- SeedPlatformOwnershipCreatedCounts : has
SeedPlatformOwnershipResult o-- SeedPlatformOwnershipReusedCounts : has
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Pull request overview
Adds a canonical “default origination owner” configuration (pinned to the FairLend MIC) and wires origination to automatically issue 100% of new mortgage share supply to that configured platform owner, with seeding + validation hardening and expanded test coverage.
Changes:
- Introduces platform ownership settings + resolver/validator (
platformSettings["default"]) and an admin mutation/query to manage it with audit journaling. - Adds an idempotent-ish seeding path for the canonical FairLend MIC lender/vehicle/workspace/trust bank account and integrates it into
seedAll. - Updates mortgage activation/origination flows to issue
TOTAL_SUPPLYshares to the configured default origination owner, plus new unit/E2E coverage.
Reviewed changes
Copilot reviewed 20 out of 21 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/test/convex/seed/seedPlatformOwnershipChaos.test.ts | Chaos regression for replay pinning to the original MIC trust account. |
| src/test/convex/seed/seedPlatformOwnership.test.ts | Validates seed idempotency/reuse behavior for canonical ownership artifacts. |
| src/test/convex/seed/seedAll.test.ts | Extends seed-all assertions to include new platform ownership tables/settings. |
| src/test/convex/platform/defaultOriginationOwner.test.ts | Covers fail-closed + governance audit + lookalike lender rejection. |
| src/test/convex/admin/origination/commit.test.ts | Adds seeding + assertions for SHARES_ISSUED and failure-closed paths. |
| e2e/origination/commit-and-inspect.spec.ts | E2E assertions verifying issuance artifacts post-commit. |
| e2e/helpers/origination.ts | Client helpers for ensuring default owner and fetching committed artifacts. |
| docs/superpowers/plans/2026-04-20-default-origination-owner.md | Implementation plan documenting schema/seeding/origination wiring. |
| convex/test/originationE2e.ts | Adds admin query to fetch committed origination artifacts for E2E. |
| convex/test/moduleMaps.ts | Registers new Convex modules for the test harness. |
| convex/seed/seedPlatformOwnership.ts | Seeds canonical MIC ownership artifacts + upserts platform settings. |
| convex/seed/seedLender.ts | Removes the prior “Maple MIC” lender fixture. |
| convex/seed/seedDeal.ts | Updates default lender auth IDs to reference the canonical FairLend MIC. |
| convex/seed/seedAll.ts | Wires platform ownership seeding and extends seed summaries/artifacts. |
| convex/schema.ts | Adds platformSettings, investmentVehicles, investmentVehicleWorkspaces, and settings scaffolds. |
| convex/platform/defaultOriginationOwnerContract.ts | Centralizes canonical MIC identifiers (email/name/legal name). |
| convex/platform/defaultOriginationOwner.ts | Resolver + strict validation + audited upsert for default origination owner. |
| convex/mortgages/activateMortgageAggregate.ts | Issues TOTAL_SUPPLY shares to default owner during activation. |
| convex/engine/validators.ts | Extends audit entity type validator for new entities. |
| convex/engine/types.ts | Extends EntityType + ENTITY_TABLE_MAP for new entities. |
| convex/_generated/api.d.ts | Regenerated API typings for the new modules. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "Configured trust account is not owned by the configured investment vehicle" | ||
| ); | ||
| } | ||
|
|
There was a problem hiding this comment.
resolveDefaultOriginationOwnerLinks validates the trust bank account is owned by the configured vehicle, but it doesn’t validate the bank account is actually usable (e.g. status === "validated", and mandate constraints if applicable). As-is, an admin (or migration) could point defaultFairlendTrustBankAccountId at a revoked/rejected/pending_validation account and the config would still pass, likely failing later payment flows. Consider enforcing at least status === "validated" here (and align with the existing transfer validation rules).
| if (trustBankAccount && trustBankAccount.status !== "validated") { | |
| throw invalidDefaultOriginationOwnerError( | |
| "Configured trust account is not validated" | |
| ); | |
| } |
| const now = Date.now(); | ||
| const existing = await ctx.db | ||
| .query("platformSettings") | ||
| .withIndex("by_key", (q) => q.eq("key", PLATFORM_SETTINGS_KEY)) | ||
| .unique(); | ||
| const beforeState = existing ? snapshotPlatformSettings(existing) : undefined; | ||
|
|
||
| const resolved = await resolveDefaultOriginationOwnerLinks(ctx, args); | ||
| const nextSettingsFields = { | ||
| key: PLATFORM_SETTINGS_KEY, | ||
| defaultOriginationLenderId: args.defaultOriginationLenderId, | ||
| defaultOriginationInvestmentVehicleId: | ||
| args.defaultOriginationInvestmentVehicleId, | ||
| defaultOriginationWorkspaceId: args.defaultOriginationWorkspaceId, | ||
| defaultFairlendTrustBankAccountId: args.defaultFairlendTrustBankAccountId, | ||
| updatedBy: args.actorId, | ||
| changeReason: args.changeReason, | ||
| source: args.source, | ||
| updatedAt: now, | ||
| }; | ||
|
|
||
| let settingsId: Id<"platformSettings">; | ||
| if (existing) { | ||
| await ctx.db.patch(existing._id, nextSettingsFields); | ||
| settingsId = existing._id; | ||
| } else { | ||
| settingsId = await ctx.db.insert("platformSettings", { | ||
| ...nextSettingsFields, | ||
| createdAt: now, | ||
| }); | ||
| } | ||
|
|
||
| const settings = await ctx.db.get(settingsId); | ||
| if (!settings) { | ||
| throw new Error("Expected platform settings to exist after upsert"); | ||
| } | ||
| const afterState = snapshotPlatformSettings(settings); | ||
|
|
||
| await appendAuditJournalEntry(ctx, { | ||
| actorId: args.actorId, | ||
| actorType: args.actorType, | ||
| channel: args.channel, | ||
| entityId: String(settingsId), | ||
| entityType: "platformSetting", | ||
| afterState, | ||
| eventCategory: "configuration", | ||
| eventType: "DEFAULT_ORIGINATION_OWNER_SET", | ||
| newState: "configured", | ||
| organizationId: resolved.lender.orgId, | ||
| outcome: "transitioned", | ||
| payload: { | ||
| after: afterState, | ||
| before: beforeState ?? null, | ||
| changeReason: args.changeReason, | ||
| source: args.source, | ||
| }, | ||
| beforeState, | ||
| previousState: existing ? "configured" : "none", | ||
| timestamp: now, | ||
| }); |
There was a problem hiding this comment.
upsertDefaultOriginationOwner always patches the existing platformSettings row (updating updatedAt/updatedBy) and always appends an audit journal entry, even when the configured IDs haven’t changed. This makes seedPlatformOwnership/seedAll replays non-idempotent and will spam the audit journal on every seed run. Consider detecting when the settings are already identical and early-returning without patching or writing an audit entry (similar to how other seed/config upserts skip no-op patches).
- Enforce canonical FairLend MIC lender/vehicle contract - Reuse existing trust accounts when seeding platform ownership - Add commit and seed test coverage for default share issuance
- Factor the canonical MIC lender email into a shared constant - Reject default origination owner configs that point at lookalike lenders - Add replay coverage so seeding stays pinned to the original trust account
0690f5d to
4deb6d8
Compare

feat: add origination owner config
feat: seed canonical mic owner
feat: issue shares to mic owner
Harden default origination owner seeding and validation
Validate canonical MIC lender identity in seeding
Summary by Sourcery
Introduce canonical default origination owner configuration tied to the FairLend MIC, and ensure newly originated mortgages automatically issue full-share ownership to this configured platform owner while enforcing strict validation and seeding behavior.
New Features:
Bug Fixes:
Enhancements:
Documentation: