diff --git a/backend/prisma/migrations/20260925000000_add_vault_allocation_tenant_fields/migration.sql b/backend/prisma/migrations/20260925000000_add_vault_allocation_tenant_fields/migration.sql new file mode 100644 index 000000000..4cad9f7d1 --- /dev/null +++ b/backend/prisma/migrations/20260925000000_add_vault_allocation_tenant_fields/migration.sql @@ -0,0 +1,86 @@ +-- AlterTable +ALTER TABLE "Transaction" ADD COLUMN "failureReason" TEXT; +ALTER TABLE "Transaction" ADD COLUMN "latencyMs" INTEGER; +ALTER TABLE "Transaction" ADD COLUMN "tenantId" TEXT; +ALTER TABLE "Transaction" ADD COLUMN "vaultId" TEXT; + +-- CreateTable +CREATE TABLE "Vault" ( + "id" TEXT NOT NULL PRIMARY KEY, + "tenantId" TEXT NOT NULL, + "aum" REAL NOT NULL DEFAULT 0, + "tvlUsd" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "deletedAt" DATETIME +); + +-- CreateTable +CREATE TABLE "Strategy" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +-- CreateTable +CREATE TABLE "Allocation" ( + "id" TEXT NOT NULL PRIMARY KEY, + "vaultId" TEXT NOT NULL, + "strategyId" TEXT NOT NULL, + "amount" REAL NOT NULL DEFAULT 0, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Allocation_vaultId_fkey" FOREIGN KEY ("vaultId") REFERENCES "Vault" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "Allocation_strategyId_fkey" FOREIGN KEY ("strategyId") REFERENCES "Strategy" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "ExposureBreach" ( + "id" TEXT NOT NULL PRIMARY KEY, + "vaultId" TEXT NOT NULL, + "strategyId" TEXT NOT NULL, + "attemptedAmount" TEXT NOT NULL, + "reason" TEXT NOT NULL, + "timestamp" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +-- CreateTable +CREATE TABLE "WalletTenantAssociation" ( + "id" TEXT NOT NULL PRIMARY KEY, + "walletAddress" TEXT NOT NULL, + "tenantId" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deletedAt" DATETIME +); + +-- CreateIndex +CREATE INDEX "Vault_tenantId_idx" ON "Vault"("tenantId"); + +-- CreateIndex +CREATE INDEX "Vault_deletedAt_idx" ON "Vault"("deletedAt"); + +-- CreateIndex +CREATE INDEX "Allocation_vaultId_idx" ON "Allocation"("vaultId"); + +-- CreateIndex +CREATE INDEX "Allocation_strategyId_idx" ON "Allocation"("strategyId"); + +-- CreateIndex +CREATE INDEX "ExposureBreach_vaultId_idx" ON "ExposureBreach"("vaultId"); + +-- CreateIndex +CREATE INDEX "ExposureBreach_strategyId_idx" ON "ExposureBreach"("strategyId"); + +-- CreateIndex +CREATE INDEX "ExposureBreach_timestamp_idx" ON "ExposureBreach"("timestamp"); + +-- CreateIndex +CREATE INDEX "WalletTenantAssociation_walletAddress_tenantId_idx" ON "WalletTenantAssociation"("walletAddress", "tenantId"); + +-- CreateIndex +CREATE INDEX "WalletTenantAssociation_tenantId_idx" ON "WalletTenantAssociation"("tenantId"); + +-- CreateIndex +CREATE INDEX "Transaction_vaultId_tenantId_timestamp_idx" ON "Transaction"("vaultId", "tenantId", "timestamp" DESC); + diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 13ba1fab8..20e8c2e41 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -40,13 +40,17 @@ model SharePriceSnapshot { } model Transaction { - id String @id @default(uuid()) - user String - amount String - type String - status String? - referralCode String? - timestamp DateTime @default(now()) + id String @id @default(uuid()) + user String + amount String + type String + status String? + referralCode String? + vaultId String? + tenantId String? + latencyMs Int? + failureReason String? + timestamp DateTime @default(now()) // Issue #895 – query optimisation indices // Ordered by timestamp DESC to match the default sort on the list endpoint. @@ -56,6 +60,7 @@ model Transaction { @@index([status, timestamp(sort: Desc)]) @@index([user, type, timestamp(sort: Desc)]) @@index([user, status, timestamp(sort: Desc)]) + @@index([vaultId, tenantId, timestamp(sort: Desc)]) } model Referral { @@ -605,6 +610,71 @@ model WriteAheadAuditEntry { @@index([actor]) } +// ─── Vault Strategy Allocation & Exposure Guardrails ───────────────────────── + +model Vault { + id String @id @default(uuid()) + tenantId String + aum Float @default(0) + tvlUsd String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime? + + allocations Allocation[] + + @@index([tenantId]) + @@index([deletedAt]) +} + +model Strategy { + id String @id @default(uuid()) + name String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + allocations Allocation[] +} + +model Allocation { + id String @id @default(uuid()) + vaultId String + strategyId String + amount Float @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + vault Vault @relation(fields: [vaultId], references: [id], onDelete: Cascade) + strategy Strategy @relation(fields: [strategyId], references: [id], onDelete: Cascade) + + @@index([vaultId]) + @@index([strategyId]) +} + +model ExposureBreach { + id String @id @default(uuid()) + vaultId String + strategyId String + attemptedAmount String + reason String + timestamp DateTime @default(now()) + + @@index([vaultId]) + @@index([strategyId]) + @@index([timestamp]) +} + +// ─── Tenant Boundary ───────────────────────────────────────────────────────── + +model WalletTenantAssociation { + id String @id @default(uuid()) + walletAddress String + tenantId String + createdAt DateTime @default(now()) + deletedAt DateTime? + + @@index([walletAddress, tenantId]) + @@index([tenantId]) model IdempotencyKey { id String @id @default(uuid()) keyId String