@derwinjs/db 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/prisma/schema.prisma +62 -3
- package/prisma-client/edge.js +5 -3
- package/prisma-client/index.js +5 -3
- package/prisma-client/package.json +1 -1
- package/prisma-client/schema.prisma +62 -3
- package/prisma/migrations/20260501165631_init/migration.sql +0 -407
- package/prisma/migrations/20260503051425_0002_qap018b_qaticket_crosslink_fields/migration.sql +0 -6
- package/prisma/migrations/20260504231316_add_project_repofullname_and_webhooksecret/migration.sql +0 -12
- package/prisma/migrations/20260504232851_add_qaticket_resolvedby/migration.sql +0 -2
- package/prisma/migrations/20260505042646_add_qapattern_qarevert/migration.sql +0 -77
- package/prisma/migrations/20260505055826_add_qauniformity_and_agent_trigger/migration.sql +0 -35
package/prisma-client/index.js
CHANGED
|
@@ -582,7 +582,9 @@ const config = {
|
|
|
582
582
|
"value": "linux-arm64-openssl-3.0.x"
|
|
583
583
|
}
|
|
584
584
|
],
|
|
585
|
-
"previewFeatures": [
|
|
585
|
+
"previewFeatures": [
|
|
586
|
+
"multiSchema"
|
|
587
|
+
],
|
|
586
588
|
"sourceFilePath": "/home/runner/work/derwin/derwin/packages/db/prisma/schema.prisma",
|
|
587
589
|
"isCustomOutput": true
|
|
588
590
|
},
|
|
@@ -606,8 +608,8 @@ const config = {
|
|
|
606
608
|
}
|
|
607
609
|
}
|
|
608
610
|
},
|
|
609
|
-
"inlineSchema": "// Derwin platform schema — Postgres + Prisma.\n//\n// 14 models, project-namespaced. Per ADR-0005 (Prisma + Postgres + project-\n// namespaced schema). Reference: technical spec §3.\n//\n// Multi-tenancy convention: every project-scoped table has a `projectId String`\n// FK to Project. Cross-project leakage is the leading regression risk; queries\n// MUST filter on projectId. Per ADR-0005, the trust-mitigation gates are:\n// 1. A lint or static check that flags Prisma queries missing projectId\n// filters on tables that have one (added in Sprint 8 / risk-tier work).\n// 2. A multi-project broken-fixture E2E that asserts isolation\n// (added in Sprint 13 / QAP-130).\n//\n// pgvector convention: `embedding Bytes?` on RAGCorpus is the v1 storage type.\n// QAP-076 (Sprint 7 — RAG corpus add) switches it to a pgvector `vector(1536)`\n// column once the extension is enabled in QAP-036.\n\n// Generator output is INSIDE the package so the generated client ships\n// in the published tarball alongside dist/. Consumer code does\n// `import { PrismaClient } from '@derwinjs/db'` (or '@derwinjs/db/client') —\n// see packages/db/src/index.ts re-exports. This is required for\n// multi-tenant correctness: each Derwin consumer (Lifeline, Side Piece,\n// Bolt, ...) already has its own @prisma/client generated against its\n// own schema; @derwinjs/db can't piggyback on the consumer's @prisma/client\n// or types break (Lifeline's PrismaClient doesn't have qAUniformity etc).\n//\n// binaryTargets ships explicit cross-platform binaries so the published\n// tarball works regardless of where the publish workflow ran:\n// - darwin-arm64: Apple Silicon Mac dev (the Derwin team's primary local)\n// - rhel-openssl-3.0.x: x86 Linux on OpenSSL 3 (Vercel default, most CI)\n// - linux-arm64-openssl-3.0.x: ARM Linux (AWS Graviton, etc.)\n// \"native\" is intentionally omitted — relying on it makes the published\n// tarball platform-dependent (whatever the publisher was running on).\n// Intel Mac users (rare) add `darwin` locally via a re-generate.\ngenerator client {\n provider = \"prisma-client-js\"\n output = \"../prisma-client\"\n binaryTargets = [\"darwin-arm64\", \"rhel-openssl-3.0.x\", \"linux-arm64-openssl-3.0.x\"]\n}\n\ndatasource db {\n provider = \"postgresql\"\n url = env(\"DATABASE_URL\")\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Multi-tenant root\n// ═══════════════════════════════════════════════════════════════════════════\n\nmodel Project {\n id String @id @default(cuid())\n name String\n slug String @unique\n type ProjectType\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n // Operating mode (Observe / TicketOnly / Author / Auto) — see product brief §11.2\n mode ProjectMode @default(OBSERVE)\n modeChangedAt DateTime @default(now())\n modeChangedBy String?\n\n // Per-project quotas + budgets\n monthlyBudgetCents Int @default(10000) // $100 default\n dailyDispatchLimit Int @default(50)\n\n // GitHub integration (QAP-019C / Group D-1, 2026-05-04).\n //\n // `repoFullName` is the canonical \"<owner>/<repo>\" identifier (e.g.,\n // \"RoryLYN/derwin\"). The github-webhook route uses it to resolve a\n // payload's `repository.full_name` → projectId. Optional + unique\n // because: not every Derwin project has a GitHub repo (Sprint 4+ may\n // onboard products that don't run on GitHub); but a given repo can map\n // to at most one Derwin project (otherwise webhook payloads would be\n // ambiguous).\n //\n // `webhookSecret` is the per-project HMAC-SHA256 secret GitHub uses to\n // sign webhook payloads. Optional so projects without GitHub integration\n // carry no secret. The webhook route passes this to\n // `QAAuthAdapter.verifyWebhook(headers, body, secret)` to authenticate\n // payloads before any DB writes.\n repoFullName String? @unique\n webhookSecret String?\n\n // Relations\n profile ProjectProfile?\n runs QARun[]\n tickets QATicket[]\n fixAttempts QAFixAttempt[]\n auditArtifacts AuditArtifact[]\n policies Policy[]\n trustScores ClassificationTrust[]\n modeLog ProjectModeLog[]\n patterns QAPattern[]\n reverts QARevert[]\n uniformities QAUniformity[]\n\n @@map(\"projects\")\n}\n\nenum ProjectType {\n SAAS\n ECOMMERCE\n MARKETING\n ERP\n ANALYTICS\n INFRA\n CONTENT\n}\n\nenum ProjectMode {\n OBSERVE // run but write nothing\n TICKET_ONLY // write tickets, no fix authoring\n AUTHOR // tickets + fix authoring, all to PR\n AUTO // full autonomy per risk tier policies\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Layer A: Project Profile (the Knowledge Base)\n// ═══════════════════════════════════════════════════════════════════════════\n\nmodel ProjectProfile {\n id String @id @default(cuid())\n projectId String @unique\n type ProjectType\n\n domainOntology Json // entities[], actions[], roles[]\n riskProfile Json // 8-vector weights across surface categories\n criticalFlows Json // named user journeys with steps + assertions\n glossary Json // term → definition; disambiguates \"ticket\"/\"tenant\"/etc.\n dependencies Json // third-party services that need integration testing\n complianceTags String[] @default([])\n\n ingestedDocsHash String // hash of (doc, version) tuples for change detection\n\n lastIngestedAt DateTime\n lastEvolvedAt DateTime\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n evolutionLog ProfileEvolutionLog[]\n\n @@map(\"project_profiles\")\n}\n\nmodel IngestedDoc {\n id String @id @default(cuid())\n projectId String\n source String // \"filesystem\" | \"notion\" | \"google-docs\" | etc.\n sourcePath String // canonical path/URL of the doc\n contentHash String\n contentSize Int\n ingestedAt DateTime @default(now())\n ontologyDelta Json? // what this doc contributed to the Profile\n\n @@unique([projectId, sourcePath, contentHash])\n @@index([projectId, sourcePath])\n @@map(\"ingested_docs\")\n}\n\nmodel ProfileEvolutionLog {\n id String @id @default(cuid())\n profileId String\n triggerType String // \"FIX_OUTCOME\" | \"SPRINT_INGEST\" | \"DEPLOY\" | \"OPERATOR_OVERRIDE\"\n delta Json // what changed\n rationale String @db.Text\n appliedAt DateTime @default(now())\n appliedBy String // \"agent\" | userId\n\n profile ProjectProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)\n\n @@index([profileId, appliedAt(sort: Desc)])\n @@map(\"profile_evolution_log\")\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Layer B/C: Discovery + Execution\n// ═══════════════════════════════════════════════════════════════════════════\n\nmodel QARun {\n id String @id @default(cuid())\n projectId String\n triggeredBy String // \"cron\" | \"operator:<userId>\" | \"webhook\" | \"<routeName>\"\n triggerType RunTrigger\n scope Json // what surfaces, which routes, which subset\n startedAt DateTime @default(now())\n completedAt DateTime?\n status RunStatus @default(IN_PROGRESS)\n\n // Outcome counts (denormalized for dashboard reads)\n signalsRaised Int @default(0)\n ticketsCreated Int @default(0)\n attemptsLaunched Int @default(0)\n\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n signals RawSignal[]\n tickets QATicket[]\n reverts QARevert[]\n uniformities QAUniformity[]\n\n @@index([projectId, startedAt(sort: Desc)])\n @@map(\"qa_runs\")\n}\n\nenum RunTrigger {\n CRON\n OPERATOR_ON_DEMAND\n WEBHOOK\n CONTINUOUS // for routes that run as a daemon (telemetry mining etc.)\n AGENT // QAP-019F (Group D-3): standalone QA agent (browser tool) ingestion\n}\n\nenum RunStatus {\n IN_PROGRESS\n COMPLETED\n FAILED\n CANCELLED\n}\n\n// A RawSignal is what a Discovery route emits. It hasn't been investigated /\n// classified / ticketed yet — Triage takes signals and produces tickets.\nmodel RawSignal {\n id String @id @default(cuid())\n qaRunId String\n routeName String // which Discovery route fired\n signalType String // \"test_failure\" | \"telemetry_anomaly\" | \"rage_click_cluster\" | etc.\n rawData Json // route-specific payload (test results, metric snapshot, etc.)\n rawArtifactRefs Json // pointers to S3 artifacts (videos, screenshots)\n\n // Investigation + classification happen later; null until Triage processes.\n qaTicketId String? @unique\n investigatedAt DateTime?\n\n capturedAt DateTime @default(now())\n\n qaRun QARun @relation(fields: [qaRunId], references: [id], onDelete: Cascade)\n ticket QATicket? @relation(fields: [qaTicketId], references: [id])\n\n @@index([qaRunId])\n @@index([routeName, capturedAt(sort: Desc)])\n @@map(\"raw_signals\")\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Layer D: Triage — the structured QA ticket\n// ═══════════════════════════════════════════════════════════════════════════\n\nmodel QATicket {\n id String @id @default(cuid())\n projectId String\n qaRunId String\n\n // Identity / linkage\n externalRef String? // if mirrored to Linear/Jira/Lifeline-native, the ID there (single primary cross-system ref; legacy from v0.4)\n externalSystem String? // \"linear\" | \"jira\" | \"github-issues\" | \"lifeline-native\"\n originatingTicketRef String? // the FEATURE ticket that introduced the affected code, if known\n\n // Cross-link stubs posted to consumer ticket systems per ADR-0008 (architecture\n // pivot 2026-05-01). Array of { system: 'github', repo: string, issueNumber: number,\n // url: string, state: 'open' | 'closed' }. Multiple stubs supported (e.g., post\n // to both GitHub Issues and Linear). QAP-018 (Sprint 2) populates this on\n // createQATicket via the LifelineTicketAdapter.\n crossLinkRefs Json @default(\"[]\")\n\n // Canonical deep-link URL for this ticket in the Derwin-owned dashboard per\n // ADR-0008. Populated by QAP-018B's createQATicket from DERWIN_DASHBOARD_URL\n // env + ticket id. The cross-link stubs in crossLinkRefs[] embed this URL in\n // their bodies so operators triaging in the consumer ticket system can click\n // through to the rich Derwin record.\n dashboardUrl String?\n\n // Classification\n surface SurfaceCategory\n classification String // SELECTOR | PRODUCT_BUG | TIMING | etc., extensible\n severity Severity\n riskTier RiskTier\n status TicketStatus @default(OPEN)\n\n // Content (from the structured ticket format in product brief §6.3)\n title String\n reproSteps Json // array of step objects\n suspectedRootCause String @db.Text\n blastRadius Json // affected files / tests / pages\n\n // Author/dispatch decision\n proposedFixApproach String @db.Text\n autoMergeEligible Boolean @default(false)\n autoMergeRationale String? @db.Text\n\n // Final bucket assignment (populated after lifecycle completes)\n finalBucket ReviewBucket?\n bucketReason String? @db.Text\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n // Optional audit field — operator userId or system actor name (e.g.,\n // \"qa-auto-fix-webhook\") that resolved this ticket. Set on status\n // transitions to RESOLVED, REJECTED, or REGRESSED_REVERTED. QAP-019C\n // (Group D-1, 2026-05-04). Mirrors Lifeline's QARecommendation.resolvedBy\n // column. Optional because not every status transition carries a\n // resolution actor.\n resolvedBy String?\n closedAt DateTime?\n\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n qaRun QARun @relation(fields: [qaRunId], references: [id], onDelete: Cascade)\n signals RawSignal[]\n attempts QAFixAttempt[]\n artifacts AuditArtifact[]\n reverts QARevert[]\n\n @@index([projectId, status])\n @@index([projectId, finalBucket])\n @@index([projectId, createdAt(sort: Desc)])\n @@map(\"qa_tickets\")\n}\n\nenum SurfaceCategory {\n CODE_HEALTH\n FUNCTIONAL_CORRECTNESS\n UI_UX_INTEGRITY\n PERF_RESILIENCE\n SECURITY\n COMPLIANCE_AUDIT\n MULTI_TENANT_SAFETY\n OPERATIONAL_HEALTH\n}\n\nenum Severity {\n CRITICAL\n HIGH\n MEDIUM\n LOW\n}\n\nenum RiskTier {\n LOW\n MEDIUM\n HIGH\n NEVER\n}\n\nenum TicketStatus {\n OPEN\n INVESTIGATING\n AUTHORING\n PR_OPEN\n MERGED\n REGRESSED_REVERTED\n CLOSED_WONTFIX\n CLOSED_RESOLVED\n ESCALATED\n}\n\nenum ReviewBucket {\n PASS\n FAIL\n ESCALATION\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Layer E: Auto-fix attempts\n// ═══════════════════════════════════════════════════════════════════════════\n\nmodel QAFixAttempt {\n id String @id @default(cuid())\n qaTicketId String\n projectId String\n attemptNumber Int // 1..N for iterative retry\n\n // Prompt + diff\n promptText String @db.Text\n diff String? @db.Text\n diffSizeBytes Int?\n modelName String?\n modelVersion String?\n inputTokens Int?\n outputTokens Int?\n costCents Int?\n\n // Pre-merge verification\n preVerifyResult Json? // sandbox run results\n preVerifyPassed Boolean?\n\n // Dispatch outcome\n branchName String?\n prUrl String?\n prNumber Int?\n dispatchStatus AttemptStatus @default(AUTHORING)\n\n // Post-merge verification (set after merge)\n mergedAt DateTime?\n postVerifyResult Json?\n regressionDetected Boolean @default(false)\n autoRevertedAt DateTime?\n humanEditLines Int?\n fixSuccessScore Float? // -1.0 to 1.0\n\n attemptedAt DateTime @default(now())\n closedAt DateTime?\n\n ticket QATicket @relation(fields: [qaTicketId], references: [id], onDelete: Cascade)\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n artifacts AuditArtifact[]\n\n @@index([qaTicketId, attemptNumber])\n @@index([projectId, attemptedAt(sort: Desc)])\n @@map(\"qa_fix_attempts\")\n}\n\nenum AttemptStatus {\n AUTHORING\n PRE_VERIFY_FAILED\n PRE_VERIFY_PASSED\n PR_OPENED\n AUTO_MERGED\n HUMAN_MERGED\n REJECTED\n REGRESSED_REVERTED\n FAILED\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Layer F: Trust scores + drift + RAG corpus\n// ═══════════════════════════════════════════════════════════════════════════\n\nmodel ClassificationTrust {\n id String @id @default(cuid())\n projectId String\n classification String // matches QATicket.classification\n surface SurfaceCategory\n\n // Rolling stats (30-day window)\n attemptsLast30d Int @default(0)\n mergedClean Int @default(0)\n mergedWithEdits Int @default(0)\n regressed Int @default(0)\n reverted Int @default(0)\n\n // Computed\n successScore Float @default(0) // -1.0 to 1.0\n trustPercent Int @default(0) // 0 to 100, operator-facing\n autoMergeEligible Boolean @default(false)\n\n lastComputedAt DateTime @default(now())\n\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n\n @@unique([projectId, classification, surface])\n @@map(\"classification_trust\")\n}\n\n// Successful past fixes used as in-context examples for new dispatches.\n// Embedding column is Bytes for v1; QAP-076 (Sprint 7) switches to pgvector.\nmodel RAGCorpus {\n id String @id @default(cuid())\n projectId String\n classification String\n surface SurfaceCategory\n\n promptText String @db.Text // the prompt that produced the fix\n diff String @db.Text // the unified diff that worked\n embedding Bytes? // for semantic search\n\n fixAttemptId String // back-reference to the source attempt\n outcomeQuality Float // priority weight; declines if later usage shows problems\n addedAt DateTime @default(now())\n\n @@index([projectId, classification, surface])\n @@map(\"rag_corpus\")\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Audit artifacts — proof-of-work, not \"trust me\"\n// ═══════════════════════════════════════════════════════════════════════════\n\nmodel AuditArtifact {\n id String @id @default(cuid())\n projectId String\n qaTicketId String?\n qaFixAttemptId String?\n\n artifactType ArtifactType\n stage ArtifactStage // which lifecycle stage produced this\n\n // Storage references (S3-style)\n storageBackend String // \"s3\" | \"supabase-storage\" | \"filesystem\"\n storageKey String // canonical key/path\n contentType String\n sizeBytes Int\n contentHash String\n\n // Metadata\n meta Json // route-specific (e.g., screenshot timestamp, video duration)\n retentionUntil DateTime? // computed from project compliance mode\n\n capturedAt DateTime @default(now())\n\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n ticket QATicket? @relation(fields: [qaTicketId], references: [id])\n fixAttempt QAFixAttempt? @relation(fields: [qaFixAttemptId], references: [id])\n\n @@index([qaTicketId])\n @@index([projectId, capturedAt(sort: Desc)])\n @@map(\"audit_artifacts\")\n}\n\nenum ArtifactType {\n SCREENSHOT\n VIDEO\n DOM_SNAPSHOT\n CONSOLE_LOG\n NETWORK_LOG\n TELEMETRY_SLICE\n CODE_SNAPSHOT\n PROMPT_TEXT\n DIFF\n REASONING_TRACE\n REVIEWER_OUTPUT\n REPLAY_BUNDLE\n}\n\nenum ArtifactStage {\n DETECTION\n INVESTIGATION\n TICKET_CREATION\n AUTHORING\n PRE_MERGE_VERIFICATION\n POST_MERGE_VERIFICATION\n ARCHIVAL\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Policies + safety (Sprint 8 — QAP-080..089 — fills these out)\n// ═══════════════════════════════════════════════════════════════════════════\n\nmodel Policy {\n id String @id @default(cuid())\n projectId String\n policyType PolicyType\n\n // Path tier rules: globs → tier\n pathRules Json // [{ glob: \"src/auth/**\", tier: \"HIGH\" }, …]\n classificationOverrides Json // { \"PRODUCT_BUG\": \"MEDIUM\", … }\n\n // Trust thresholds\n autoMergeTrustThreshold Int @default(70)\n autoMergeMediumThreshold Int @default(85)\n\n // Concurrency\n dailyDispatchLimit Int @default(50)\n // Fix-attempt retry limit per ticket. Default 1 per ADR-0006 — try once,\n // route to FAIL on failure (conservative trust posture). Configurable per\n // project; recommended range 1–4. Higher values raise auto-fix throughput\n // on iterative-success classifications at the cost of Anthropic spend and\n // delayed operator awareness of classifier weakness.\n perTicketAttemptLimit Int @default(1)\n\n // Time-of-day\n freezeWindowsCron String[] @default([])\n\n // Escalation triggers\n escalationTriggers Json\n\n updatedAt DateTime @updatedAt\n updatedBy String\n\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n\n @@index([projectId])\n @@map(\"policies\")\n}\n\nenum PolicyType {\n RISK_TIER\n CONCURRENCY\n TRUST_THRESHOLD\n ESCALATION\n}\n\nmodel ProjectModeLog {\n id String @id @default(cuid())\n projectId String\n fromMode ProjectMode\n toMode ProjectMode\n reason String @db.Text\n changedBy String // \"auto-promotion\" | userId\n changedAt DateTime @default(now())\n\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n\n @@index([projectId, changedAt(sort: Desc)])\n @@map(\"project_mode_log\")\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Cost + telemetry\n// ═══════════════════════════════════════════════════════════════════════════\n\nmodel SpendLedger {\n id String @id @default(cuid())\n projectId String\n qaFixAttemptId String?\n\n // Per-event spend\n vendor String // \"anthropic\" | \"openai\" | \"github-actions\" | \"supabase-storage\"\n eventType String // \"claude_dispatch\" | \"claude_review\" | \"embedding\" | \"storage_write\"\n costCents Int // signed; can be credit\n meta Json\n\n occurredAt DateTime @default(now())\n\n @@index([projectId, occurredAt(sort: Desc)])\n @@index([projectId, vendor, occurredAt])\n @@map(\"spend_ledger\")\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Layer F: Learning — patterns + reverts (QAP-019D + QAP-019E, Group D-2)\n// ═══════════════════════════════════════════════════════════════════════════\n\n// QAPattern is a learned failure signature — the orchestrator's Triage stage\n// (Sprint 6) bumps occurrenceCount + lastSeenAt when a new RawSignal matches\n// a known signature. Lifeline-shape preserved verbatim per Group D-2 plan\n// decision #1: re-ranker fields (fixSuccessScore / fixOutcomeSampleSize /\n// lastWeightedAt) stay even though Derwin's re-ranker today reads\n// classification-trust-store. Preserves optionality for Sprint 4+ consumers\n// and removes shim translation work in QAP-019G.\n//\n// (signature, projectId) is unique — the same failure signature in different\n// projects is two distinct rows. Lifeline-style.\nmodel QAPattern {\n id String @id @default(cuid())\n projectId String\n signature String\n classification QAFailureClass\n firstSeenAt DateTime @default(now())\n lastSeenAt DateTime @default(now())\n occurrenceCount Int @default(1)\n affectedTickets String[] @default([])\n trendDirection String @default(\"stable\") // improving | stable | degrading\n status QAPatternStatus @default(OPEN)\n resolvedAt DateTime?\n promotedToLintAt DateTime?\n promotedLintPR String?\n archivedAt DateTime?\n\n // Re-ranker fields (Lifeline-shape preserved, Decision 1).\n fixSuccessScore Float?\n fixOutcomeSampleSize Int @default(0)\n lastWeightedAt DateTime?\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n\n @@unique([projectId, signature])\n @@index([projectId, lastSeenAt(sort: Desc)])\n @@index([status])\n @@map(\"qa_patterns\")\n}\n\nenum QAPatternStatus {\n OPEN\n RESOLVED\n ARCHIVED\n}\n\nenum QAFailureClass {\n SELECTOR\n TIMING\n TEST_DATA\n PRODUCT_BUG\n SCHEMA_DRIFT\n UNKNOWN\n}\n\n// QARevert logs a manual or auto-revert of a fix-forward commit. The route\n// handler does NOT execute `git revert` — it only records the operator's\n// action. Restore is the inverse: a fix-forward commit lands and the operator\n// flips restoredAt + restoredBy + restoredBySha.\n//\n// Decision 2: keep qaRunId FK only. Lifeline's qaResultId pointed at the\n// QARunResult table, which Derwin doesn't have under Option β (results are\n// RawSignal records). Reverts are at the ticket+commit level, not the\n// per-test-result level.\nmodel QARevert {\n id String @id @default(cuid())\n projectId String\n ticketId String\n revertedFromSha String\n revertedToSha String?\n revertedAt DateTime @default(now())\n revertedBy String\n reason String @db.Text\n qaRunId String?\n\n // Restore audit trail — set when the fix-forward commit ships.\n restoredAt DateTime?\n restoredBy String?\n restoredBySha String?\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n ticket QATicket @relation(fields: [ticketId], references: [id], onDelete: Cascade)\n run QARun? @relation(fields: [qaRunId], references: [id], onDelete: SetNull)\n\n @@index([projectId, revertedAt(sort: Desc)])\n @@index([ticketId])\n @@map(\"qa_reverts\")\n}\n\n// QAUniformity stores per-page rule outcomes from the invariant crawler\n// (uniformity audit). Lifeline calls this `QAUniformityAudit`; renamed to\n// `QAUniformity` per Group D-3 plan Decision 5 (drop `Audit` suffix to match\n// other Derwin model names). Bulk-inserted by /api/qa/uniformity POST and\n// aggregated by GET. Optionally tied to a QARun (most ingestions are run-\n// scoped; manual operator runs may not have a qaRunId).\nmodel QAUniformity {\n id String @id @default(cuid())\n projectId String\n qaRunId String?\n pagePath String\n ruleName String\n ruleCategory String\n status QAUniformityStatus\n violationDetail String? @db.Text\n createdAt DateTime @default(now())\n\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n run QARun? @relation(fields: [qaRunId], references: [id], onDelete: SetNull)\n\n @@index([projectId, createdAt(sort: Desc)])\n @@index([projectId, ruleName, status])\n @@index([projectId, pagePath])\n @@map(\"qa_uniformities\")\n}\n\nenum QAUniformityStatus {\n PASSED\n FAILED\n SKIPPED\n}\n",
|
|
610
|
-
"inlineSchemaHash": "
|
|
611
|
+
"inlineSchema": "// Derwin platform schema — Postgres + Prisma.\n//\n// 14 models, project-namespaced. Per ADR-0005 (Prisma + Postgres + project-\n// namespaced schema). Reference: technical spec §3.\n//\n// Multi-tenancy convention: every project-scoped table has a `projectId String`\n// FK to Project. Cross-project leakage is the leading regression risk; queries\n// MUST filter on projectId. Per ADR-0005, the trust-mitigation gates are:\n// 1. A lint or static check that flags Prisma queries missing projectId\n// filters on tables that have one (added in Sprint 8 / risk-tier work).\n// 2. A multi-project broken-fixture E2E that asserts isolation\n// (added in Sprint 13 / QAP-130).\n//\n// pgvector convention: `embedding Bytes?` on RAGCorpus is the v1 storage type.\n// QAP-076 (Sprint 7 — RAG corpus add) switches it to a pgvector `vector(1536)`\n// column once the extension is enabled in QAP-036.\n//\n// Multi-schema: every model + enum is in the `derwin` Postgres schema (not\n// `public`). This isolates Derwin's tables from each consumer's existing\n// schema — e.g., Lifeline's pre-extraction `public.qa_fix_attempts` stays\n// untouched while Derwin's tables live at `derwin.qa_fix_attempts`. Required\n// for the multi-tenant boundary rule: each consumer = ONE Project row, but\n// each consumer also has its own pre-existing schema in `public`. (Added in\n// QAP-019G when extracting Derwin into Lifeline first revealed the conflict.)\n\n// Generator output is INSIDE the package so the generated client ships\n// in the published tarball alongside dist/. Consumer code does\n// `import { PrismaClient } from '@derwinjs/db'` (or '@derwinjs/db/client') —\n// see packages/db/src/index.ts re-exports. This is required for\n// multi-tenant correctness: each Derwin consumer (Lifeline, Side Piece,\n// Bolt, ...) already has its own @prisma/client generated against its\n// own schema; @derwinjs/db can't piggyback on the consumer's @prisma/client\n// or types break (Lifeline's PrismaClient doesn't have qAUniformity etc).\n//\n// binaryTargets ships explicit cross-platform binaries so the published\n// tarball works regardless of where the publish workflow ran:\n// - darwin-arm64: Apple Silicon Mac dev (the Derwin team's primary local)\n// - rhel-openssl-3.0.x: x86 Linux on OpenSSL 3 (Vercel default, most CI)\n// - linux-arm64-openssl-3.0.x: ARM Linux (AWS Graviton, etc.)\n// \"native\" is intentionally omitted — relying on it makes the published\n// tarball platform-dependent (whatever the publisher was running on).\n// Intel Mac users (rare) add `darwin` locally via a re-generate.\ngenerator client {\n provider = \"prisma-client-js\"\n output = \"../prisma-client\"\n binaryTargets = [\"darwin-arm64\", \"rhel-openssl-3.0.x\", \"linux-arm64-openssl-3.0.x\"]\n previewFeatures = [\"multiSchema\"]\n}\n\ndatasource db {\n provider = \"postgresql\"\n url = env(\"DATABASE_URL\")\n schemas = [\"derwin\"]\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Multi-tenant root\n// ═══════════════════════════════════════════════════════════════════════════\n\nmodel Project {\n id String @id @default(cuid())\n name String\n slug String @unique\n type ProjectType\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n // Operating mode (Observe / TicketOnly / Author / Auto) — see product brief §11.2\n mode ProjectMode @default(OBSERVE)\n modeChangedAt DateTime @default(now())\n modeChangedBy String?\n\n // Per-project quotas + budgets\n monthlyBudgetCents Int @default(10000) // $100 default\n dailyDispatchLimit Int @default(50)\n\n // GitHub integration (QAP-019C / Group D-1, 2026-05-04).\n //\n // `repoFullName` is the canonical \"<owner>/<repo>\" identifier (e.g.,\n // \"RoryLYN/derwin\"). The github-webhook route uses it to resolve a\n // payload's `repository.full_name` → projectId. Optional + unique\n // because: not every Derwin project has a GitHub repo (Sprint 4+ may\n // onboard products that don't run on GitHub); but a given repo can map\n // to at most one Derwin project (otherwise webhook payloads would be\n // ambiguous).\n //\n // `webhookSecret` is the per-project HMAC-SHA256 secret GitHub uses to\n // sign webhook payloads. Optional so projects without GitHub integration\n // carry no secret. The webhook route passes this to\n // `QAAuthAdapter.verifyWebhook(headers, body, secret)` to authenticate\n // payloads before any DB writes.\n repoFullName String? @unique\n webhookSecret String?\n\n // Relations\n profile ProjectProfile?\n runs QARun[]\n tickets QATicket[]\n fixAttempts QAFixAttempt[]\n auditArtifacts AuditArtifact[]\n policies Policy[]\n trustScores ClassificationTrust[]\n modeLog ProjectModeLog[]\n patterns QAPattern[]\n reverts QARevert[]\n uniformities QAUniformity[]\n\n @@map(\"projects\")\n @@schema(\"derwin\")\n}\n\nenum ProjectType {\n SAAS\n ECOMMERCE\n MARKETING\n ERP\n ANALYTICS\n INFRA\n CONTENT\n\n @@schema(\"derwin\")\n}\n\nenum ProjectMode {\n OBSERVE // run but write nothing\n TICKET_ONLY // write tickets, no fix authoring\n AUTHOR // tickets + fix authoring, all to PR\n AUTO // full autonomy per risk tier policies\n\n @@schema(\"derwin\")\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Layer A: Project Profile (the Knowledge Base)\n// ═══════════════════════════════════════════════════════════════════════════\n\nmodel ProjectProfile {\n id String @id @default(cuid())\n projectId String @unique\n type ProjectType\n\n domainOntology Json // entities[], actions[], roles[]\n riskProfile Json // 8-vector weights across surface categories\n criticalFlows Json // named user journeys with steps + assertions\n glossary Json // term → definition; disambiguates \"ticket\"/\"tenant\"/etc.\n dependencies Json // third-party services that need integration testing\n complianceTags String[] @default([])\n\n ingestedDocsHash String // hash of (doc, version) tuples for change detection\n\n lastIngestedAt DateTime\n lastEvolvedAt DateTime\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n evolutionLog ProfileEvolutionLog[]\n\n @@map(\"project_profiles\")\n @@schema(\"derwin\")\n}\n\nmodel IngestedDoc {\n id String @id @default(cuid())\n projectId String\n source String // \"filesystem\" | \"notion\" | \"google-docs\" | etc.\n sourcePath String // canonical path/URL of the doc\n contentHash String\n contentSize Int\n ingestedAt DateTime @default(now())\n ontologyDelta Json? // what this doc contributed to the Profile\n\n @@unique([projectId, sourcePath, contentHash])\n @@index([projectId, sourcePath])\n @@map(\"ingested_docs\")\n @@schema(\"derwin\")\n}\n\nmodel ProfileEvolutionLog {\n id String @id @default(cuid())\n profileId String\n triggerType String // \"FIX_OUTCOME\" | \"SPRINT_INGEST\" | \"DEPLOY\" | \"OPERATOR_OVERRIDE\"\n delta Json // what changed\n rationale String @db.Text\n appliedAt DateTime @default(now())\n appliedBy String // \"agent\" | userId\n\n profile ProjectProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)\n\n @@index([profileId, appliedAt(sort: Desc)])\n @@map(\"profile_evolution_log\")\n @@schema(\"derwin\")\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Layer B/C: Discovery + Execution\n// ═══════════════════════════════════════════════════════════════════════════\n\nmodel QARun {\n id String @id @default(cuid())\n projectId String\n triggeredBy String // \"cron\" | \"operator:<userId>\" | \"webhook\" | \"<routeName>\"\n triggerType RunTrigger\n scope Json // what surfaces, which routes, which subset\n startedAt DateTime @default(now())\n completedAt DateTime?\n status RunStatus @default(IN_PROGRESS)\n\n // Outcome counts (denormalized for dashboard reads)\n signalsRaised Int @default(0)\n ticketsCreated Int @default(0)\n attemptsLaunched Int @default(0)\n\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n signals RawSignal[]\n tickets QATicket[]\n reverts QARevert[]\n uniformities QAUniformity[]\n\n @@index([projectId, startedAt(sort: Desc)])\n @@map(\"qa_runs\")\n @@schema(\"derwin\")\n}\n\nenum RunTrigger {\n CRON\n OPERATOR_ON_DEMAND\n WEBHOOK\n CONTINUOUS // for routes that run as a daemon (telemetry mining etc.)\n AGENT // QAP-019F (Group D-3): standalone QA agent (browser tool) ingestion\n\n @@schema(\"derwin\")\n}\n\nenum RunStatus {\n IN_PROGRESS\n COMPLETED\n FAILED\n CANCELLED\n\n @@schema(\"derwin\")\n}\n\n// A RawSignal is what a Discovery route emits. It hasn't been investigated /\n// classified / ticketed yet — Triage takes signals and produces tickets.\nmodel RawSignal {\n id String @id @default(cuid())\n qaRunId String\n routeName String // which Discovery route fired\n signalType String // \"test_failure\" | \"telemetry_anomaly\" | \"rage_click_cluster\" | etc.\n rawData Json // route-specific payload (test results, metric snapshot, etc.)\n rawArtifactRefs Json // pointers to S3 artifacts (videos, screenshots)\n\n // Investigation + classification happen later; null until Triage processes.\n qaTicketId String? @unique\n investigatedAt DateTime?\n\n capturedAt DateTime @default(now())\n\n qaRun QARun @relation(fields: [qaRunId], references: [id], onDelete: Cascade)\n ticket QATicket? @relation(fields: [qaTicketId], references: [id])\n\n @@index([qaRunId])\n @@index([routeName, capturedAt(sort: Desc)])\n @@map(\"raw_signals\")\n @@schema(\"derwin\")\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Layer D: Triage — the structured QA ticket\n// ═══════════════════════════════════════════════════════════════════════════\n\nmodel QATicket {\n id String @id @default(cuid())\n projectId String\n qaRunId String\n\n // Identity / linkage\n externalRef String? // if mirrored to Linear/Jira/Lifeline-native, the ID there (single primary cross-system ref; legacy from v0.4)\n externalSystem String? // \"linear\" | \"jira\" | \"github-issues\" | \"lifeline-native\"\n originatingTicketRef String? // the FEATURE ticket that introduced the affected code, if known\n\n // Cross-link stubs posted to consumer ticket systems per ADR-0008 (architecture\n // pivot 2026-05-01). Array of { system: 'github', repo: string, issueNumber: number,\n // url: string, state: 'open' | 'closed' }. Multiple stubs supported (e.g., post\n // to both GitHub Issues and Linear). QAP-018 (Sprint 2) populates this on\n // createQATicket via the LifelineTicketAdapter.\n crossLinkRefs Json @default(\"[]\")\n\n // Canonical deep-link URL for this ticket in the Derwin-owned dashboard per\n // ADR-0008. Populated by QAP-018B's createQATicket from DERWIN_DASHBOARD_URL\n // env + ticket id. The cross-link stubs in crossLinkRefs[] embed this URL in\n // their bodies so operators triaging in the consumer ticket system can click\n // through to the rich Derwin record.\n dashboardUrl String?\n\n // Classification\n surface SurfaceCategory\n classification String // SELECTOR | PRODUCT_BUG | TIMING | etc., extensible\n severity Severity\n riskTier RiskTier\n status TicketStatus @default(OPEN)\n\n // Content (from the structured ticket format in product brief §6.3)\n title String\n reproSteps Json // array of step objects\n suspectedRootCause String @db.Text\n blastRadius Json // affected files / tests / pages\n\n // Author/dispatch decision\n proposedFixApproach String @db.Text\n autoMergeEligible Boolean @default(false)\n autoMergeRationale String? @db.Text\n\n // Final bucket assignment (populated after lifecycle completes)\n finalBucket ReviewBucket?\n bucketReason String? @db.Text\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n // Optional audit field — operator userId or system actor name (e.g.,\n // \"qa-auto-fix-webhook\") that resolved this ticket. Set on status\n // transitions to RESOLVED, REJECTED, or REGRESSED_REVERTED. QAP-019C\n // (Group D-1, 2026-05-04). Mirrors Lifeline's QARecommendation.resolvedBy\n // column. Optional because not every status transition carries a\n // resolution actor.\n resolvedBy String?\n closedAt DateTime?\n\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n qaRun QARun @relation(fields: [qaRunId], references: [id], onDelete: Cascade)\n signals RawSignal[]\n attempts QAFixAttempt[]\n artifacts AuditArtifact[]\n reverts QARevert[]\n\n @@index([projectId, status])\n @@index([projectId, finalBucket])\n @@index([projectId, createdAt(sort: Desc)])\n @@map(\"qa_tickets\")\n @@schema(\"derwin\")\n}\n\nenum SurfaceCategory {\n CODE_HEALTH\n FUNCTIONAL_CORRECTNESS\n UI_UX_INTEGRITY\n PERF_RESILIENCE\n SECURITY\n COMPLIANCE_AUDIT\n MULTI_TENANT_SAFETY\n OPERATIONAL_HEALTH\n\n @@schema(\"derwin\")\n}\n\nenum Severity {\n CRITICAL\n HIGH\n MEDIUM\n LOW\n\n @@schema(\"derwin\")\n}\n\nenum RiskTier {\n LOW\n MEDIUM\n HIGH\n NEVER\n\n @@schema(\"derwin\")\n}\n\nenum TicketStatus {\n OPEN\n INVESTIGATING\n AUTHORING\n PR_OPEN\n MERGED\n REGRESSED_REVERTED\n CLOSED_WONTFIX\n CLOSED_RESOLVED\n ESCALATED\n\n @@schema(\"derwin\")\n}\n\nenum ReviewBucket {\n PASS\n FAIL\n ESCALATION\n\n @@schema(\"derwin\")\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Layer E: Auto-fix attempts\n// ═══════════════════════════════════════════════════════════════════════════\n\nmodel QAFixAttempt {\n id String @id @default(cuid())\n qaTicketId String\n projectId String\n attemptNumber Int // 1..N for iterative retry\n\n // Prompt + diff\n promptText String @db.Text\n diff String? @db.Text\n diffSizeBytes Int?\n modelName String?\n modelVersion String?\n inputTokens Int?\n outputTokens Int?\n costCents Int?\n\n // Pre-merge verification\n preVerifyResult Json? // sandbox run results\n preVerifyPassed Boolean?\n\n // Dispatch outcome\n branchName String?\n prUrl String?\n prNumber Int?\n dispatchStatus AttemptStatus @default(AUTHORING)\n\n // Post-merge verification (set after merge)\n mergedAt DateTime?\n postVerifyResult Json?\n regressionDetected Boolean @default(false)\n autoRevertedAt DateTime?\n humanEditLines Int?\n fixSuccessScore Float? // -1.0 to 1.0\n\n attemptedAt DateTime @default(now())\n closedAt DateTime?\n\n ticket QATicket @relation(fields: [qaTicketId], references: [id], onDelete: Cascade)\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n artifacts AuditArtifact[]\n\n @@index([qaTicketId, attemptNumber])\n @@index([projectId, attemptedAt(sort: Desc)])\n @@map(\"qa_fix_attempts\")\n @@schema(\"derwin\")\n}\n\nenum AttemptStatus {\n AUTHORING\n PRE_VERIFY_FAILED\n PRE_VERIFY_PASSED\n PR_OPENED\n AUTO_MERGED\n HUMAN_MERGED\n REJECTED\n REGRESSED_REVERTED\n FAILED\n\n @@schema(\"derwin\")\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Layer F: Trust scores + drift + RAG corpus\n// ═══════════════════════════════════════════════════════════════════════════\n\nmodel ClassificationTrust {\n id String @id @default(cuid())\n projectId String\n classification String // matches QATicket.classification\n surface SurfaceCategory\n\n // Rolling stats (30-day window)\n attemptsLast30d Int @default(0)\n mergedClean Int @default(0)\n mergedWithEdits Int @default(0)\n regressed Int @default(0)\n reverted Int @default(0)\n\n // Computed\n successScore Float @default(0) // -1.0 to 1.0\n trustPercent Int @default(0) // 0 to 100, operator-facing\n autoMergeEligible Boolean @default(false)\n\n lastComputedAt DateTime @default(now())\n\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n\n @@unique([projectId, classification, surface])\n @@map(\"classification_trust\")\n @@schema(\"derwin\")\n}\n\n// Successful past fixes used as in-context examples for new dispatches.\n// Embedding column is Bytes for v1; QAP-076 (Sprint 7) switches to pgvector.\nmodel RAGCorpus {\n id String @id @default(cuid())\n projectId String\n classification String\n surface SurfaceCategory\n\n promptText String @db.Text // the prompt that produced the fix\n diff String @db.Text // the unified diff that worked\n embedding Bytes? // for semantic search\n\n fixAttemptId String // back-reference to the source attempt\n outcomeQuality Float // priority weight; declines if later usage shows problems\n addedAt DateTime @default(now())\n\n @@index([projectId, classification, surface])\n @@map(\"rag_corpus\")\n @@schema(\"derwin\")\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Audit artifacts — proof-of-work, not \"trust me\"\n// ═══════════════════════════════════════════════════════════════════════════\n\nmodel AuditArtifact {\n id String @id @default(cuid())\n projectId String\n qaTicketId String?\n qaFixAttemptId String?\n\n artifactType ArtifactType\n stage ArtifactStage // which lifecycle stage produced this\n\n // Storage references (S3-style)\n storageBackend String // \"s3\" | \"supabase-storage\" | \"filesystem\"\n storageKey String // canonical key/path\n contentType String\n sizeBytes Int\n contentHash String\n\n // Metadata\n meta Json // route-specific (e.g., screenshot timestamp, video duration)\n retentionUntil DateTime? // computed from project compliance mode\n\n capturedAt DateTime @default(now())\n\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n ticket QATicket? @relation(fields: [qaTicketId], references: [id])\n fixAttempt QAFixAttempt? @relation(fields: [qaFixAttemptId], references: [id])\n\n @@index([qaTicketId])\n @@index([projectId, capturedAt(sort: Desc)])\n @@map(\"audit_artifacts\")\n @@schema(\"derwin\")\n}\n\nenum ArtifactType {\n SCREENSHOT\n VIDEO\n DOM_SNAPSHOT\n CONSOLE_LOG\n NETWORK_LOG\n TELEMETRY_SLICE\n CODE_SNAPSHOT\n PROMPT_TEXT\n DIFF\n REASONING_TRACE\n REVIEWER_OUTPUT\n REPLAY_BUNDLE\n\n @@schema(\"derwin\")\n}\n\nenum ArtifactStage {\n DETECTION\n INVESTIGATION\n TICKET_CREATION\n AUTHORING\n PRE_MERGE_VERIFICATION\n POST_MERGE_VERIFICATION\n ARCHIVAL\n\n @@schema(\"derwin\")\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Policies + safety (Sprint 8 — QAP-080..089 — fills these out)\n// ═══════════════════════════════════════════════════════════════════════════\n\nmodel Policy {\n id String @id @default(cuid())\n projectId String\n policyType PolicyType\n\n // Path tier rules: globs → tier\n pathRules Json // [{ glob: \"src/auth/**\", tier: \"HIGH\" }, …]\n classificationOverrides Json // { \"PRODUCT_BUG\": \"MEDIUM\", … }\n\n // Trust thresholds\n autoMergeTrustThreshold Int @default(70)\n autoMergeMediumThreshold Int @default(85)\n\n // Concurrency\n dailyDispatchLimit Int @default(50)\n // Fix-attempt retry limit per ticket. Default 1 per ADR-0006 — try once,\n // route to FAIL on failure (conservative trust posture). Configurable per\n // project; recommended range 1–4. Higher values raise auto-fix throughput\n // on iterative-success classifications at the cost of Anthropic spend and\n // delayed operator awareness of classifier weakness.\n perTicketAttemptLimit Int @default(1)\n\n // Time-of-day\n freezeWindowsCron String[] @default([])\n\n // Escalation triggers\n escalationTriggers Json\n\n updatedAt DateTime @updatedAt\n updatedBy String\n\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n\n @@index([projectId])\n @@map(\"policies\")\n @@schema(\"derwin\")\n}\n\nenum PolicyType {\n RISK_TIER\n CONCURRENCY\n TRUST_THRESHOLD\n ESCALATION\n\n @@schema(\"derwin\")\n}\n\nmodel ProjectModeLog {\n id String @id @default(cuid())\n projectId String\n fromMode ProjectMode\n toMode ProjectMode\n reason String @db.Text\n changedBy String // \"auto-promotion\" | userId\n changedAt DateTime @default(now())\n\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n\n @@index([projectId, changedAt(sort: Desc)])\n @@map(\"project_mode_log\")\n @@schema(\"derwin\")\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Cost + telemetry\n// ═══════════════════════════════════════════════════════════════════════════\n\nmodel SpendLedger {\n id String @id @default(cuid())\n projectId String\n qaFixAttemptId String?\n\n // Per-event spend\n vendor String // \"anthropic\" | \"openai\" | \"github-actions\" | \"supabase-storage\"\n eventType String // \"claude_dispatch\" | \"claude_review\" | \"embedding\" | \"storage_write\"\n costCents Int // signed; can be credit\n meta Json\n\n occurredAt DateTime @default(now())\n\n @@index([projectId, occurredAt(sort: Desc)])\n @@index([projectId, vendor, occurredAt])\n @@map(\"spend_ledger\")\n @@schema(\"derwin\")\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Layer F: Learning — patterns + reverts (QAP-019D + QAP-019E, Group D-2)\n// ═══════════════════════════════════════════════════════════════════════════\n\n// QAPattern is a learned failure signature — the orchestrator's Triage stage\n// (Sprint 6) bumps occurrenceCount + lastSeenAt when a new RawSignal matches\n// a known signature. Lifeline-shape preserved verbatim per Group D-2 plan\n// decision #1: re-ranker fields (fixSuccessScore / fixOutcomeSampleSize /\n// lastWeightedAt) stay even though Derwin's re-ranker today reads\n// classification-trust-store. Preserves optionality for Sprint 4+ consumers\n// and removes shim translation work in QAP-019G.\n//\n// (signature, projectId) is unique — the same failure signature in different\n// projects is two distinct rows. Lifeline-style.\nmodel QAPattern {\n id String @id @default(cuid())\n projectId String\n signature String\n classification QAFailureClass\n firstSeenAt DateTime @default(now())\n lastSeenAt DateTime @default(now())\n occurrenceCount Int @default(1)\n affectedTickets String[] @default([])\n trendDirection String @default(\"stable\") // improving | stable | degrading\n status QAPatternStatus @default(OPEN)\n resolvedAt DateTime?\n promotedToLintAt DateTime?\n promotedLintPR String?\n archivedAt DateTime?\n\n // Re-ranker fields (Lifeline-shape preserved, Decision 1).\n fixSuccessScore Float?\n fixOutcomeSampleSize Int @default(0)\n lastWeightedAt DateTime?\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n\n @@unique([projectId, signature])\n @@index([projectId, lastSeenAt(sort: Desc)])\n @@index([status])\n @@map(\"qa_patterns\")\n @@schema(\"derwin\")\n}\n\nenum QAPatternStatus {\n OPEN\n RESOLVED\n ARCHIVED\n\n @@schema(\"derwin\")\n}\n\nenum QAFailureClass {\n SELECTOR\n TIMING\n TEST_DATA\n PRODUCT_BUG\n SCHEMA_DRIFT\n UNKNOWN\n\n @@schema(\"derwin\")\n}\n\n// QARevert logs a manual or auto-revert of a fix-forward commit. The route\n// handler does NOT execute `git revert` — it only records the operator's\n// action. Restore is the inverse: a fix-forward commit lands and the operator\n// flips restoredAt + restoredBy + restoredBySha.\n//\n// Decision 2: keep qaRunId FK only. Lifeline's qaResultId pointed at the\n// QARunResult table, which Derwin doesn't have under Option β (results are\n// RawSignal records). Reverts are at the ticket+commit level, not the\n// per-test-result level.\nmodel QARevert {\n id String @id @default(cuid())\n projectId String\n ticketId String\n revertedFromSha String\n revertedToSha String?\n revertedAt DateTime @default(now())\n revertedBy String\n reason String @db.Text\n qaRunId String?\n\n // Restore audit trail — set when the fix-forward commit ships.\n restoredAt DateTime?\n restoredBy String?\n restoredBySha String?\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n ticket QATicket @relation(fields: [ticketId], references: [id], onDelete: Cascade)\n run QARun? @relation(fields: [qaRunId], references: [id], onDelete: SetNull)\n\n @@index([projectId, revertedAt(sort: Desc)])\n @@index([ticketId])\n @@map(\"qa_reverts\")\n @@schema(\"derwin\")\n}\n\n// QAUniformity stores per-page rule outcomes from the invariant crawler\n// (uniformity audit). Lifeline calls this `QAUniformityAudit`; renamed to\n// `QAUniformity` per Group D-3 plan Decision 5 (drop `Audit` suffix to match\n// other Derwin model names). Bulk-inserted by /api/qa/uniformity POST and\n// aggregated by GET. Optionally tied to a QARun (most ingestions are run-\n// scoped; manual operator runs may not have a qaRunId).\nmodel QAUniformity {\n id String @id @default(cuid())\n projectId String\n qaRunId String?\n pagePath String\n ruleName String\n ruleCategory String\n status QAUniformityStatus\n violationDetail String? @db.Text\n createdAt DateTime @default(now())\n\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n run QARun? @relation(fields: [qaRunId], references: [id], onDelete: SetNull)\n\n @@index([projectId, createdAt(sort: Desc)])\n @@index([projectId, ruleName, status])\n @@index([projectId, pagePath])\n @@map(\"qa_uniformities\")\n @@schema(\"derwin\")\n}\n\nenum QAUniformityStatus {\n PASSED\n FAILED\n SKIPPED\n\n @@schema(\"derwin\")\n}\n",
|
|
612
|
+
"inlineSchemaHash": "4fa036f75087a439bd3685ce630eb418553b67a25b7abe2526c98f4e56e239e9",
|
|
611
613
|
"copyEngine": true
|
|
612
614
|
}
|
|
613
615
|
|
|
@@ -14,6 +14,14 @@
|
|
|
14
14
|
// pgvector convention: `embedding Bytes?` on RAGCorpus is the v1 storage type.
|
|
15
15
|
// QAP-076 (Sprint 7 — RAG corpus add) switches it to a pgvector `vector(1536)`
|
|
16
16
|
// column once the extension is enabled in QAP-036.
|
|
17
|
+
//
|
|
18
|
+
// Multi-schema: every model + enum is in the `derwin` Postgres schema (not
|
|
19
|
+
// `public`). This isolates Derwin's tables from each consumer's existing
|
|
20
|
+
// schema — e.g., Lifeline's pre-extraction `public.qa_fix_attempts` stays
|
|
21
|
+
// untouched while Derwin's tables live at `derwin.qa_fix_attempts`. Required
|
|
22
|
+
// for the multi-tenant boundary rule: each consumer = ONE Project row, but
|
|
23
|
+
// each consumer also has its own pre-existing schema in `public`. (Added in
|
|
24
|
+
// QAP-019G when extracting Derwin into Lifeline first revealed the conflict.)
|
|
17
25
|
|
|
18
26
|
// Generator output is INSIDE the package so the generated client ships
|
|
19
27
|
// in the published tarball alongside dist/. Consumer code does
|
|
@@ -33,14 +41,16 @@
|
|
|
33
41
|
// tarball platform-dependent (whatever the publisher was running on).
|
|
34
42
|
// Intel Mac users (rare) add `darwin` locally via a re-generate.
|
|
35
43
|
generator client {
|
|
36
|
-
provider
|
|
37
|
-
output
|
|
38
|
-
binaryTargets
|
|
44
|
+
provider = "prisma-client-js"
|
|
45
|
+
output = "../prisma-client"
|
|
46
|
+
binaryTargets = ["darwin-arm64", "rhel-openssl-3.0.x", "linux-arm64-openssl-3.0.x"]
|
|
47
|
+
previewFeatures = ["multiSchema"]
|
|
39
48
|
}
|
|
40
49
|
|
|
41
50
|
datasource db {
|
|
42
51
|
provider = "postgresql"
|
|
43
52
|
url = env("DATABASE_URL")
|
|
53
|
+
schemas = ["derwin"]
|
|
44
54
|
}
|
|
45
55
|
|
|
46
56
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -96,6 +106,7 @@ model Project {
|
|
|
96
106
|
uniformities QAUniformity[]
|
|
97
107
|
|
|
98
108
|
@@map("projects")
|
|
109
|
+
@@schema("derwin")
|
|
99
110
|
}
|
|
100
111
|
|
|
101
112
|
enum ProjectType {
|
|
@@ -106,6 +117,8 @@ enum ProjectType {
|
|
|
106
117
|
ANALYTICS
|
|
107
118
|
INFRA
|
|
108
119
|
CONTENT
|
|
120
|
+
|
|
121
|
+
@@schema("derwin")
|
|
109
122
|
}
|
|
110
123
|
|
|
111
124
|
enum ProjectMode {
|
|
@@ -113,6 +126,8 @@ enum ProjectMode {
|
|
|
113
126
|
TICKET_ONLY // write tickets, no fix authoring
|
|
114
127
|
AUTHOR // tickets + fix authoring, all to PR
|
|
115
128
|
AUTO // full autonomy per risk tier policies
|
|
129
|
+
|
|
130
|
+
@@schema("derwin")
|
|
116
131
|
}
|
|
117
132
|
|
|
118
133
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -142,6 +157,7 @@ model ProjectProfile {
|
|
|
142
157
|
evolutionLog ProfileEvolutionLog[]
|
|
143
158
|
|
|
144
159
|
@@map("project_profiles")
|
|
160
|
+
@@schema("derwin")
|
|
145
161
|
}
|
|
146
162
|
|
|
147
163
|
model IngestedDoc {
|
|
@@ -157,6 +173,7 @@ model IngestedDoc {
|
|
|
157
173
|
@@unique([projectId, sourcePath, contentHash])
|
|
158
174
|
@@index([projectId, sourcePath])
|
|
159
175
|
@@map("ingested_docs")
|
|
176
|
+
@@schema("derwin")
|
|
160
177
|
}
|
|
161
178
|
|
|
162
179
|
model ProfileEvolutionLog {
|
|
@@ -172,6 +189,7 @@ model ProfileEvolutionLog {
|
|
|
172
189
|
|
|
173
190
|
@@index([profileId, appliedAt(sort: Desc)])
|
|
174
191
|
@@map("profile_evolution_log")
|
|
192
|
+
@@schema("derwin")
|
|
175
193
|
}
|
|
176
194
|
|
|
177
195
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -201,6 +219,7 @@ model QARun {
|
|
|
201
219
|
|
|
202
220
|
@@index([projectId, startedAt(sort: Desc)])
|
|
203
221
|
@@map("qa_runs")
|
|
222
|
+
@@schema("derwin")
|
|
204
223
|
}
|
|
205
224
|
|
|
206
225
|
enum RunTrigger {
|
|
@@ -209,6 +228,8 @@ enum RunTrigger {
|
|
|
209
228
|
WEBHOOK
|
|
210
229
|
CONTINUOUS // for routes that run as a daemon (telemetry mining etc.)
|
|
211
230
|
AGENT // QAP-019F (Group D-3): standalone QA agent (browser tool) ingestion
|
|
231
|
+
|
|
232
|
+
@@schema("derwin")
|
|
212
233
|
}
|
|
213
234
|
|
|
214
235
|
enum RunStatus {
|
|
@@ -216,6 +237,8 @@ enum RunStatus {
|
|
|
216
237
|
COMPLETED
|
|
217
238
|
FAILED
|
|
218
239
|
CANCELLED
|
|
240
|
+
|
|
241
|
+
@@schema("derwin")
|
|
219
242
|
}
|
|
220
243
|
|
|
221
244
|
// A RawSignal is what a Discovery route emits. It hasn't been investigated /
|
|
@@ -240,6 +263,7 @@ model RawSignal {
|
|
|
240
263
|
@@index([qaRunId])
|
|
241
264
|
@@index([routeName, capturedAt(sort: Desc)])
|
|
242
265
|
@@map("raw_signals")
|
|
266
|
+
@@schema("derwin")
|
|
243
267
|
}
|
|
244
268
|
|
|
245
269
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -315,6 +339,7 @@ model QATicket {
|
|
|
315
339
|
@@index([projectId, finalBucket])
|
|
316
340
|
@@index([projectId, createdAt(sort: Desc)])
|
|
317
341
|
@@map("qa_tickets")
|
|
342
|
+
@@schema("derwin")
|
|
318
343
|
}
|
|
319
344
|
|
|
320
345
|
enum SurfaceCategory {
|
|
@@ -326,6 +351,8 @@ enum SurfaceCategory {
|
|
|
326
351
|
COMPLIANCE_AUDIT
|
|
327
352
|
MULTI_TENANT_SAFETY
|
|
328
353
|
OPERATIONAL_HEALTH
|
|
354
|
+
|
|
355
|
+
@@schema("derwin")
|
|
329
356
|
}
|
|
330
357
|
|
|
331
358
|
enum Severity {
|
|
@@ -333,6 +360,8 @@ enum Severity {
|
|
|
333
360
|
HIGH
|
|
334
361
|
MEDIUM
|
|
335
362
|
LOW
|
|
363
|
+
|
|
364
|
+
@@schema("derwin")
|
|
336
365
|
}
|
|
337
366
|
|
|
338
367
|
enum RiskTier {
|
|
@@ -340,6 +369,8 @@ enum RiskTier {
|
|
|
340
369
|
MEDIUM
|
|
341
370
|
HIGH
|
|
342
371
|
NEVER
|
|
372
|
+
|
|
373
|
+
@@schema("derwin")
|
|
343
374
|
}
|
|
344
375
|
|
|
345
376
|
enum TicketStatus {
|
|
@@ -352,12 +383,16 @@ enum TicketStatus {
|
|
|
352
383
|
CLOSED_WONTFIX
|
|
353
384
|
CLOSED_RESOLVED
|
|
354
385
|
ESCALATED
|
|
386
|
+
|
|
387
|
+
@@schema("derwin")
|
|
355
388
|
}
|
|
356
389
|
|
|
357
390
|
enum ReviewBucket {
|
|
358
391
|
PASS
|
|
359
392
|
FAIL
|
|
360
393
|
ESCALATION
|
|
394
|
+
|
|
395
|
+
@@schema("derwin")
|
|
361
396
|
}
|
|
362
397
|
|
|
363
398
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -408,6 +443,7 @@ model QAFixAttempt {
|
|
|
408
443
|
@@index([qaTicketId, attemptNumber])
|
|
409
444
|
@@index([projectId, attemptedAt(sort: Desc)])
|
|
410
445
|
@@map("qa_fix_attempts")
|
|
446
|
+
@@schema("derwin")
|
|
411
447
|
}
|
|
412
448
|
|
|
413
449
|
enum AttemptStatus {
|
|
@@ -420,6 +456,8 @@ enum AttemptStatus {
|
|
|
420
456
|
REJECTED
|
|
421
457
|
REGRESSED_REVERTED
|
|
422
458
|
FAILED
|
|
459
|
+
|
|
460
|
+
@@schema("derwin")
|
|
423
461
|
}
|
|
424
462
|
|
|
425
463
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -450,6 +488,7 @@ model ClassificationTrust {
|
|
|
450
488
|
|
|
451
489
|
@@unique([projectId, classification, surface])
|
|
452
490
|
@@map("classification_trust")
|
|
491
|
+
@@schema("derwin")
|
|
453
492
|
}
|
|
454
493
|
|
|
455
494
|
// Successful past fixes used as in-context examples for new dispatches.
|
|
@@ -470,6 +509,7 @@ model RAGCorpus {
|
|
|
470
509
|
|
|
471
510
|
@@index([projectId, classification, surface])
|
|
472
511
|
@@map("rag_corpus")
|
|
512
|
+
@@schema("derwin")
|
|
473
513
|
}
|
|
474
514
|
|
|
475
515
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -505,6 +545,7 @@ model AuditArtifact {
|
|
|
505
545
|
@@index([qaTicketId])
|
|
506
546
|
@@index([projectId, capturedAt(sort: Desc)])
|
|
507
547
|
@@map("audit_artifacts")
|
|
548
|
+
@@schema("derwin")
|
|
508
549
|
}
|
|
509
550
|
|
|
510
551
|
enum ArtifactType {
|
|
@@ -520,6 +561,8 @@ enum ArtifactType {
|
|
|
520
561
|
REASONING_TRACE
|
|
521
562
|
REVIEWER_OUTPUT
|
|
522
563
|
REPLAY_BUNDLE
|
|
564
|
+
|
|
565
|
+
@@schema("derwin")
|
|
523
566
|
}
|
|
524
567
|
|
|
525
568
|
enum ArtifactStage {
|
|
@@ -530,6 +573,8 @@ enum ArtifactStage {
|
|
|
530
573
|
PRE_MERGE_VERIFICATION
|
|
531
574
|
POST_MERGE_VERIFICATION
|
|
532
575
|
ARCHIVAL
|
|
576
|
+
|
|
577
|
+
@@schema("derwin")
|
|
533
578
|
}
|
|
534
579
|
|
|
535
580
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -571,6 +616,7 @@ model Policy {
|
|
|
571
616
|
|
|
572
617
|
@@index([projectId])
|
|
573
618
|
@@map("policies")
|
|
619
|
+
@@schema("derwin")
|
|
574
620
|
}
|
|
575
621
|
|
|
576
622
|
enum PolicyType {
|
|
@@ -578,6 +624,8 @@ enum PolicyType {
|
|
|
578
624
|
CONCURRENCY
|
|
579
625
|
TRUST_THRESHOLD
|
|
580
626
|
ESCALATION
|
|
627
|
+
|
|
628
|
+
@@schema("derwin")
|
|
581
629
|
}
|
|
582
630
|
|
|
583
631
|
model ProjectModeLog {
|
|
@@ -593,6 +641,7 @@ model ProjectModeLog {
|
|
|
593
641
|
|
|
594
642
|
@@index([projectId, changedAt(sort: Desc)])
|
|
595
643
|
@@map("project_mode_log")
|
|
644
|
+
@@schema("derwin")
|
|
596
645
|
}
|
|
597
646
|
|
|
598
647
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -615,6 +664,7 @@ model SpendLedger {
|
|
|
615
664
|
@@index([projectId, occurredAt(sort: Desc)])
|
|
616
665
|
@@index([projectId, vendor, occurredAt])
|
|
617
666
|
@@map("spend_ledger")
|
|
667
|
+
@@schema("derwin")
|
|
618
668
|
}
|
|
619
669
|
|
|
620
670
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -661,12 +711,15 @@ model QAPattern {
|
|
|
661
711
|
@@index([projectId, lastSeenAt(sort: Desc)])
|
|
662
712
|
@@index([status])
|
|
663
713
|
@@map("qa_patterns")
|
|
714
|
+
@@schema("derwin")
|
|
664
715
|
}
|
|
665
716
|
|
|
666
717
|
enum QAPatternStatus {
|
|
667
718
|
OPEN
|
|
668
719
|
RESOLVED
|
|
669
720
|
ARCHIVED
|
|
721
|
+
|
|
722
|
+
@@schema("derwin")
|
|
670
723
|
}
|
|
671
724
|
|
|
672
725
|
enum QAFailureClass {
|
|
@@ -676,6 +729,8 @@ enum QAFailureClass {
|
|
|
676
729
|
PRODUCT_BUG
|
|
677
730
|
SCHEMA_DRIFT
|
|
678
731
|
UNKNOWN
|
|
732
|
+
|
|
733
|
+
@@schema("derwin")
|
|
679
734
|
}
|
|
680
735
|
|
|
681
736
|
// QARevert logs a manual or auto-revert of a fix-forward commit. The route
|
|
@@ -713,6 +768,7 @@ model QARevert {
|
|
|
713
768
|
@@index([projectId, revertedAt(sort: Desc)])
|
|
714
769
|
@@index([ticketId])
|
|
715
770
|
@@map("qa_reverts")
|
|
771
|
+
@@schema("derwin")
|
|
716
772
|
}
|
|
717
773
|
|
|
718
774
|
// QAUniformity stores per-page rule outcomes from the invariant crawler
|
|
@@ -739,10 +795,13 @@ model QAUniformity {
|
|
|
739
795
|
@@index([projectId, ruleName, status])
|
|
740
796
|
@@index([projectId, pagePath])
|
|
741
797
|
@@map("qa_uniformities")
|
|
798
|
+
@@schema("derwin")
|
|
742
799
|
}
|
|
743
800
|
|
|
744
801
|
enum QAUniformityStatus {
|
|
745
802
|
PASSED
|
|
746
803
|
FAILED
|
|
747
804
|
SKIPPED
|
|
805
|
+
|
|
806
|
+
@@schema("derwin")
|
|
748
807
|
}
|