@derwinjs/db 0.1.0

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.
Files changed (102) hide show
  1. package/LICENSE +25 -0
  2. package/README.md +79 -0
  3. package/dist/agent-findings-ingestor.d.ts +40 -0
  4. package/dist/agent-findings-ingestor.d.ts.map +1 -0
  5. package/dist/agent-findings-ingestor.js +154 -0
  6. package/dist/agent-findings-ingestor.js.map +1 -0
  7. package/dist/classification-trust-store.d.ts +31 -0
  8. package/dist/classification-trust-store.d.ts.map +1 -0
  9. package/dist/classification-trust-store.js +154 -0
  10. package/dist/classification-trust-store.js.map +1 -0
  11. package/dist/coverage-gap-reporter.d.ts +35 -0
  12. package/dist/coverage-gap-reporter.d.ts.map +1 -0
  13. package/dist/coverage-gap-reporter.js +84 -0
  14. package/dist/coverage-gap-reporter.js.map +1 -0
  15. package/dist/fix-policy.d.ts +46 -0
  16. package/dist/fix-policy.d.ts.map +1 -0
  17. package/dist/fix-policy.js +162 -0
  18. package/dist/fix-policy.js.map +1 -0
  19. package/dist/index.d.ts +27 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +53 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/learning-health-reporter.d.ts +37 -0
  24. package/dist/learning-health-reporter.d.ts.map +1 -0
  25. package/dist/learning-health-reporter.js +141 -0
  26. package/dist/learning-health-reporter.js.map +1 -0
  27. package/dist/prisma.d.ts +31 -0
  28. package/dist/prisma.d.ts.map +1 -0
  29. package/dist/prisma.js +31 -0
  30. package/dist/prisma.js.map +1 -0
  31. package/dist/qa-fix-attempt-store.d.ts +28 -0
  32. package/dist/qa-fix-attempt-store.d.ts.map +1 -0
  33. package/dist/qa-fix-attempt-store.js +258 -0
  34. package/dist/qa-fix-attempt-store.js.map +1 -0
  35. package/dist/qa-pattern-store.d.ts +32 -0
  36. package/dist/qa-pattern-store.d.ts.map +1 -0
  37. package/dist/qa-pattern-store.js +123 -0
  38. package/dist/qa-pattern-store.js.map +1 -0
  39. package/dist/qa-revert-store.d.ts +24 -0
  40. package/dist/qa-revert-store.d.ts.map +1 -0
  41. package/dist/qa-revert-store.js +139 -0
  42. package/dist/qa-revert-store.js.map +1 -0
  43. package/dist/qa-run-store.d.ts +46 -0
  44. package/dist/qa-run-store.d.ts.map +1 -0
  45. package/dist/qa-run-store.js +201 -0
  46. package/dist/qa-run-store.js.map +1 -0
  47. package/dist/qa-ticket-store.d.ts +35 -0
  48. package/dist/qa-ticket-store.d.ts.map +1 -0
  49. package/dist/qa-ticket-store.js +293 -0
  50. package/dist/qa-ticket-store.js.map +1 -0
  51. package/dist/qa-uniformity-store.d.ts +41 -0
  52. package/dist/qa-uniformity-store.d.ts.map +1 -0
  53. package/dist/qa-uniformity-store.js +288 -0
  54. package/dist/qa-uniformity-store.js.map +1 -0
  55. package/dist/scripts/smoke-auto-fix.d.ts +37 -0
  56. package/dist/scripts/smoke-auto-fix.d.ts.map +1 -0
  57. package/dist/scripts/smoke-auto-fix.js +270 -0
  58. package/dist/scripts/smoke-auto-fix.js.map +1 -0
  59. package/dist/scripts/smoke-learning-loop.d.ts +21 -0
  60. package/dist/scripts/smoke-learning-loop.d.ts.map +1 -0
  61. package/dist/scripts/smoke-learning-loop.js +375 -0
  62. package/dist/scripts/smoke-learning-loop.js.map +1 -0
  63. package/dist/scripts/smoke-orchestration.d.ts +35 -0
  64. package/dist/scripts/smoke-orchestration.d.ts.map +1 -0
  65. package/dist/scripts/smoke-orchestration.js +215 -0
  66. package/dist/scripts/smoke-orchestration.js.map +1 -0
  67. package/dist/scripts/smoke-qa-ticket-store.d.ts +18 -0
  68. package/dist/scripts/smoke-qa-ticket-store.d.ts.map +1 -0
  69. package/dist/scripts/smoke-qa-ticket-store.js +233 -0
  70. package/dist/scripts/smoke-qa-ticket-store.js.map +1 -0
  71. package/package.json +69 -0
  72. package/prisma/migrations/20260501165631_init/migration.sql +407 -0
  73. package/prisma/migrations/20260503051425_0002_qap018b_qaticket_crosslink_fields/migration.sql +6 -0
  74. package/prisma/migrations/20260504231316_add_project_repofullname_and_webhooksecret/migration.sql +12 -0
  75. package/prisma/migrations/20260504232851_add_qaticket_resolvedby/migration.sql +2 -0
  76. package/prisma/migrations/20260505042646_add_qapattern_qarevert/migration.sql +77 -0
  77. package/prisma/migrations/20260505055826_add_qauniformity_and_agent_trigger/migration.sql +35 -0
  78. package/prisma/migrations/migration_lock.toml +3 -0
  79. package/prisma/schema.prisma +748 -0
  80. package/prisma/seed.ts +181 -0
  81. package/prisma-client/default.d.ts +1 -0
  82. package/prisma-client/default.js +1 -0
  83. package/prisma-client/edge.d.ts +1 -0
  84. package/prisma-client/edge.js +631 -0
  85. package/prisma-client/index-browser.js +615 -0
  86. package/prisma-client/index.d.ts +34509 -0
  87. package/prisma-client/index.js +660 -0
  88. package/prisma-client/libquery_engine-darwin-arm64.dylib.node +0 -0
  89. package/prisma-client/libquery_engine-linux-arm64-openssl-3.0.x.so.node +0 -0
  90. package/prisma-client/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  91. package/prisma-client/package.json +97 -0
  92. package/prisma-client/runtime/edge-esm.js +31 -0
  93. package/prisma-client/runtime/edge.js +31 -0
  94. package/prisma-client/runtime/index-browser.d.ts +365 -0
  95. package/prisma-client/runtime/index-browser.js +13 -0
  96. package/prisma-client/runtime/library.d.ts +3403 -0
  97. package/prisma-client/runtime/library.js +143 -0
  98. package/prisma-client/runtime/react-native.js +80 -0
  99. package/prisma-client/runtime/wasm.js +32 -0
  100. package/prisma-client/schema.prisma +748 -0
  101. package/prisma-client/wasm.d.ts +1 -0
  102. package/prisma-client/wasm.js +615 -0
package/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ Derwin — Proprietary License
2
+
3
+ Copyright (c) 2026 Vize LLC. All rights reserved.
4
+
5
+ This software and its associated documentation files (the "Software") are
6
+ proprietary and confidential. No part of the Software may be copied, modified,
7
+ distributed, sublicensed, sold, or otherwise transferred to any third party
8
+ without the prior written consent of the copyright holder.
9
+
10
+ Unauthorized use, reproduction, or distribution of the Software is prohibited
11
+ and may result in civil and criminal penalties.
12
+
13
+ This license may be superseded by an alternative license (e.g., MIT or
14
+ Apache 2.0) at a future date if a decision is made to release the Software
15
+ as open source. Such a decision will be recorded as a superseding ADR in
16
+ docs/adrs/ and will apply only to the version of the Software in effect at
17
+ the time of the decision.
18
+
19
+ The Software is provided "as is", without warranty of any kind, express or
20
+ implied, including but not limited to the warranties of merchantability,
21
+ fitness for a particular purpose, and non-infringement. In no event shall the
22
+ authors or copyright holders be liable for any claim, damages, or other
23
+ liability, whether in an action of contract, tort, or otherwise, arising
24
+ from, out of, or in connection with the Software or the use or other dealings
25
+ in the Software.
package/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # @derwinjs/db
2
+
3
+ Prisma schema + migrations for Derwin's own Postgres. **14 models, project-namespaced.** Per ADR-0005.
4
+
5
+ This package owns the platform's persistence layer. Consumer DBs (Lifeline's Postgres, Side Piece's Postgres) are NOT shared with this — Derwin reads from them via Ticket / Document adapters; it stores its own state here.
6
+
7
+ ## Quick start
8
+
9
+ ```bash
10
+ # 1. Set up Postgres locally (one option: Docker)
11
+ docker run -d --name derwin-pg \
12
+ -e POSTGRES_USER=derwin \
13
+ -e POSTGRES_PASSWORD=derwin \
14
+ -e POSTGRES_DB=derwin \
15
+ -p 5432:5432 \
16
+ postgres:16
17
+
18
+ # 2. Configure connection
19
+ cp packages/db/.env.example packages/db/.env
20
+ # Edit DATABASE_URL if needed
21
+
22
+ # 3. Generate Prisma client + apply migrations
23
+ pnpm --filter @derwinjs/db prisma:generate
24
+ pnpm --filter @derwinjs/db prisma:migrate
25
+
26
+ # 4. Seed dev fixture (Lifeline project + Profile + default Policy)
27
+ pnpm --filter @derwinjs/db db:seed
28
+ ```
29
+
30
+ After seeding, you can connect with `psql` and inspect:
31
+
32
+ ```bash
33
+ docker exec -it derwin-pg psql -U derwin -d derwin -c "SELECT slug, type, mode FROM projects;"
34
+ ```
35
+
36
+ Expected: one row, `lifeline | ERP | AUTHOR`.
37
+
38
+ ## Production: Supabase
39
+
40
+ Per ADR-0005, the production deployment uses Supabase (parity with Lifeline). Connection string format:
41
+
42
+ ```
43
+ postgresql://postgres:[PASSWORD]@db.[PROJECT-REF].supabase.co:5432/postgres
44
+ ```
45
+
46
+ The pgvector extension gets enabled in QAP-036 (Sprint 3 — DB deployment). The RAGCorpus.embedding column is `Bytes?` for v1; QAP-076 (Sprint 7) switches it to `vector(1536)` once pgvector is available.
47
+
48
+ ## Schema conventions
49
+
50
+ - **Project-namespaced.** Every project-scoped table has a `projectId` FK to `Project`. Cross-project leakage is the leading regression risk; queries MUST filter on `projectId`.
51
+ - **Cascading deletes.** Deleting a Project cleanly removes all project-scoped data (cascades through 12 of 14 models).
52
+ - **Append-only logs.** `ProfileEvolutionLog`, `ProjectModeLog`, `SpendLedger` are append-only — never updated, never deleted (compliance requirement per product brief §11.3).
53
+ - **Audit retention.** `AuditArtifact.retentionUntil` is computed at write time from project compliance mode; a separate retention cron prunes expired artifacts (lands in Sprint 7).
54
+ - **Indices on every read pattern.** Per technical spec §3 — `(projectId, status)`, `(projectId, finalBucket)`, `(projectId, createdAt DESC)`, etc.
55
+
56
+ ## Models (14)
57
+
58
+ | # | Model | Purpose | Layer |
59
+ | --- | --------------------- | --------------------------------------------------------- | ------------- |
60
+ | 1 | `Project` | Multi-tenant root | A (Profile) |
61
+ | 2 | `ProjectProfile` | Domain ontology, risk weights, critical flows, glossary | A |
62
+ | 3 | `IngestedDoc` | Source docs that fed the Profile | A |
63
+ | 4 | `ProfileEvolutionLog` | Append-only audit of every Profile change | A |
64
+ | 5 | `QARun` | One discovery pass | B/C |
65
+ | 6 | `RawSignal` | What a Discovery route emits before triage | B/C |
66
+ | 7 | `QATicket` | The structured bug ticket | D |
67
+ | 8 | `QAFixAttempt` | One Claude dispatch + outcome | E |
68
+ | 9 | `ClassificationTrust` | Per-classification rolling 30-day trust score | F |
69
+ | 10 | `RAGCorpus` | Successful past fixes for in-context examples | F |
70
+ | 11 | `AuditArtifact` | Persistent evidence (screenshots, videos, prompts, diffs) | cross-cutting |
71
+ | 12 | `Policy` | Risk-tier rules, classification overrides, thresholds | safety |
72
+ | 13 | `ProjectModeLog` | Append-only mode-change audit | safety |
73
+ | 14 | `SpendLedger` | Per-event $ spend (Anthropic, storage, etc.) | cost |
74
+
75
+ Detail: see [`prisma/schema.prisma`](./prisma/schema.prisma) and technical spec §3.
76
+
77
+ ## Status
78
+
79
+ Scaffolded in QAP-005 (Sprint 1). Schema is complete; the Prisma client gets imported by `@derwinjs/core` and `@derwinjs/api` starting in Sprint 2.
@@ -0,0 +1,40 @@
1
+ /**
2
+ * AgentFindingsIngestor — Prisma-backed ingestor that projects an inbound
3
+ * QA-agent envelope into a QARun + N RawSignals.
4
+ *
5
+ * QAP-019F (Sprint 2 Group D-3, Commit 2). Implements the
6
+ * AgentFindingsIngestor contract from @derwinjs/sdk by composing
7
+ * QARunStore.createRun under the hood (Group D-3 plan Decision 2).
8
+ *
9
+ * Translation under Option β:
10
+ * - Envelope → QARun(triggerType='AGENT', triggeredBy=agentVersion ?? 'qa-agent',
11
+ * scope = non-finding fields).
12
+ * - Each finding → RawSignal(routeName='agent', signalType='agent_finding',
13
+ * rawData = {sev, area, msg, file, tags, proposedFix}).
14
+ * - Findings with sev='info' are dropped (Lifeline parity); counted
15
+ * separately as droppedInfoCount in the response.
16
+ * - **No inline ticket creation.** The orchestrator's Triage stage owns
17
+ * ticket creation (ADR-0008); this ingestor only writes signals.
18
+ *
19
+ * FK violations from the underlying QARunStore (unknown projectId) bubble
20
+ * up as QAStoreError('fk_violation', ...) — we re-throw as
21
+ * AgentFindingsIngestorError('fk_violation', ...) so the route handler
22
+ * has a single typed error class to switch on.
23
+ */
24
+ import type { QARunStore } from '@derwinjs/sdk';
25
+ import { type AgentFindingsIngestor } from '@derwinjs/sdk';
26
+ /**
27
+ * The ingestor composes QARunStore.createRun, so config takes a runStore
28
+ * rather than a Prisma client. Consumers that need a custom run store
29
+ * (e.g., a write-through cache, a dual-write to legacy Lifeline) can pass
30
+ * their own.
31
+ *
32
+ * configureQAPlatform() wires `runs: createPrismaQARunStore({ prisma })`
33
+ * and then `agentFindings: createPrismaAgentFindingsIngestor({ runStore: runs })`.
34
+ */
35
+ export interface PrismaAgentFindingsIngestorConfig {
36
+ /** A QARunStore implementation. Most consumers pass createPrismaQARunStore output. */
37
+ runStore: QARunStore;
38
+ }
39
+ export declare function createPrismaAgentFindingsIngestor(config: PrismaAgentFindingsIngestorConfig): AgentFindingsIngestor;
40
+ //# sourceMappingURL=agent-findings-ingestor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-findings-ingestor.d.ts","sourceRoot":"","sources":["../src/agent-findings-ingestor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAML,KAAK,qBAAqB,EAG3B,MAAM,eAAe,CAAC;AAIvB;;;;;;;;GAQG;AACH,MAAM,WAAW,iCAAiC;IAChD,sFAAsF;IACtF,QAAQ,EAAE,UAAU,CAAC;CACtB;AAID,wBAAgB,iCAAiC,CAC/C,MAAM,EAAE,iCAAiC,GACxC,qBAAqB,CAwDvB"}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * AgentFindingsIngestor — Prisma-backed ingestor that projects an inbound
3
+ * QA-agent envelope into a QARun + N RawSignals.
4
+ *
5
+ * QAP-019F (Sprint 2 Group D-3, Commit 2). Implements the
6
+ * AgentFindingsIngestor contract from @derwinjs/sdk by composing
7
+ * QARunStore.createRun under the hood (Group D-3 plan Decision 2).
8
+ *
9
+ * Translation under Option β:
10
+ * - Envelope → QARun(triggerType='AGENT', triggeredBy=agentVersion ?? 'qa-agent',
11
+ * scope = non-finding fields).
12
+ * - Each finding → RawSignal(routeName='agent', signalType='agent_finding',
13
+ * rawData = {sev, area, msg, file, tags, proposedFix}).
14
+ * - Findings with sev='info' are dropped (Lifeline parity); counted
15
+ * separately as droppedInfoCount in the response.
16
+ * - **No inline ticket creation.** The orchestrator's Triage stage owns
17
+ * ticket creation (ADR-0008); this ingestor only writes signals.
18
+ *
19
+ * FK violations from the underlying QARunStore (unknown projectId) bubble
20
+ * up as QAStoreError('fk_violation', ...) — we re-throw as
21
+ * AgentFindingsIngestorError('fk_violation', ...) so the route handler
22
+ * has a single typed error class to switch on.
23
+ */
24
+ import { AgentFindingsIngestorError, QARunStoreError, } from '@derwinjs/sdk';
25
+ // ─── Factory ─────────────────────────────────────────────────────────────
26
+ export function createPrismaAgentFindingsIngestor(config) {
27
+ const { runStore } = config;
28
+ return {
29
+ async ingestAgentFindings(input) {
30
+ validateInput(input);
31
+ const { projectId, envelope } = input;
32
+ // Filter findings: drop info-severity. Lifeline rule — info-level
33
+ // findings are diagnostic noise, not signal.
34
+ const droppedInfoCount = envelope.findings.filter((f) => f.sev === 'info').length;
35
+ const keptFindings = envelope.findings.filter((f) => f.sev !== 'info');
36
+ // Build the signals payload. routeName is a synthetic constant
37
+ // 'agent' — Lifeline's RawSignal.routeName is required + non-empty
38
+ // and the agent has no per-finding route concept.
39
+ const signals = keptFindings.map((f) => ({
40
+ routeName: 'agent',
41
+ signalType: 'agent_finding',
42
+ rawData: agentFindingToRawData(f),
43
+ rawArtifactRefs: [],
44
+ }));
45
+ // Compose QARunStore.createRun. Triage stage may later promote some
46
+ // signals to QATickets; that's not this ingestor's job.
47
+ let detail;
48
+ try {
49
+ detail = await runStore.createRun({
50
+ projectId,
51
+ triggeredBy: envelope.agentVersion ?? 'qa-agent',
52
+ triggerType: 'AGENT',
53
+ scope: buildScopeFromEnvelope(envelope),
54
+ status: 'COMPLETED',
55
+ completedAt: new Date(),
56
+ signals,
57
+ });
58
+ }
59
+ catch (err) {
60
+ if (err instanceof QARunStoreError && err.code === 'fk_violation') {
61
+ throw new AgentFindingsIngestorError('fk_violation', `AgentFindingsIngestor: QARunStore reports FK violation: ${err.message}`, { projectId });
62
+ }
63
+ throw err;
64
+ }
65
+ return {
66
+ qaRunId: detail.run.id,
67
+ status: detail.run.status,
68
+ signalsRaised: detail.run.signalsRaised,
69
+ droppedInfoCount,
70
+ };
71
+ },
72
+ };
73
+ }
74
+ // ─── Helpers ─────────────────────────────────────────────────────────────
75
+ /**
76
+ * Project a single agent finding to RawSignal.rawData. Optional fields
77
+ * are omitted (rather than written as null) so JSON column reads stay
78
+ * tight — Lifeline parity.
79
+ */
80
+ function agentFindingToRawData(f) {
81
+ const data = {
82
+ sev: f.sev,
83
+ area: f.area,
84
+ msg: f.msg,
85
+ };
86
+ if (f.file !== undefined && f.file !== null)
87
+ data.file = f.file;
88
+ if (f.tags !== undefined && f.tags.length > 0)
89
+ data.tags = [...f.tags];
90
+ if (f.proposedFix !== undefined && f.proposedFix !== null)
91
+ data.proposedFix = f.proposedFix;
92
+ return data;
93
+ }
94
+ /**
95
+ * Pack the non-finding envelope fields into QARun.scope so the dashboard
96
+ * can render the agent's summary metadata without an extra query. Findings
97
+ * intentionally omitted — they live as RawSignal rows.
98
+ */
99
+ function buildScopeFromEnvelope(envelope) {
100
+ const scope = {
101
+ source: envelope.source,
102
+ generatedAt: envelope.generatedAt,
103
+ platform: { ...envelope.platform },
104
+ };
105
+ if (envelope.agentVersion !== undefined)
106
+ scope.agentVersion = envelope.agentVersion;
107
+ if (envelope.healthScore !== undefined)
108
+ scope.healthScore = envelope.healthScore;
109
+ if (envelope.summary !== undefined)
110
+ scope.summary = envelope.summary;
111
+ if (envelope.sprintPlan !== undefined)
112
+ scope.sprintPlan = envelope.sprintPlan;
113
+ if (envelope.moduleHealth !== undefined)
114
+ scope.moduleHealth = envelope.moduleHealth;
115
+ if (envelope.issuesByArea !== undefined)
116
+ scope.issuesByArea = envelope.issuesByArea;
117
+ return scope;
118
+ }
119
+ // ─── Validation ──────────────────────────────────────────────────────────
120
+ function validateInput(input) {
121
+ if (typeof input.projectId !== 'string' || input.projectId.length === 0) {
122
+ throw new AgentFindingsIngestorError('invalid_input', 'AgentFindingsIngestor: projectId is required');
123
+ }
124
+ const envelopeValue = input.envelope;
125
+ if (typeof envelopeValue !== 'object' || envelopeValue === null) {
126
+ throw new AgentFindingsIngestorError('invalid_input', 'AgentFindingsIngestor: envelope is required');
127
+ }
128
+ // Cast through unknown to bypass the literal-type narrowing — this guard
129
+ // catches untyped JSON from the route layer where source could be anything.
130
+ const sourceValue = input.envelope.source;
131
+ if (sourceValue !== 'qa-agent') {
132
+ throw new AgentFindingsIngestorError('invalid_input', `AgentFindingsIngestor: envelope.source must be 'qa-agent' (got '${String(sourceValue)}')`);
133
+ }
134
+ const findingsValue = input.envelope.findings;
135
+ if (!Array.isArray(findingsValue)) {
136
+ throw new AgentFindingsIngestorError('invalid_input', 'AgentFindingsIngestor: envelope.findings must be an array');
137
+ }
138
+ input.envelope.findings.forEach((f, i) => {
139
+ validateFinding(f, i);
140
+ });
141
+ }
142
+ function validateFinding(f, idx) {
143
+ const validSevs = ['critical', 'high', 'medium', 'low', 'info'];
144
+ if (typeof f.sev !== 'string' || !validSevs.includes(f.sev)) {
145
+ throw new AgentFindingsIngestorError('invalid_input', `AgentFindingsIngestor: findings[${String(idx)}].sev must be one of ${validSevs.join('|')}`);
146
+ }
147
+ if (typeof f.area !== 'string' || f.area.length === 0) {
148
+ throw new AgentFindingsIngestorError('invalid_input', `AgentFindingsIngestor: findings[${String(idx)}].area is required`);
149
+ }
150
+ if (typeof f.msg !== 'string' || f.msg.length === 0) {
151
+ throw new AgentFindingsIngestorError('invalid_input', `AgentFindingsIngestor: findings[${String(idx)}].msg is required`);
152
+ }
153
+ }
154
+ //# sourceMappingURL=agent-findings-ingestor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-findings-ingestor.js","sourceRoot":"","sources":["../src/agent-findings-ingestor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * ClassificationTrustStore — Prisma implementation against Derwin's
3
+ * ClassificationTrust table.
4
+ *
5
+ * QAP-015 (Sprint 2). Implements the ClassificationTrustStore contract from
6
+ * @derwinjs/sdk (see packages/sdk/src/types/classification-trust-store.ts)
7
+ * against the @derwinjs/db Prisma client. Mirrors createPrismaQAFixAttemptStore
8
+ * in structure: a factory taking `{ prisma }` so callers inject the client
9
+ * (per the architecture pivot's DI seam — @derwinjs/db owns persistence; the
10
+ * orchestrator wires it in via configureQAPlatform()).
11
+ *
12
+ * Tenant isolation: every method scopes by projectId in the WHERE clause and
13
+ * uses findFirst (not findUnique) for reads so wrong-tenant lookups return
14
+ * null instead of leaking the (classification, surface) tuple's existence
15
+ * cross-project. App-layer guards in Sprint 2 ahead of the full Supabase RLS
16
+ * migration (Sprint 3 / QAP-024).
17
+ *
18
+ * Composes with TrustScoreUpdater (@derwinjs/core, Layer F): the updater
19
+ * recomputes whole rows from scratch on each cron tick (rolling 30-day
20
+ * window aggregation). It does NOT supply lastComputedAt — this store
21
+ * always overwrites it with `new Date()` on both upsert branches per the
22
+ * SDK contract.
23
+ */
24
+ import { type PrismaClient } from './prisma.js';
25
+ import { type ClassificationTrustStore } from '@derwinjs/sdk';
26
+ export interface PrismaClassificationTrustStoreConfig {
27
+ /** Generated Prisma client. Pass an instance per process. */
28
+ prisma: PrismaClient;
29
+ }
30
+ export declare function createPrismaClassificationTrustStore(config: PrismaClassificationTrustStoreConfig): ClassificationTrustStore;
31
+ //# sourceMappingURL=classification-trust-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classification-trust-store.d.ts","sourceRoot":"","sources":["../src/classification-trust-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAU,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAEL,KAAK,wBAAwB,EAG9B,MAAM,eAAe,CAAC;AAIvB,MAAM,WAAW,oCAAoC;IACnD,6DAA6D;IAC7D,MAAM,EAAE,YAAY,CAAC;CACtB;AAID,wBAAgB,oCAAoC,CAClD,MAAM,EAAE,oCAAoC,GAC3C,wBAAwB,CA6F1B"}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * ClassificationTrustStore — Prisma implementation against Derwin's
3
+ * ClassificationTrust table.
4
+ *
5
+ * QAP-015 (Sprint 2). Implements the ClassificationTrustStore contract from
6
+ * @derwinjs/sdk (see packages/sdk/src/types/classification-trust-store.ts)
7
+ * against the @derwinjs/db Prisma client. Mirrors createPrismaQAFixAttemptStore
8
+ * in structure: a factory taking `{ prisma }` so callers inject the client
9
+ * (per the architecture pivot's DI seam — @derwinjs/db owns persistence; the
10
+ * orchestrator wires it in via configureQAPlatform()).
11
+ *
12
+ * Tenant isolation: every method scopes by projectId in the WHERE clause and
13
+ * uses findFirst (not findUnique) for reads so wrong-tenant lookups return
14
+ * null instead of leaking the (classification, surface) tuple's existence
15
+ * cross-project. App-layer guards in Sprint 2 ahead of the full Supabase RLS
16
+ * migration (Sprint 3 / QAP-024).
17
+ *
18
+ * Composes with TrustScoreUpdater (@derwinjs/core, Layer F): the updater
19
+ * recomputes whole rows from scratch on each cron tick (rolling 30-day
20
+ * window aggregation). It does NOT supply lastComputedAt — this store
21
+ * always overwrites it with `new Date()` on both upsert branches per the
22
+ * SDK contract.
23
+ */
24
+ import { ClassificationTrustStoreError, } from '@derwinjs/sdk';
25
+ // ─── Factory ─────────────────────────────────────────────────────────────
26
+ export function createPrismaClassificationTrustStore(config) {
27
+ const { prisma } = config;
28
+ return {
29
+ async upsertTrust(input) {
30
+ validateUpsertInput(input);
31
+ // The lastComputedAt timestamp is owned by this store, not the caller —
32
+ // the SDK contract documents it as "Sets lastComputedAt to now on both
33
+ // paths." Generate once so create + update branches agree.
34
+ const lastComputedAt = new Date();
35
+ const dataShared = {
36
+ attemptsLast30d: input.attemptsLast30d,
37
+ mergedClean: input.mergedClean,
38
+ mergedWithEdits: input.mergedWithEdits,
39
+ regressed: input.regressed,
40
+ reverted: input.reverted,
41
+ successScore: input.successScore,
42
+ trustPercent: input.trustPercent,
43
+ autoMergeEligible: input.autoMergeEligible,
44
+ lastComputedAt,
45
+ };
46
+ const upserted = await prisma.classificationTrust.upsert({
47
+ where: {
48
+ projectId_classification_surface: {
49
+ projectId: input.projectId,
50
+ classification: input.classification,
51
+ surface: input.surface,
52
+ },
53
+ },
54
+ create: {
55
+ projectId: input.projectId,
56
+ classification: input.classification,
57
+ surface: input.surface,
58
+ ...dataShared,
59
+ },
60
+ update: dataShared,
61
+ });
62
+ return mapRow(upserted);
63
+ },
64
+ async getTrust(key) {
65
+ // findFirst (not findUnique) — leaking the (classification, surface)
66
+ // composite's existence cross-tenant via P2025 vs null is the exact
67
+ // enumeration vector ADR-0009 / Pattern D guards against. With
68
+ // projectId in the WHERE, missing-row + wrong-tenant collapse to
69
+ // identical null returns.
70
+ const row = await prisma.classificationTrust.findFirst({
71
+ where: {
72
+ projectId: key.projectId,
73
+ classification: key.classification,
74
+ surface: key.surface,
75
+ },
76
+ });
77
+ return row ? mapRow(row) : null;
78
+ },
79
+ async listTrust(filter) {
80
+ if (!filter.projectId) {
81
+ throw new ClassificationTrustStoreError('invalid_input', 'projectId is required for listTrust — there is no cross-tenant list query');
82
+ }
83
+ const where = {
84
+ projectId: filter.projectId,
85
+ };
86
+ if (filter.surface !== undefined) {
87
+ where.surface = Array.isArray(filter.surface) ? { in: filter.surface } : filter.surface;
88
+ }
89
+ if (filter.minTrustPercent !== undefined) {
90
+ where.trustPercent = { gte: filter.minTrustPercent };
91
+ }
92
+ if (filter.autoMergeEligible !== undefined) {
93
+ where.autoMergeEligible = filter.autoMergeEligible;
94
+ }
95
+ const limit = Math.max(1, Math.min(200, filter.limit ?? 50));
96
+ const rows = await prisma.classificationTrust.findMany({
97
+ where,
98
+ orderBy: { trustPercent: 'desc' },
99
+ take: limit,
100
+ });
101
+ return { trusts: rows.map(mapRow) };
102
+ },
103
+ };
104
+ }
105
+ // ─── Helpers ─────────────────────────────────────────────────────────────
106
+ /** Convert a Prisma ClassificationTrust row to the SDK value type. */
107
+ function mapRow(row) {
108
+ return {
109
+ id: row.id,
110
+ projectId: row.projectId,
111
+ classification: row.classification,
112
+ surface: row.surface,
113
+ attemptsLast30d: row.attemptsLast30d,
114
+ mergedClean: row.mergedClean,
115
+ mergedWithEdits: row.mergedWithEdits,
116
+ regressed: row.regressed,
117
+ reverted: row.reverted,
118
+ successScore: row.successScore,
119
+ trustPercent: row.trustPercent,
120
+ autoMergeEligible: row.autoMergeEligible,
121
+ lastComputedAt: row.lastComputedAt,
122
+ };
123
+ }
124
+ function validateUpsertInput(input) {
125
+ if (!input.projectId) {
126
+ throw new ClassificationTrustStoreError('invalid_input', 'projectId is required');
127
+ }
128
+ if (!input.classification) {
129
+ throw new ClassificationTrustStoreError('invalid_input', 'classification is required');
130
+ }
131
+ // Bucket counts: rolling 30-day aggregates that can never be negative.
132
+ const counts = [
133
+ ['attemptsLast30d', input.attemptsLast30d],
134
+ ['mergedClean', input.mergedClean],
135
+ ['mergedWithEdits', input.mergedWithEdits],
136
+ ['regressed', input.regressed],
137
+ ['reverted', input.reverted],
138
+ ];
139
+ for (const [name, value] of counts) {
140
+ if (!Number.isFinite(value) || value < 0) {
141
+ throw new ClassificationTrustStoreError('invalid_input', `${name} must be a non-negative finite number`);
142
+ }
143
+ }
144
+ // successScore: derived ratio, bounded [-1, 1] per the schema docstring.
145
+ if (!Number.isFinite(input.successScore) || input.successScore < -1 || input.successScore > 1) {
146
+ throw new ClassificationTrustStoreError('invalid_input', 'successScore must be in [-1, 1]');
147
+ }
148
+ // trustPercent: integer percent for operator UI; range + integer enforced
149
+ // here so the DB never stores a fractional or out-of-range percent.
150
+ if (!Number.isInteger(input.trustPercent) || input.trustPercent < 0 || input.trustPercent > 100) {
151
+ throw new ClassificationTrustStoreError('invalid_input', 'trustPercent must be an integer in [0, 100]');
152
+ }
153
+ }
154
+ //# sourceMappingURL=classification-trust-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classification-trust-store.js","sourceRoot":"","sources":["../src/classification-trust-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * CoverageGapReporter — Prisma-backed report listing tickets joined with
3
+ * their test-coverage signal status.
4
+ *
5
+ * QAP-019F (Sprint 2 Group D-3, Commit 3). Implements the
6
+ * CoverageGapReporter contract from @derwinjs/sdk under **Option β**.
7
+ *
8
+ * **Reframe vs Lifeline**: Lifeline's coverage-gap was a hardcoded list of
9
+ * dashboard ticket IDs joined LEFT against QARunResult to find tickets
10
+ * without a Playwright spec. Derwin doesn't ship dashboard-ticket parity
11
+ * (Sprint 4+ owns Profile-driven ticket inventory) and doesn't have
12
+ * QARunResult under Option β — per-test envelopes live as RawSignal records
13
+ * with `signalType='test_failure'`.
14
+ *
15
+ * The reframed query (Decision 3): a ticket "has coverage" if there exists
16
+ * at least one RawSignal with `signalType='test_failure'` and
17
+ * `qaTicketId=ticket.id`. Same dashboard intent ("tickets we track but
18
+ * lack signal evidence"), different mechanic.
19
+ *
20
+ * The query is two findMany calls:
21
+ * 1. List all QATickets in the project (id + display fields).
22
+ * 2. List the distinct qaTicketIds from RawSignals where
23
+ * signalType='test_failure' in this project.
24
+ * Then we LEFT JOIN in memory: hasSignal = signalIds.has(ticket.id).
25
+ *
26
+ * Tenant isolation: every query scopes by projectId.
27
+ */
28
+ import type { PrismaClient } from './prisma.js';
29
+ import { type CoverageGapReporter } from '@derwinjs/sdk';
30
+ export interface PrismaCoverageGapReporterConfig {
31
+ /** Generated Prisma client. Pass an instance per process. */
32
+ prisma: PrismaClient;
33
+ }
34
+ export declare function createPrismaCoverageGapReporter(config: PrismaCoverageGapReporterConfig): CoverageGapReporter;
35
+ //# sourceMappingURL=coverage-gap-reporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage-gap-reporter.d.ts","sourceRoot":"","sources":["../src/coverage-gap-reporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAIL,KAAK,mBAAmB,EAEzB,MAAM,eAAe,CAAC;AAIvB,MAAM,WAAW,+BAA+B;IAC9C,6DAA6D;IAC7D,MAAM,EAAE,YAAY,CAAC;CACtB;AAID,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,+BAA+B,GACtC,mBAAmB,CAuDrB"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * CoverageGapReporter — Prisma-backed report listing tickets joined with
3
+ * their test-coverage signal status.
4
+ *
5
+ * QAP-019F (Sprint 2 Group D-3, Commit 3). Implements the
6
+ * CoverageGapReporter contract from @derwinjs/sdk under **Option β**.
7
+ *
8
+ * **Reframe vs Lifeline**: Lifeline's coverage-gap was a hardcoded list of
9
+ * dashboard ticket IDs joined LEFT against QARunResult to find tickets
10
+ * without a Playwright spec. Derwin doesn't ship dashboard-ticket parity
11
+ * (Sprint 4+ owns Profile-driven ticket inventory) and doesn't have
12
+ * QARunResult under Option β — per-test envelopes live as RawSignal records
13
+ * with `signalType='test_failure'`.
14
+ *
15
+ * The reframed query (Decision 3): a ticket "has coverage" if there exists
16
+ * at least one RawSignal with `signalType='test_failure'` and
17
+ * `qaTicketId=ticket.id`. Same dashboard intent ("tickets we track but
18
+ * lack signal evidence"), different mechanic.
19
+ *
20
+ * The query is two findMany calls:
21
+ * 1. List all QATickets in the project (id + display fields).
22
+ * 2. List the distinct qaTicketIds from RawSignals where
23
+ * signalType='test_failure' in this project.
24
+ * Then we LEFT JOIN in memory: hasSignal = signalIds.has(ticket.id).
25
+ *
26
+ * Tenant isolation: every query scopes by projectId.
27
+ */
28
+ import { CoverageGapReporterError, } from '@derwinjs/sdk';
29
+ // ─── Factory ─────────────────────────────────────────────────────────────
30
+ export function createPrismaCoverageGapReporter(config) {
31
+ const { prisma } = config;
32
+ return {
33
+ async getCoverageGap(filter) {
34
+ validateFilter(filter);
35
+ // 1. All tickets in the project — minimal fields the contract returns.
36
+ const tickets = await prisma.qATicket.findMany({
37
+ where: { projectId: filter.projectId },
38
+ orderBy: { createdAt: 'desc' },
39
+ select: {
40
+ id: true,
41
+ title: true,
42
+ classification: true,
43
+ status: true,
44
+ createdAt: true,
45
+ },
46
+ });
47
+ if (tickets.length === 0) {
48
+ return { tickets: [] };
49
+ }
50
+ // 2. Distinct qaTicketIds with at least one test_failure signal.
51
+ //
52
+ // RawSignal.qaTicketId is FK-scoped to QATicket; tenant-isolation
53
+ // happens via the parent ticket projectId filter when we join.
54
+ // We restrict the lookup to this project's ticket ids to keep the
55
+ // query bounded.
56
+ const ticketIds = tickets.map((t) => t.id);
57
+ const signalRows = await prisma.rawSignal.findMany({
58
+ where: {
59
+ signalType: 'test_failure',
60
+ qaTicketId: { in: ticketIds },
61
+ },
62
+ select: { qaTicketId: true },
63
+ distinct: ['qaTicketId'],
64
+ });
65
+ const ticketIdsWithSignal = new Set(signalRows.map((r) => r.qaTicketId).filter((id) => id !== null));
66
+ const rows = tickets.map((t) => ({
67
+ id: t.id,
68
+ title: t.title,
69
+ classification: t.classification,
70
+ status: t.status,
71
+ createdAt: t.createdAt,
72
+ hasSignal: ticketIdsWithSignal.has(t.id),
73
+ }));
74
+ return { tickets: rows };
75
+ },
76
+ };
77
+ }
78
+ // ─── Validation ──────────────────────────────────────────────────────────
79
+ function validateFilter(filter) {
80
+ if (typeof filter.projectId !== 'string' || filter.projectId.length === 0) {
81
+ throw new CoverageGapReporterError('invalid_input', 'CoverageGapReporter: projectId is required');
82
+ }
83
+ }
84
+ //# sourceMappingURL=coverage-gap-reporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage-gap-reporter.js","sourceRoot":"","sources":["../src/coverage-gap-reporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,EACL,wBAAwB,GAKzB,MAAM,eAAe,CAAC;AASvB,4EAA4E;AAE5E,MAAM,UAAU,+BAA+B,CAC7C,MAAuC;IAEvC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAE1B,OAAO;QACL,KAAK,CAAC,cAAc,CAAC,MAAyB;YAC5C,cAAc,CAAC,MAAM,CAAC,CAAC;YAEvB,uEAAuE;YACvE,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAC7C,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE;gBACtC,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE;gBAC9B,MAAM,EAAE;oBACN,EAAE,EAAE,IAAI;oBACR,KAAK,EAAE,IAAI;oBACX,cAAc,EAAE,IAAI;oBACpB,MAAM,EAAE,IAAI;oBACZ,SAAS,EAAE,IAAI;iBAChB;aACF,CAAC,CAAC;YAEH,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACzB,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;YACzB,CAAC;YAED,iEAAiE;YACjE,EAAE;YACF,kEAAkE;YAClE,+DAA+D;YAC/D,kEAAkE;YAClE,iBAAiB;YACjB,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC3C,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC;gBACjD,KAAK,EAAE;oBACL,UAAU,EAAE,cAAc;oBAC1B,UAAU,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE;iBAC9B;gBACD,MAAM,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;gBAC5B,QAAQ,EAAE,CAAC,YAAY,CAAC;aACzB,CAAC,CAAC;YACH,MAAM,mBAAmB,GAAG,IAAI,GAAG,CACjC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAgB,EAAE,CAAC,EAAE,KAAK,IAAI,CAAC,CAC9E,CAAC;YAEF,MAAM,IAAI,GAA2B,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACvD,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,cAAc,EAAE,CAAC,CAAC,cAAc;gBAChC,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,SAAS,EAAE,CAAC,CAAC,SAAS;gBACtB,SAAS,EAAE,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;aACzC,CAAC,CAAC,CAAC;YAEJ,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC3B,CAAC;KACF,CAAC;AACJ,CAAC;AAED,4EAA4E;AAE5E,SAAS,cAAc,CAAC,MAAyB;IAC/C,IAAI,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1E,MAAM,IAAI,wBAAwB,CAChC,eAAe,EACf,4CAA4C,CAC7C,CAAC;IACJ,CAAC;AACH,CAAC"}