@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
@@ -0,0 +1,288 @@
1
+ /**
2
+ * QAUniformityStore — Prisma implementation against Derwin's QAUniformity
3
+ * table.
4
+ *
5
+ * QAP-019F (Sprint 2 Group D-3, Commit 1 foundation). Implements the
6
+ * QAUniformityStore contract from @derwinjs/sdk against the @derwinjs/db Prisma
7
+ * client. Migrated from Lifeline's `QAUniformityAudit` model + service —
8
+ * dropped the `Audit` suffix per Group D-3 plan Decision 5.
9
+ *
10
+ * Two methods:
11
+ * - ingestFindings: bulk-insert N findings as a single createMany call.
12
+ * Optionally tied to a qaRunId. Returns inserted + per-status counts.
13
+ * P2003 → fk_violation (unknown projectId or qaRunId).
14
+ * - getSummary: three aggregate sections for the dashboard's "Uniformity"
15
+ * tab — per-rule pass rates, latest violations (most recent run only),
16
+ * and trend across the most recent N runs (default 10, max 50).
17
+ *
18
+ * Tenant isolation: every method scopes by projectId in WHERE clauses.
19
+ * App-layer guard ahead of Sprint 3 RLS migration.
20
+ */
21
+ import { Prisma } from './prisma.js';
22
+ import { QAUniformityStoreError, } from '@derwinjs/sdk';
23
+ // ─── Constants (Lifeline parity) ─────────────────────────────────────────
24
+ const DEFAULT_RECENT = 10;
25
+ const MAX_RECENT = 50;
26
+ // ─── Factory ─────────────────────────────────────────────────────────────
27
+ export function createPrismaQAUniformityStore(config) {
28
+ const { prisma } = config;
29
+ return {
30
+ async ingestFindings(input) {
31
+ validateIngestInput(input);
32
+ const data = input.findings.map((f) => ({
33
+ projectId: input.projectId,
34
+ qaRunId: input.qaRunId ?? null,
35
+ pagePath: f.pagePath,
36
+ ruleName: f.ruleName,
37
+ ruleCategory: f.ruleCategory,
38
+ status: f.status,
39
+ violationDetail: f.violationDetail ?? null,
40
+ }));
41
+ let result;
42
+ try {
43
+ result = await prisma.qAUniformity.createMany({ data });
44
+ }
45
+ catch (err) {
46
+ // P2003 surfaces as fk_violation — the projectId or qaRunId FK is
47
+ // bad. Distinguish from generic DB errors so the route handler
48
+ // returns 400 vs 500.
49
+ if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === 'P2003') {
50
+ throw new QAUniformityStoreError('fk_violation', `QAUniformity FK violation: ${err.message}`, { projectId: input.projectId, qaRunId: input.qaRunId });
51
+ }
52
+ throw err;
53
+ }
54
+ // Per-status counts derived from the input (createMany returns count
55
+ // only). Cheaper than another query and 1:1 with what we just wrote
56
+ // because createMany either inserts everything or throws.
57
+ let passed = 0;
58
+ let failed = 0;
59
+ let skipped = 0;
60
+ for (const f of input.findings) {
61
+ if (f.status === 'PASSED')
62
+ passed++;
63
+ else if (f.status === 'FAILED')
64
+ failed++;
65
+ else
66
+ skipped++; // f.status === 'SKIPPED' — the remaining union member.
67
+ }
68
+ return {
69
+ inserted: result.count,
70
+ passed,
71
+ failed,
72
+ skipped,
73
+ };
74
+ },
75
+ async getSummary(filter) {
76
+ validateProjectId(filter.projectId);
77
+ const recent = clampRecent(filter.recent);
78
+ // ─── Section 1: per-rule pass rates (project-wide) ─────────────────
79
+ //
80
+ // groupBy ruleName + ruleCategory + status. Walk the result once to
81
+ // produce one row per (ruleName, ruleCategory) with passed/failed/
82
+ // skipped counts and pass rate.
83
+ const grouped = await prisma.qAUniformity.groupBy({
84
+ by: ['ruleName', 'ruleCategory', 'status'],
85
+ where: { projectId: filter.projectId },
86
+ _count: { _all: true },
87
+ _max: { createdAt: true },
88
+ });
89
+ const ruleAcc = new Map();
90
+ for (const row of grouped) {
91
+ const key = `${row.ruleName}::${row.ruleCategory}`;
92
+ const acc = ruleAcc.get(key) ?? {
93
+ ruleName: row.ruleName,
94
+ ruleCategory: row.ruleCategory,
95
+ passedCount: 0,
96
+ failedCount: 0,
97
+ skippedCount: 0,
98
+ lastSeenAt: new Date(0),
99
+ };
100
+ const count = row._count._all;
101
+ if (row.status === 'PASSED')
102
+ acc.passedCount += count;
103
+ else if (row.status === 'FAILED')
104
+ acc.failedCount += count;
105
+ else
106
+ acc.skippedCount += count; // 'SKIPPED' — remaining union member.
107
+ const groupLastSeen = row._max.createdAt;
108
+ if (groupLastSeen !== null && groupLastSeen > acc.lastSeenAt) {
109
+ acc.lastSeenAt = groupLastSeen;
110
+ }
111
+ ruleAcc.set(key, acc);
112
+ }
113
+ const rules = Array.from(ruleAcc.values()).map((r) => {
114
+ const passFailDenominator = r.passedCount + r.failedCount;
115
+ const passRate = passFailDenominator > 0 ? r.passedCount / passFailDenominator : 0;
116
+ return {
117
+ ruleName: r.ruleName,
118
+ ruleCategory: r.ruleCategory,
119
+ totalCount: r.passedCount + r.failedCount + r.skippedCount,
120
+ passedCount: r.passedCount,
121
+ failedCount: r.failedCount,
122
+ skippedCount: r.skippedCount,
123
+ passRate,
124
+ lastSeenAt: r.lastSeenAt,
125
+ };
126
+ });
127
+ // Sort: highest violation count first (most-broken rules at top).
128
+ rules.sort((a, b) => b.failedCount - a.failedCount);
129
+ // ─── Section 2: latest violations (most recent run only) ───────────
130
+ //
131
+ // Find the most recent qaRunId that has uniformity rows in this
132
+ // project, then list the FAILED rows for that run. Untied rows
133
+ // (qaRunId IS NULL) are excluded from latestViolations because
134
+ // "latest run" implies a run.
135
+ const latestRun = await prisma.qAUniformity.findFirst({
136
+ where: {
137
+ projectId: filter.projectId,
138
+ qaRunId: { not: null },
139
+ },
140
+ orderBy: { createdAt: 'desc' },
141
+ select: { qaRunId: true },
142
+ });
143
+ let latestViolations = [];
144
+ if (latestRun?.qaRunId !== undefined && latestRun.qaRunId !== null) {
145
+ const violations = await prisma.qAUniformity.findMany({
146
+ where: {
147
+ projectId: filter.projectId,
148
+ qaRunId: latestRun.qaRunId,
149
+ status: 'FAILED',
150
+ },
151
+ orderBy: { createdAt: 'desc' },
152
+ });
153
+ latestViolations = violations.map((v) => ({
154
+ pagePath: v.pagePath,
155
+ ruleName: v.ruleName,
156
+ ruleCategory: v.ruleCategory,
157
+ violationDetail: v.violationDetail,
158
+ createdAt: v.createdAt,
159
+ }));
160
+ }
161
+ // ─── Section 3: trend across recent N runs ─────────────────────────
162
+ //
163
+ // Find the most recent N distinct qaRunIds, then aggregate per-run.
164
+ // Untied rows excluded (no run → no trend point). One row per
165
+ // (projectId, qaRunId), oldest-first so the chart axis is monotonic.
166
+ const distinctRuns = await prisma.qAUniformity.groupBy({
167
+ by: ['qaRunId'],
168
+ where: {
169
+ projectId: filter.projectId,
170
+ qaRunId: { not: null },
171
+ },
172
+ _max: { createdAt: true },
173
+ orderBy: { _max: { createdAt: 'desc' } },
174
+ take: recent,
175
+ });
176
+ const runIds = distinctRuns.map((r) => r.qaRunId).filter((id) => id !== null);
177
+ let trend = [];
178
+ if (runIds.length > 0) {
179
+ const trendRows = await prisma.qAUniformity.groupBy({
180
+ by: ['qaRunId', 'status'],
181
+ where: {
182
+ projectId: filter.projectId,
183
+ qaRunId: { in: runIds },
184
+ },
185
+ _count: { _all: true },
186
+ _max: { createdAt: true },
187
+ });
188
+ const trendAcc = new Map();
189
+ for (const row of trendRows) {
190
+ if (row.qaRunId === null)
191
+ continue;
192
+ const acc = trendAcc.get(row.qaRunId) ?? {
193
+ passedCount: 0,
194
+ failedCount: 0,
195
+ capturedAt: new Date(0),
196
+ };
197
+ const count = row._count._all;
198
+ if (row.status === 'PASSED')
199
+ acc.passedCount += count;
200
+ else if (row.status === 'FAILED')
201
+ acc.failedCount += count;
202
+ const rowCapturedAt = row._max.createdAt;
203
+ if (rowCapturedAt !== null && rowCapturedAt > acc.capturedAt) {
204
+ acc.capturedAt = rowCapturedAt;
205
+ }
206
+ trendAcc.set(row.qaRunId, acc);
207
+ }
208
+ trend = Array.from(trendAcc.entries())
209
+ .map(([qaRunId, acc]) => {
210
+ const denominator = acc.passedCount + acc.failedCount;
211
+ return {
212
+ qaRunId,
213
+ passRate: denominator > 0 ? acc.passedCount / denominator : 0,
214
+ failedCount: acc.failedCount,
215
+ capturedAt: acc.capturedAt,
216
+ };
217
+ })
218
+ // Oldest first — chart x-axis renders left→right chronologically.
219
+ .sort((a, b) => a.capturedAt.getTime() - b.capturedAt.getTime());
220
+ }
221
+ return { rules, latestViolations, trend };
222
+ },
223
+ };
224
+ }
225
+ // ─── Validation ──────────────────────────────────────────────────────────
226
+ function validateProjectId(projectId) {
227
+ if (typeof projectId !== 'string' || projectId.length === 0) {
228
+ throw new QAUniformityStoreError('invalid_input', 'QAUniformityStore: projectId is required');
229
+ }
230
+ }
231
+ function validateIngestInput(input) {
232
+ validateProjectId(input.projectId);
233
+ if (input.qaRunId !== undefined &&
234
+ (typeof input.qaRunId !== 'string' || input.qaRunId.length === 0)) {
235
+ throw new QAUniformityStoreError('invalid_input', 'QAUniformityStore.ingestFindings: qaRunId must be a non-empty string when provided');
236
+ }
237
+ const findingsValue = input.findings;
238
+ if (!Array.isArray(findingsValue)) {
239
+ throw new QAUniformityStoreError('invalid_input', 'QAUniformityStore.ingestFindings: findings must be an array');
240
+ }
241
+ if (input.findings.length === 0) {
242
+ throw new QAUniformityStoreError('invalid_input', 'QAUniformityStore.ingestFindings: findings must not be empty');
243
+ }
244
+ input.findings.forEach((f, i) => {
245
+ validateFinding(f, i);
246
+ });
247
+ }
248
+ function validateFinding(f, idx) {
249
+ if (typeof f.pagePath !== 'string' || f.pagePath.length === 0) {
250
+ throw new QAUniformityStoreError('invalid_input', `QAUniformityStore.ingestFindings: findings[${String(idx)}].pagePath is required`);
251
+ }
252
+ if (typeof f.ruleName !== 'string' || f.ruleName.length === 0) {
253
+ throw new QAUniformityStoreError('invalid_input', `QAUniformityStore.ingestFindings: findings[${String(idx)}].ruleName is required`);
254
+ }
255
+ if (typeof f.ruleCategory !== 'string' || f.ruleCategory.length === 0) {
256
+ throw new QAUniformityStoreError('invalid_input', `QAUniformityStore.ingestFindings: findings[${String(idx)}].ruleCategory is required`);
257
+ }
258
+ // Cast through unknown to bypass the literal-union narrowing — this is a
259
+ // runtime guard against consumers that bypass TS (route layer / direct
260
+ // SDK callers passing untyped JSON).
261
+ const statusValue = f.status;
262
+ if (statusValue !== 'PASSED' && statusValue !== 'FAILED' && statusValue !== 'SKIPPED') {
263
+ throw new QAUniformityStoreError('invalid_input', `QAUniformityStore.ingestFindings: findings[${String(idx)}].status must be PASSED|FAILED|SKIPPED`);
264
+ }
265
+ }
266
+ function clampRecent(recent) {
267
+ if (recent === undefined)
268
+ return DEFAULT_RECENT;
269
+ if (recent < 1)
270
+ return 1;
271
+ if (recent > MAX_RECENT)
272
+ return MAX_RECENT;
273
+ return recent;
274
+ }
275
+ export function mapUniformity(row) {
276
+ return {
277
+ id: row.id,
278
+ projectId: row.projectId,
279
+ qaRunId: row.qaRunId,
280
+ pagePath: row.pagePath,
281
+ ruleName: row.ruleName,
282
+ ruleCategory: row.ruleCategory,
283
+ status: row.status,
284
+ violationDetail: row.violationDetail,
285
+ createdAt: row.createdAt,
286
+ };
287
+ }
288
+ //# sourceMappingURL=qa-uniformity-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qa-uniformity-store.js","sourceRoot":"","sources":["../src/qa-uniformity-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * QAP-020 smoke — exercises the auto-fix dispatcher (DRY_RUN) end-to-end
4
+ * against the real local Postgres. Parity reference: Lifeline's
5
+ * scripts/qa-auto-fix-smoke.ts (QLL-028).
6
+ *
7
+ * Pipeline exercised:
8
+ * 1. Seed Lifeline-project QARun + RawSignal + QATicket (HIGH/PRODUCT_BUG)
9
+ * 2. Construct Prisma-backed AttemptStore + TicketStore + FixPolicy
10
+ * (FixPolicy uses a `defaultConfig` override so we don't need a
11
+ * Policy row — { enabled: true, mode: 'DRY_RUN', minSeverity: 'LOW',
12
+ * maxAttemptsPerDay: 5 })
13
+ * 3. Stub the FixAuthor adapter via the dispatcher's
14
+ * QA_AUTOFIX_E2E_FIXTURE_DIFF env hook (no Anthropic call). Stub the
15
+ * Repo adapter to throw on any method (defensive — DRY_RUN should
16
+ * never reach openPR/etc).
17
+ * 4. Call dispatcher.dispatch({ qaTicketId, projectId })
18
+ * 5. Assert outcome === 'dry_run_logged'
19
+ * 6. Assert the QAFixAttempt row has dispatchStatus === 'PRE_VERIFY_PASSED'
20
+ * (Derwin's terminal-for-DRY_RUN status — see dispatcher.ts line ~218),
21
+ * a non-empty diff, prUrl null, and attemptedAt within the last minute
22
+ *
23
+ * Run: pnpm --filter @derwinjs/db smoke:auto-fix
24
+ *
25
+ * Prereqs:
26
+ * - Local Postgres running on port 5433 (per Sprint 1 docker setup)
27
+ * - DATABASE_URL set in packages/db/.env
28
+ * - Migrations applied
29
+ * - Seed run (Lifeline project exists at slug='lifeline')
30
+ *
31
+ * Self-cleaning: deletes its own test rows in a finally block whether the
32
+ * script succeeds or fails. Re-runs idempotently.
33
+ *
34
+ * Prints "QAP-020 AUTO-FIX SMOKE READY ✓" on success. Exits 1 on failure.
35
+ */
36
+ export {};
37
+ //# sourceMappingURL=smoke-auto-fix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smoke-auto-fix.d.ts","sourceRoot":"","sources":["../../src/scripts/smoke-auto-fix.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG"}
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * QAP-020 smoke — exercises the auto-fix dispatcher (DRY_RUN) end-to-end
4
+ * against the real local Postgres. Parity reference: Lifeline's
5
+ * scripts/qa-auto-fix-smoke.ts (QLL-028).
6
+ *
7
+ * Pipeline exercised:
8
+ * 1. Seed Lifeline-project QARun + RawSignal + QATicket (HIGH/PRODUCT_BUG)
9
+ * 2. Construct Prisma-backed AttemptStore + TicketStore + FixPolicy
10
+ * (FixPolicy uses a `defaultConfig` override so we don't need a
11
+ * Policy row — { enabled: true, mode: 'DRY_RUN', minSeverity: 'LOW',
12
+ * maxAttemptsPerDay: 5 })
13
+ * 3. Stub the FixAuthor adapter via the dispatcher's
14
+ * QA_AUTOFIX_E2E_FIXTURE_DIFF env hook (no Anthropic call). Stub the
15
+ * Repo adapter to throw on any method (defensive — DRY_RUN should
16
+ * never reach openPR/etc).
17
+ * 4. Call dispatcher.dispatch({ qaTicketId, projectId })
18
+ * 5. Assert outcome === 'dry_run_logged'
19
+ * 6. Assert the QAFixAttempt row has dispatchStatus === 'PRE_VERIFY_PASSED'
20
+ * (Derwin's terminal-for-DRY_RUN status — see dispatcher.ts line ~218),
21
+ * a non-empty diff, prUrl null, and attemptedAt within the last minute
22
+ *
23
+ * Run: pnpm --filter @derwinjs/db smoke:auto-fix
24
+ *
25
+ * Prereqs:
26
+ * - Local Postgres running on port 5433 (per Sprint 1 docker setup)
27
+ * - DATABASE_URL set in packages/db/.env
28
+ * - Migrations applied
29
+ * - Seed run (Lifeline project exists at slug='lifeline')
30
+ *
31
+ * Self-cleaning: deletes its own test rows in a finally block whether the
32
+ * script succeeds or fails. Re-runs idempotently.
33
+ *
34
+ * Prints "QAP-020 AUTO-FIX SMOKE READY ✓" on success. Exits 1 on failure.
35
+ */
36
+ import { PrismaClient } from '../prisma.js';
37
+ import { createFixDispatcher } from '@derwinjs/core';
38
+ import { createPrismaQATicketStore } from '../qa-ticket-store.js';
39
+ import { createPrismaQAFixAttemptStore } from '../qa-fix-attempt-store.js';
40
+ import { createPrismaFixPolicy } from '../fix-policy.js';
41
+ // ─── Constants ────────────────────────────────────────────────────────────
42
+ const SMOKE_MARKER = '[SMOKE-QAP-020]';
43
+ const SMOKE_PROJECT_SLUG = 'lifeline';
44
+ const DASHBOARD_BASE_URL = process.env.DERWIN_DASHBOARD_URL ?? 'http://localhost:3000';
45
+ const FIXTURE_DIFF = `--- a/src/example/fixture.ts
46
+ +++ b/src/example/fixture.ts
47
+ @@ -1,3 +1,3 @@
48
+ -const broken = true;
49
+ +const broken = false; // QAP-020 fixture diff
50
+ export default broken;
51
+ `;
52
+ // Inject the fixture diff via the dispatcher's test-only env hook. Must be
53
+ // set BEFORE dispatcher.dispatch() runs. The dispatcher's callAuthor()
54
+ // honors QA_AUTOFIX_E2E_FIXTURE_DIFF when NODE_ENV !== 'production' and
55
+ // returns this diff instead of calling the real FixAuthor adapter — lets
56
+ // the smoke exercise the full pipeline without burning tokens or needing
57
+ // network.
58
+ process.env.QA_AUTOFIX_E2E_FIXTURE_DIFF = FIXTURE_DIFF;
59
+ // ─── Stub adapters ────────────────────────────────────────────────────────
60
+ /**
61
+ * QAFixAuthorAdapter stub. The env-fixture hook short-circuits the
62
+ * dispatcher BEFORE it calls generateDiff(), so this should never be
63
+ * reached. If it is, throw loudly so the smoke fails rather than silently
64
+ * burning a real Anthropic call.
65
+ */
66
+ const stubFixAuthor = {
67
+ name: 'qap020-smoke-stub',
68
+ generateDiff: (_req) => {
69
+ throw new Error('smoke: stubFixAuthor.generateDiff was called — env fixture hook should have short-circuited');
70
+ },
71
+ };
72
+ /**
73
+ * QARepoAdapter stub. DRY_RUN mode should never reach the repo adapter
74
+ * (the dispatcher branches before openPR/mergePR). Defensive guard — if
75
+ * any method is called, the smoke fails rather than attempting a real
76
+ * GitHub operation.
77
+ */
78
+ const stubRepo = {
79
+ openPR: (_opts) => {
80
+ throw new Error('smoke: stubRepo.openPR called — DRY_RUN should not reach repo');
81
+ },
82
+ mergePR: (_prNumber) => {
83
+ throw new Error('smoke: stubRepo.mergePR called — DRY_RUN should not reach repo');
84
+ },
85
+ revertPR: (_prNumber, _reason) => {
86
+ throw new Error('smoke: stubRepo.revertPR called — DRY_RUN should not reach repo');
87
+ },
88
+ describeRepo: () => {
89
+ throw new Error('smoke: stubRepo.describeRepo called — DRY_RUN should not reach repo');
90
+ },
91
+ fetchPRDiff: (_prNumber) => Promise.resolve(null),
92
+ };
93
+ // ─── Wiring ───────────────────────────────────────────────────────────────
94
+ const prisma = new PrismaClient();
95
+ const ticketStore = createPrismaQATicketStore({
96
+ prisma,
97
+ dashboardBaseUrl: DASHBOARD_BASE_URL,
98
+ });
99
+ const attemptStore = createPrismaQAFixAttemptStore({ prisma });
100
+ const fixPolicy = createPrismaFixPolicy({
101
+ prisma,
102
+ attemptStore,
103
+ // Override platform defaults so the smoke doesn't depend on a Policy row
104
+ // existing for the Lifeline project. Permissive for the fixture path.
105
+ defaultConfig: {
106
+ enabled: true,
107
+ mode: 'DRY_RUN',
108
+ minSeverity: 'LOW',
109
+ maxAttemptsPerDay: 5,
110
+ pathAllowlist: [],
111
+ pathBlocklist: [],
112
+ },
113
+ });
114
+ let createdRunId;
115
+ let createdSignalId;
116
+ let createdTicketId;
117
+ const createdAttemptIds = [];
118
+ async function main() {
119
+ // ─── Setup: find Lifeline + create QARun + RawSignal + QATicket ─────
120
+ const lifeline = await prisma.project.findUnique({
121
+ where: { slug: SMOKE_PROJECT_SLUG },
122
+ });
123
+ if (!lifeline) {
124
+ throw new Error(`Project '${SMOKE_PROJECT_SLUG}' not found. Run \`pnpm db:seed\` first.`);
125
+ }
126
+ console.log(`✓ Found Lifeline (id=${lifeline.id})`);
127
+ const run = await prisma.qARun.create({
128
+ data: {
129
+ projectId: lifeline.id,
130
+ triggeredBy: SMOKE_MARKER,
131
+ triggerType: 'OPERATOR_ON_DEMAND',
132
+ scope: { specs: ['smoke/auto-fix-fixture.spec.ts'], note: SMOKE_MARKER },
133
+ status: 'COMPLETED',
134
+ completedAt: new Date(),
135
+ signalsRaised: 1,
136
+ ticketsCreated: 1,
137
+ },
138
+ });
139
+ createdRunId = run.id;
140
+ console.log(`✓ Created synthetic QARun (id=${run.id})`);
141
+ const created = await ticketStore.createQATicket({
142
+ projectId: lifeline.id,
143
+ qaRunId: run.id,
144
+ title: `${SMOKE_MARKER} fixture: const broken should be false`,
145
+ surface: 'FUNCTIONAL_CORRECTNESS',
146
+ classification: 'PRODUCT_BUG',
147
+ severity: 'HIGH',
148
+ riskTier: 'HIGH',
149
+ reproSteps: [
150
+ {
151
+ step: 1,
152
+ action: 'Run the fixture',
153
+ expected: 'broken === false',
154
+ actual: 'broken === true',
155
+ },
156
+ ],
157
+ suspectedRootCause: `${SMOKE_MARKER} fixture file has \`const broken = true\` — should be false`,
158
+ blastRadius: {
159
+ affectedFiles: ['src/example/fixture.ts'],
160
+ affectedTests: ['smoke/auto-fix-fixture.spec.ts'],
161
+ affectedPages: [],
162
+ },
163
+ proposedFixApproach: 'Flip `const broken = true` to `const broken = false`',
164
+ });
165
+ createdTicketId = created.ticket.id;
166
+ console.log(`✓ Created QATicket (id=${created.ticket.id}, severity=HIGH)`);
167
+ // RawSignal pointing at the ticket. Per schema this is the discovery
168
+ // input that triage already processed (qaTicketId set means investigated).
169
+ const signal = await prisma.rawSignal.create({
170
+ data: {
171
+ qaRunId: run.id,
172
+ qaTicketId: created.ticket.id,
173
+ routeName: 'qap020-smoke',
174
+ signalType: 'test_failure',
175
+ rawData: {
176
+ marker: SMOKE_MARKER,
177
+ testTitle: '[QAP-020] fixture',
178
+ errorMessage: 'fixture: const broken should be false',
179
+ },
180
+ rawArtifactRefs: [],
181
+ investigatedAt: new Date(),
182
+ },
183
+ });
184
+ createdSignalId = signal.id;
185
+ console.log(`✓ Created RawSignal (id=${signal.id})`);
186
+ // ─── Construct dispatcher + dispatch ────────────────────────────────
187
+ const dispatcher = createFixDispatcher({
188
+ fixAuthor: stubFixAuthor,
189
+ repo: stubRepo,
190
+ ticketStore,
191
+ attemptStore,
192
+ policy: fixPolicy,
193
+ });
194
+ console.log('✓ Dispatcher constructed; calling dispatch() (DRY_RUN, fixture diff via env hook)...');
195
+ const beforeDispatch = Date.now();
196
+ const out = await dispatcher.dispatch({
197
+ qaTicketId: created.ticket.id,
198
+ projectId: lifeline.id,
199
+ });
200
+ console.log(` outcome=${out.outcome} attemptId=${out.attemptId ?? 'null'}`);
201
+ if (out.outcome !== 'dry_run_logged') {
202
+ throw new Error(`Expected outcome 'dry_run_logged', got '${out.outcome}' (errorMessage=${out.errorMessage ?? ''})`);
203
+ }
204
+ if (!out.attemptId) {
205
+ throw new Error('dispatch returned dry_run_logged but attemptId is null');
206
+ }
207
+ createdAttemptIds.push(out.attemptId);
208
+ // ─── Verify QAFixAttempt row landed correctly ───────────────────────
209
+ // NOTE: Lifeline writes 'DRY_RUN_LOGGED' as its terminal status; Derwin's
210
+ // AttemptStatus enum doesn't have that value — the dispatcher writes
211
+ // 'PRE_VERIFY_PASSED' for DRY_RUN (see packages/core/src/auto-fix/
212
+ // dispatcher.ts line ~218). The semantic equivalent is the OUTCOME
213
+ // string 'dry_run_logged' (asserted above).
214
+ const attempt = await attemptStore.getAttempt({
215
+ id: out.attemptId,
216
+ projectId: lifeline.id,
217
+ });
218
+ if (!attempt) {
219
+ throw new Error(`QAFixAttempt ${out.attemptId} not found after dispatch`);
220
+ }
221
+ if (attempt.dispatchStatus !== 'PRE_VERIFY_PASSED') {
222
+ throw new Error(`attempt.dispatchStatus expected 'PRE_VERIFY_PASSED' (Derwin DRY_RUN terminal), got '${attempt.dispatchStatus}'`);
223
+ }
224
+ if (!attempt.diff || attempt.diff.length === 0) {
225
+ throw new Error('attempt.diff was empty — fixture diff did not persist');
226
+ }
227
+ if (attempt.prUrl !== null) {
228
+ throw new Error(`attempt.prUrl was non-null in DRY_RUN mode: ${attempt.prUrl}`);
229
+ }
230
+ const attemptedAtMs = attempt.attemptedAt.getTime();
231
+ if (attemptedAtMs < beforeDispatch - 1000 || attemptedAtMs > Date.now() + 1000) {
232
+ throw new Error(`attempt.attemptedAt (${attempt.attemptedAt.toISOString()}) not within the dispatch window`);
233
+ }
234
+ console.log(` ✓ attempt ${out.attemptId} captured ${String(attempt.diff.length)}-byte diff, status=PRE_VERIFY_PASSED, prUrl=null`);
235
+ console.log('\nQAP-020 AUTO-FIX SMOKE READY ✓');
236
+ }
237
+ async function cleanup() {
238
+ // Delete in reverse FK order: attempt → signal → ticket → run.
239
+ // QAFixAttempt has Cascade on qaTicketId, but be explicit so cleanup is
240
+ // grep-friendly and survives if the schema relaxes that cascade later.
241
+ if (createdAttemptIds.length > 0) {
242
+ await prisma.qAFixAttempt.deleteMany({
243
+ where: { id: { in: createdAttemptIds } },
244
+ });
245
+ }
246
+ if (createdSignalId) {
247
+ await prisma.rawSignal.deleteMany({ where: { id: createdSignalId } });
248
+ }
249
+ if (createdTicketId) {
250
+ await prisma.qATicket.deleteMany({ where: { id: createdTicketId } });
251
+ }
252
+ if (createdRunId) {
253
+ await prisma.qARun.delete({ where: { id: createdRunId } });
254
+ }
255
+ }
256
+ main()
257
+ .catch((err) => {
258
+ console.error('\n[smoke-auto-fix] ✗ Smoke failed:', err);
259
+ process.exitCode = 1;
260
+ })
261
+ .finally(async () => {
262
+ try {
263
+ await cleanup();
264
+ }
265
+ catch (cleanupErr) {
266
+ console.error('[smoke-auto-fix] cleanup error (manual row pruning may be needed):', cleanupErr);
267
+ }
268
+ await prisma.$disconnect();
269
+ });
270
+ //# sourceMappingURL=smoke-auto-fix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smoke-auto-fix.js","sourceRoot":"","sources":["../../src/scripts/smoke-auto-fix.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * QAP-014 / QAP-015 smoke — exercises the OutcomeRecorder + TrustScoreUpdater
4
+ * + ranker against the real local Postgres. Parity reference: Lifeline's
5
+ * scripts/qa-learning-backfill.ts (QLL-035).
6
+ *
7
+ * Run: pnpm --filter @derwinjs/db smoke:learning-loop
8
+ *
9
+ * Prereqs:
10
+ * - Local Postgres running on port 5433 (per Sprint 1 docker setup)
11
+ * - DATABASE_URL set in packages/db/.env
12
+ * - Migrations applied
13
+ * - Seed run (Lifeline project exists at slug='lifeline')
14
+ *
15
+ * Self-cleaning: deletes its own test rows in a finally block. Re-runs
16
+ * idempotently.
17
+ *
18
+ * Prints "QAP-014/015 LEARNING LOOP READY ✓" on success. Exits 1 on failure.
19
+ */
20
+ export {};
21
+ //# sourceMappingURL=smoke-learning-loop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smoke-learning-loop.d.ts","sourceRoot":"","sources":["../../src/scripts/smoke-learning-loop.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;GAiBG"}