[DONT MERGE] Add mocked dashboard app#1236
Open
Sharqiewicz wants to merge 19 commits into
Open
Conversation
❌ Deploy Preview for vortex-sandbox failed. Why did it fail? →
|
❌ Deploy Preview for vortexfi failed. Why did it fail? →
|
…hain/vortex into feature/dashboard-app-staging
… dual-write) Realizes the complete data model from the two architecture docs in one migration set, per the amended plan (migrate data once, cut reads over next, keep legacy tables as read-only backup): - 038 customer_entities + backfill (one individual entity per profile) - 039 partners split: partner_pricing_configs, dedup to unique-name partners, FK repoint (quotes/assignments) before fold, partners_legacy snapshot, assignments partner_id collapse, idempotent vortex re-seed - 040 provider_customers + kyc_cases + backfills from mykobo_customers, alfredpay_customers, tax_ids (owned rows only; statuses verbatim; raw tax ids replaced by sha256 hash + masked display; ownerless rows quarantined) - 041 api_keys.partner_id FK (backfill from partner_name) + scopes + revoked_at (user_id from 034 already is the doc's profile_id) - 042 recipient_invitations (link-based, D1) + sender_recipients + recipient_payout_references (thin provider pointer, D3) - 043 notifications + notification_preferences All new tables ENABLE ROW LEVEL SECURITY (Supabase default privileges grant anon/authenticated ALL on future tables; RLS-no-policy denies PostgREST). New status columns are VARCHAR + CHECK (plan D6). Verified: full chain 001-043 clean on fresh PG16; backfills exercised against seeded legacy fixtures (dup alfredpay rows, IND+BUS same country, multi-tax-id user, empty-string subaccount sentinel, orphan tax_id, divergent per-direction partner activity, ghost partner_name key) with all assertions passing. NOTE: not independently deployable — existing code still reads the pre-split partners shape; the cutover commits follow. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
zod was added to packages/shared and @vortexfi/sdk pinned shared to =0.2.0 without a lockfile update; the missing zod entry broke bun build:shared. Pure bun install output — no package.json changes here. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Existing functionality unchanged; pricing now resolves through the split
schema (migrate-once cutover, legacy columns unread):
- New partners/partner-pricing.service: findPartnerWithPricing merges the
unique-name partner row with its per-direction pricing config — the exact
semantics of the pre-split Partner.findOne({name|id, isActive, rampType})
- partner-resolution: assignment branch reads the collapsed
profile_partner_assignments.partner_id (buy/sell pair now unread backup)
- quote-fees: single pricing-config lookup replaces the findAll loop;
CalculateFeeComponentsRequest.partnerName renamed to partnerId (it always
carried an id)
- discount helpers: ActivePartner sourced from pricing configs; in-memory
dynamic-difference state keyed by (partner_id, ramp_type) to preserve the
per-direction isolation the split rows used to give for free
- feeDistribution: vortex + markup payout addresses read from pricing configs
- apiKeyAuth: secret/public keys resolve partners via api_keys.partner_id FK;
partner_name never read; user-scoped branch keys off partner_id IS NULL
- admin controllers: unique-name partner resolution; assignments write a
single partner_id; api-key CRUD keyed by partner_id; revocation stamps
revoked_at; ambiguity 409 retired (structurally impossible)
- factories: createTestPartner creates partner + config (findOrCreate by
unique name); seedVortexPartners = 1 partner + BUY/SELL configs;
updatePartnerPricing helper for per-direction test overrides
Verified: full API suite green (414 pass / 0 fail) including the golden
pricing test (fee parity through the split), corridor scenarios and
sdk-contract suites. Security-spec updated in the same change set:
01-auth/api-keys.md, 03-ramp-engine/{profile-partner-pricing,
discount-mechanism,fee-integrity,quote-lifecycle}.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mers/kyc_cases
Migrate-once cutover of the EUR (Mykobo) and BRL (Avenia) provider identity;
existing functionality unchanged (full suite green: 415 pass / 0 fail):
- customer-entity.service: getOrCreateCustomerEntityForProfile — eager create
in verify-otp (outside the OTP error mapping so a DB failure cannot surface
as "Invalid OTP") + lazy fallback on every entity-scoped read (covers users
with pre-existing sessions who never re-verify)
- mykobo-customer.service: KYC mirror upserts/reads provider_customers
(provider=mykobo, provider_customer_id = last-synced email); module path and
exports unchanged (integration-test mock seam preserved)
- avenia-customer.service (new): hash/mask helpers, tax-hash lookups,
kyc_cases upkeep, idempotent Requested→Accepted/Rejected outcome transition
- avenia-account/brla.controller/ramp.service/phase handlers/worker: all
tax_ids reads keyed by tax_reference_hash against provider_customers;
ownership checks compare the effective user's customer_entity; SDK-matched
error strings byte-identical ("Subaccount not found", "KYC invalid", ...)
- createSubaccount keeps claim-on-create: quarantined (ownerless) legacy
tax_ids rows are adoptable via a one-time read-only probe of the backup;
owned legacy rows still 409
- Ownership gaps closed (the real remaining Phase 5): fetchSubaccountKycStatus
(which also writes status transitions) and getSelfieLivenessUrl now enforce
account ownership. Known residual: getKybAttemptStatus cannot be bound
server-side (attempt response carries no subaccount linkage)
- Migration 040 amended (pre-deploy): provider_customers.tax_reference retains
the raw normalized tax id — documented deviation from the "no raw tax IDs"
non-goal; it is the join/aggregation key for in-flight ramp state and
already persists in ramp_states.state JSONB
- Quote-provenance writes dropped (write-only columns); tests updated
(brla.controller.test rewritten for the new model incl. legacy-adoption
scenarios; factories create provider_customers; unique-subaccount fix in
sdk-contract)
Security-spec updated in the same change set: 05-integrations/brla.md
(+ cutover section documenting the raw-tax-reference deviation and the KYB
residual), 05-integrations/mykobo.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…odels Completes the provider cutover (full suite green: 415 pass / 0 fail): - alfredpay-customer.service (new): findAlfredpayCustomer resolves the caller's customer_entity and preserves the legacy updatedAt-DESC tie-break; a legacy-shaped view keeps the controller's status machine verbatim (alfredPayId/type/status accessors); status transitions mirror into the account's kyc_cases row; createAlfredpayCustomer maps country → rail - alfredpay.controller: all 22 lookup/create sites ported (mechanical); status vocabulary unchanged — and UPDATE_REQUIRED writes now succeed (the legacy Postgres enum lacked the value the shared enum had) - alfredpay.helpers/quote resolvers: customerType ASC keeps the type-ASC precedence; KYC gates read provider_customers - Removed models with zero remaining readers: AlfredPayCustomer, MykoboCustomer, KycLevel2 (tables stay as read-only backup). TaxId model retained solely for the createSubaccount legacy-adoption probe Security-spec updated: 05-integrations/alfredpay.md (+ cutover section), 07-operations/api-surface.md and 03-ramp-engine/quote-lifecycle.md resolution chains. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dashboard backend surface (plan §7/§8/D5), reading the new tables directly: - /v1/recipients: link-token invites (sha256-hashed at rest, 14-day TTL, optional email binding), token-bound acceptance creating the sender_recipients relationship, listing, nickname/block/archive PATCH, and the transfer-eligibility gate with per-check blocking reasons. - /v1/notifications: owner-scoped feed + unread count, read/read-all, preferences (defaults on first read). emitNotification never throws; email dispatch (D7) deliberately not wired yet. - GET /v1/onboarding/status: per-entity aggregate over provider_customers + kyc_cases with a normalized approved/pending/rejected state. New security specs 03-ramp-engine/recipient-transfers.md and 07-operations/notifications.md; plan Phase 1 marked landed (§7.1 payout instrument mechanism still open, so verified payout references have no production writer yet). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 0 core connection plus the swap-execution engine, ported from the
widget's ramp machine per plan §9:
- Real auth: /v1/auth request-otp/verify-otp/refresh via a ported
AuthService (dashboard-scoped storage keys) + api-client with bearer
auth and single-flight 401 refresh; login page drops the fake OTP.
- Real quotes: POST /v1/quotes with a two-pass inversion so the
payout-driven form ("recipient gets X") maps onto the input-driven
quote endpoint.
- transfer.machine.ts: the widget's RegisterRamp → UpdateRamp/sign →
StartRamp → RampFollowUp core as a dashboard-owned XState machine —
register with fresh ephemerals, presign ephemeral txs client-side
(signUnsignedTransactions), sign user-owned txs with the wagmi wallet
(typed-data permits + squidRouter broadcasts, EVM-only), start, then
poll to terminal. An app-lifetime actor keeps polling after the form
unmounts and feeds the transactions table.
- CORS: allow :5174 in development; dashboard gains @vortexfi/shared
(runtime signing helpers), .env.example, esnext build target.
AssetHub payin is disabled (no substrate wallet in the dashboard).
Alfredpay/BRL offramp additionalData stays incomplete until the §7.1
payout-instrument capture lands — EUR is the fully-wirable corridor.
Recipients/notifications/onboarding pages still run on mocks; wiring
them to the new /v1 endpoints is the remaining §9-D work.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Code-verified per corridor: registration is structurally a self-offramp — destinations are already sender-bound on mykobo (anchor-side IBAN) and alfredpay (fiatAccountId scoped to the derived customer); only BRL takes a third-party destination (pix key + receiverTaxId consistency check). Sender→recipient transfers therefore need a second principal in registration (resolve the payout side from the recipient's provider identity), not a bolted-on eligibility check. Recorded as a pressing open decision (plan §12.7) and corrected in the recipient-transfers spec. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a new
apps/dashboardsub-app (mocked) with auth, onboarding, recipients, transactions, and transfer UI.Changes
apps/dashboard/Vite + shadcn/ui apppackage.json:build:dashboard/dev:dashboardscripts wired inapps/frontend/_redirects: SPA fallback for/dashboard/*Branch is rebuilt on top of the current
stagingtip so it compares cleanly.