New packages: - @lab/core: Resource types, Output<T> (Pulumi), audit event types, auth types, environment/account types, resource kind registry New Prisma schema (mcpctl pattern): - User (email/password/bcrypt), Session (bearer tokens), Group, GroupMember - ServiceAccount, RbacDefinition (JSON subjects + roleBindings) - AuditEvent (correlation IDs, causal chains, fire-and-forget batching) - Environment, Account (driver config, Infisical secret path), Binding - Resource (generic, kind/name/env unique, origin/managedBy tracking) - Secret, Fleet, FleetMember, GitSource - Keeps v1.0 models: Server, Agent, Bastion, Cluster, JoinToken New services: - AuthService: bearer token login, bootstrap (first login creates admin), session management with 30-day expiry - RbacService: environment-scoped permission checks, group membership, role hierarchy (admin > edit > view) - AuditService: fire-and-forget event collection, batch 50 / flush 5s, correlation IDs for causal chains - ResourceStore: CRUD with origin/managedBy, RBAC-enforced routes New routes: - POST /api/auth/login, POST /api/auth/logout (bearer token auth) - GET/POST/PUT/DELETE /api/resources (RBAC-enforced CRUD) - GET/POST /api/environments, GET/POST /api/accounts - POST /api/accounts/bind, GET /api/bindings - GET /api/events (audit query with --last, --kind, --env, --correlation) New middleware: - Bearer token auth (validates Authorization header, resolves user identity) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
292 lines
8.1 KiB
Plaintext
292 lines
8.1 KiB
Plaintext
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "cockroachdb"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
// ── Auth (mcpctl pattern: email/password + bearer token sessions) ──
|
|
|
|
model User {
|
|
id String @id @default(cuid())
|
|
email String @unique
|
|
password String // bcrypt
|
|
name String?
|
|
role UserRole @default(USER)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
sessions Session[]
|
|
auditLogs AuditEvent[]
|
|
groups GroupMember[]
|
|
}
|
|
|
|
enum UserRole {
|
|
USER
|
|
ADMIN
|
|
}
|
|
|
|
model Session {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
token String @unique
|
|
expiresAt DateTime
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([userId])
|
|
@@index([token])
|
|
}
|
|
|
|
model Group {
|
|
id String @id @default(cuid())
|
|
name String @unique
|
|
description String?
|
|
createdAt DateTime @default(now())
|
|
members GroupMember[]
|
|
}
|
|
|
|
model GroupMember {
|
|
id String @id @default(cuid())
|
|
groupId String
|
|
group Group @relation(fields: [groupId], references: [id], onDelete: Cascade)
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([groupId, userId])
|
|
}
|
|
|
|
model ServiceAccount {
|
|
id String @id @default(cuid())
|
|
name String @unique
|
|
token String @unique
|
|
createdAt DateTime @default(now())
|
|
}
|
|
|
|
// ── RBAC (mcpctl pattern: named definitions with JSON subjects/bindings) ──
|
|
|
|
model RbacDefinition {
|
|
id String @id @default(cuid())
|
|
name String @unique
|
|
subjects Json // [{kind: "User"|"Group"|"ServiceAccount", name: string}]
|
|
roleBindings Json // [{role, resource, name?, environment?, action?}]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
// ── Audit (mcpctl pattern: fire-and-forget with correlation IDs) ──
|
|
|
|
model AuditEvent {
|
|
id String @id @default(cuid())
|
|
timestamp DateTime @default(now())
|
|
eventKind String
|
|
source String // cli | labd | agent | driver | fleet-controller | sync-controller
|
|
verified Boolean @default(false)
|
|
|
|
userId String?
|
|
user User? @relation(fields: [userId], references: [id])
|
|
userName String?
|
|
sessionId String?
|
|
environmentName String?
|
|
accountName String?
|
|
|
|
resourceKind String?
|
|
resourceName String?
|
|
|
|
correlationId String
|
|
parentEventId String?
|
|
|
|
details Json @default("{}")
|
|
result String // success | failure | denied | skipped
|
|
error String?
|
|
durationMs Int?
|
|
|
|
@@index([correlationId])
|
|
@@index([eventKind, timestamp])
|
|
@@index([environmentName, timestamp])
|
|
@@index([resourceKind, resourceName])
|
|
@@index([userId, timestamp])
|
|
}
|
|
|
|
// ── Core infrastructure ──
|
|
|
|
model Environment {
|
|
id String @id @default(cuid())
|
|
name String @unique
|
|
status String @default("active") // active | archived
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
bindings Binding[]
|
|
resources Resource[]
|
|
}
|
|
|
|
model Account {
|
|
id String @id @default(cuid())
|
|
name String @unique
|
|
driver String // baremetal-pxe | aws | gcp | kubernetes | ovh
|
|
config Json @default("{}")
|
|
// Credentials stored in Infisical, referenced by secretPath
|
|
secretPath String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
bindings Binding[]
|
|
resources Resource[]
|
|
}
|
|
|
|
model Binding {
|
|
id String @id @default(cuid())
|
|
environmentId String
|
|
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
|
|
accountId String
|
|
account Account @relation(fields: [accountId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([environmentId, accountId])
|
|
}
|
|
|
|
model Resource {
|
|
id String @id @default(cuid())
|
|
kind String
|
|
name String
|
|
environmentId String
|
|
environment Environment @relation(fields: [environmentId], references: [id])
|
|
accountId String
|
|
account Account @relation(fields: [accountId], references: [id])
|
|
origin String @default("cli") // file | cli | fleet | imported
|
|
managedBy String @default("manual") // gitops | manual | auto
|
|
sourceRef String?
|
|
desiredSpec Json @default("{}")
|
|
actualSpec Json?
|
|
platformRef String?
|
|
status String @default("pending") // pending | creating | ready | updating | deleting | error
|
|
statusMessage String?
|
|
lastReconciled DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([kind, name, environmentId])
|
|
@@index([environmentId])
|
|
@@index([accountId])
|
|
@@index([kind, status])
|
|
}
|
|
|
|
model Secret {
|
|
id String @id @default(cuid())
|
|
name String @unique
|
|
// Encrypted data — application-layer encryption as fallback if Infisical unavailable
|
|
data Json @default("{}")
|
|
version Int @default(1)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
// ── Fleet ──
|
|
|
|
model Fleet {
|
|
id String @id @default(cuid())
|
|
name String
|
|
environmentId String
|
|
accountId String
|
|
selector Json // fact-matching rules
|
|
onboardPipeline Json // step definitions
|
|
offboardPipeline Json?
|
|
approvalConfig Json?
|
|
status String @default("active")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
members FleetMember[]
|
|
}
|
|
|
|
model FleetMember {
|
|
id String @id @default(cuid())
|
|
fleetId String
|
|
fleet Fleet @relation(fields: [fleetId], references: [id], onDelete: Cascade)
|
|
serverId String
|
|
status String // discovered | pending | onboarding | active | offboarding | removed
|
|
joinedAt DateTime @default(now())
|
|
|
|
@@index([fleetId])
|
|
}
|
|
|
|
// ── Git sources (for sync controller) ──
|
|
|
|
model GitSource {
|
|
id String @id @default(cuid())
|
|
name String @unique
|
|
repo String
|
|
branch String @default("main")
|
|
path String @default("environments/")
|
|
lastSync DateTime?
|
|
createdAt DateTime @default(now())
|
|
}
|
|
|
|
// ── Existing v1.0 models (kept for bastion/agent compatibility) ──
|
|
|
|
model Server {
|
|
id String @id @default(uuid())
|
|
hostname String @unique
|
|
mac String? @unique
|
|
cloud String @default("baremetal")
|
|
environment String @default("default")
|
|
role String @default("worker")
|
|
labels Json @default("{}")
|
|
ip String?
|
|
agentVersion String?
|
|
status String @default("unknown")
|
|
lastHeartbeat DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
agent Agent?
|
|
}
|
|
|
|
model Agent {
|
|
id String @id @default(uuid())
|
|
serverId String @unique
|
|
server Server @relation(fields: [serverId], references: [id], onDelete: Cascade)
|
|
certificatePem String?
|
|
enrolledAt DateTime @default(now())
|
|
lastSeen DateTime?
|
|
facts Json? // hardware facts reported by agent
|
|
|
|
@@index([serverId])
|
|
}
|
|
|
|
model JoinToken {
|
|
id String @id @default(uuid())
|
|
token String @unique
|
|
type String @default("one-time")
|
|
label String?
|
|
usedBy String?
|
|
usedAt DateTime?
|
|
revokedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
expiresAt DateTime?
|
|
}
|
|
|
|
model Bastion {
|
|
id String @id @default(uuid())
|
|
hostname String @unique
|
|
network String
|
|
serverIp String
|
|
status String @default("offline")
|
|
lastHeartbeat DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
model Cluster {
|
|
id String @id @default(uuid())
|
|
name String @unique
|
|
cloud String @default("baremetal")
|
|
environment String @default("default")
|
|
kubeconfigEnc String?
|
|
labels Json @default("{}")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|